3、页面导航与等待:page.goto()、page.wait_for_load_state()、page.wait_for_selector()、网络空闲等待
页面导航,说白了就是让浏览器去访问一个网址。但这里有个坑——你告诉浏览器「去打开百度」,它到底什么时候算打开完成?
我刚开始用 Playwright 时,以为 page.goto() 返回了就万事大吉。结果呢?页面是渲染了,但某个按钮还没加载出来,脚本直接报错。嗯,这就是我们今天要聊的核心——导航与等待。
3.1 page.goto():最基础的导航
page.goto() 是 Playwright 里最常用的导航方法。它的用法很简单:
await page.goto('https://example.com')
但这里有个细节——goto() 默认会等待页面触发 load 事件。也就是说,它会等到页面的主要资源(HTML、CSS、JS)都加载完毕才返回。
我在项目中遇到过一个问题:某个页面加载特别慢,因为里面嵌了个第三方广告。广告资源一直 pending,goto() 就一直卡着。后来我加了超时参数:
await page.goto('https://example.com', { timeout: 10000 })
10 秒超时,超时了就抛异常。这样至少不会让脚本无限等下去。
wait_until: 'domcontentloaded'。这样 goto() 会在 DOM 解析完成后立即返回,速度会快很多。
await page.goto('https://example.com', { wait_until: 'domcontentloaded' })
3.2 page.wait_for_load_state():等待特定加载状态
有时候,页面导航已经完成了,但某个异步操作还在跑。比如页面用了 setTimeout 加载数据,或者用了 WebSocket 推送。这时候 goto() 已经返回了,但页面还没「稳定」。
page.wait_for_load_state() 就是干这个的。它可以等待页面进入以下几种状态:
| 状态 | 说明 |
|---|---|
load |
页面所有资源加载完成(默认) |
domcontentloaded |
DOM 解析完成,不等待图片等资源 |
networkidle |
网络连接空闲(500ms 内无新请求) |
我个人最常用的是 networkidle。为什么?因为很多现代页面会先加载骨架屏,然后通过 AJAX 请求拉数据。如果只等 load,数据可能还没到。
await page.goto('https://example.com')
await page.wait_for_load_state('networkidle')
这样写,相当于告诉 Playwright:「等页面所有网络请求都消停了,再往下走。」
networkidle 不是万能的。如果页面有轮询请求(比如每 2 秒发一次心跳),那它永远不会空闲。这时候就别用 networkidle 了,改用 wait_for_selector() 更靠谱。
3.3 page.wait_for_selector():等待元素出现
这是我最喜欢的方法,没有之一。因为它最直观——「我要等某个按钮出现,出现了就继续,不出现就等。」
await page.wait_for_selector('#submit-button', { timeout: 5000 })
这段代码的意思是:等 5 秒,如果 #submit-button 出现了,就继续;如果 5 秒后还没出现,就抛异常。
我曾经踩过一个坑:某个页面用了动态加载,按钮在滚动到可视区域后才渲染。直接用 click() 会报错,因为元素还没挂到 DOM 上。后来我加了个 wait_for_selector(),问题就解决了。
你想想看,wait_for_selector() 其实比 sleep() 优雅得多。硬等几秒,要么等不够,要么等太久。而 wait_for_selector() 是「按需等待」——元素一出现就继续,不浪费一毫秒。
wait_for_selector() 就别用 sleep()。前者是智能等待,后者是盲等。
3.4 网络空闲等待:什么时候用?
网络空闲等待,说白了就是 wait_for_load_state('networkidle')。它适合以下场景:
- 页面加载后,还有一批异步请求(比如埋点、数据拉取)
- 页面使用了懒加载,滚动后才触发请求
- 你希望确保所有网络活动都停止了,再截图或断言
但要注意,网络空闲等待有它的局限性。我记得有一次,页面加载了一个 WebSocket 连接,它一直保持长连接。结果 networkidle 永远等不到空闲,脚本直接超时了。
所以我的建议是:优先用 wait_for_selector() 等待关键元素,网络空闲等待作为补充手段。比如:
await page.goto('https://example.com')
await page.wait_for_load_state('networkidle') // 等网络消停
await page.wait_for_selector('.data-loaded') // 等数据渲染
这样双重保障,基本不会出问题。
3.5 实战:一个完整的导航+等待流程
来,我们写一个完整的例子。假设你要测试一个电商网站的商品详情页:
// 1. 导航到商品页,等 DOM 解析完成
await page.goto('https://shop.example.com/product/123', {
wait_until: 'domcontentloaded'
})
// 2. 等商品图片加载完成(图片是懒加载的)
await page.wait_for_selector('.product-image[loaded]')
// 3. 等「加入购物车」按钮出现
await page.wait_for_selector('#add-to-cart', { timeout: 10000 })
// 4. 点击按钮
await page.click('#add-to-cart')
// 5. 等购物车图标更新(网络请求完成)
await page.wait_for_load_state('networkidle')
// 6. 断言购物车数量
const cartCount = await page.textContent('.cart-badge')
expect(cartCount).toBe('1')
这个流程里,每一步都用了合适的等待策略。没有盲等,也没有过早操作。
goto() 后面跟一个 wait_for_load_state('networkidle'),然后再跟一个 wait_for_selector() 等关键元素。这样虽然多写两行代码,但脚本稳定性提升很明显。
3.6 避坑指南
最后,分享几个我踩过的坑:
- 不要过度依赖
networkidle:如果页面有轮询或长连接,它会一直等不到空闲。这时候改用wait_for_selector()更靠谱。 - 超时时间要合理:我见过有人设 30 秒超时,结果页面加载慢,脚本跑一次要 5 分钟。建议根据页面实际情况设 5-10 秒,超时了就重试或报错。
- 注意页面跳转:点击某个链接后,页面可能会跳转。这时候
wait_for_selector()可能找不到元素,因为 DOM 被替换了。可以用page.wait_for_url()等待新 URL。
嗯,导航与等待就聊到这里。说白了,核心就一句话:别急着操作,等页面准备好了再说。下一章我们会聊元素定位与交互,到时候这些等待技巧还会用到。