定位断言
Day 09 - Auto-waiting and Timeouts
今日目标
- 理解 Playwright 的 auto-waiting。
- 避免滥用
waitForTimeout()。 - 掌握合理等待策略。
学习时间安排(60–120 分钟)
| 时间 | 模块 | 做什么 |
|---|---|---|
| 0-20 分钟 | 核心概念与词汇 | 读概念表,重点理解 actionability 的 5 个条件。 |
| 20-45 分钟 | 官方文档阅读 | 读 Auto-waiting 文档,理解 action 和 assertion 各自的等待。 |
| 45-75 分钟 | 实操练习 | 改造硬等待脚本,验证自动等待的行为差异。 |
| 75-105 分钟 | 示例代码改写 | 把 waitForTimeout 全部替换成 locator/assertion 等待。 |
| 105-120 分钟 | 复盘与作业 | 完成改造前后对比表,完成今日问题。 |
核心概念与词汇
| English | 中文 | 场景用法 |
|---|---|---|
| auto-waiting | 自动等待 | Use it when you explain that actions and assertions wait for conditions automatically. |
| actionability | 可操作性 | Use it when you describe the conditions Playwright checks before an action. |
| attached | 已挂载 | Use it when you describe an element being present in the DOM. |
| visible | 可见 | Use it when you describe an element having a visible bounding box. |
| stable | 稳定 | Use it when you describe an element no longer animating or moving. |
| enabled | 可用 | Use it when you describe a control that is not disabled. |
| receives events | 可接收事件 | Use it when you describe an element not covered by another element. |
waitForURL() | 等待 URL | Use it when you wait for navigation or redirects. |
waitForLoadState() | 等待加载状态 | Use it when you wait for load, domcontentloaded, or networkidle. |
networkidle | 网络空闲 | Use it with caution when you wait for no network requests for 500ms. |
waitForTimeout() | 硬等待 | Use it only for debugging or simulating a user pause, never as synchronization. |
| timeout | 超时 | Use it when you describe how long an action or assertion waits before failing. |
学习材料
- 必读:[Auto-waiting](https://playwright.dev/docs/actionability)
- 必读:[Navigations](https://playwright.dev/docs/navigations) 中关于等待的说明
- 选读:[Timeouts](https://playwright.dev/docs/test-timeouts)
重点理解
Playwright 会在 action 前自动检查元素是否:
- attached。
- visible。
- stable。
- enabled。
- receives events。
常见等待方式:
await expect(locator).toBeVisible();
await page.waitForURL(/dashboard/);
await page.waitForLoadState('networkidle'); // 谨慎使用
await locator.waitFor();
不推荐:
await page.waitForTimeout(5000);
除非你是在 debug 或模拟用户等待。
实操步骤
把一段使用 waitForTimeout() 的脚本改造成基于 locator/assertion 的等待。
示例代码
// 不推荐
await page.getByRole('button', { name: 'Submit' }).click();
await page.waitForTimeout(3000);
await expect(page.getByText('Success')).toBeVisible();
// 推荐:断言自带重试,等到出现为止
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByText('Success')).toBeVisible();
// 等待跳转用 waitForURL,而不是 sleep 后读 URL
await page.getByRole('link', { name: 'Dashboard' }).click();
await page.waitForURL(/dashboard/);
常见坑
- 全项目散落
waitForTimeout(3000),网络一慢就超时、网络一快就浪费时间。 - 用
networkidle等待长轮询/实时推送页面,永远等不到空闲。 - 在断言前手动 sleep,以为 sleep 能替代断言的重试机制。
- 全局 timeout 和 expect timeout 分不清:前者是整条测试,后者是单个断言。
- 不等待就用
expect(locator).toBeVisible()的反向断言(不可见),其实等到了超时。
今日产出
- 一份“等待策略改造前后对比”。
- 至少删除 2 个硬等待。
今日问题
- Playwright 自动等待会等什么?
- 为什么硬等待会导致测试不稳定或变慢?
- 什么情况下需要显式等待?
waitForURL适合什么场景?networkidle为什么要谨慎使用?
复盘要点
- 等待策略的口诀:能断言就断言,跳转等 URL,元素等 locator,sleep 只用于 debug。
- 硬等待是 flaky 的最大来源之一,删掉它比加长它更有效。
- 理解“自动等待 + 超时失败”后,你才能读懂测试失败日志里的等待信息。
AI 时代扩展:AI 辅助 Playwright 测试
新增概念
| English | 中文 |
|---|---|
| flaky test | 不稳定测试 |
| synchronization | 同步策略 |
| wait strategy | 等待策略 |
| false negative | 假阴性 |
适用场景
让 AI 扫描测试代码找出所有硬等待并给出替代方案,或者让 AI 分析一条 flaky 测试的失败日志,判断是等待问题还是断言问题。
可复用表达 / 提示词
请扫描我的测试代码,找出所有 waitForTimeout 和 sleep,逐个说明它想等什么、应该用什么替代。
这条测试在 CI 上时好时坏,失败日志如下,请判断是等待问题、数据问题还是断言问题,并给出依据。
追问加练
- AI 把一处 sleep 改成断言等待,你如何验证改法有效?
- AI 说“这个 flaky 是等待问题”,你需要哪些证据才相信?
- 为什么删硬等待可能让测试变快,也可能暴露新问题?
今日作业
- 完成“等待策略改造前后对比”表,至少 3 组。
- 在自己的测试里找出并删除所有硬等待。
- 故意给页面加慢速接口(用浏览器 DevTools throttling),验证你的等待策略是否稳健。