Playwright 自动等待与超时
本章深入介绍 Playwright 的自动等待机制,以及手动等待和超时配置的完整知识。
为什么需要自动等待
现代 Web 应用大量使用异步渲染,页面内容不会瞬间全部加载完毕。
传统测试框架中,开发者不得不写 sleep 或 waitFor 来等待,但这些操作不够灵活,要么等待太久拖慢测试,要么等待不够导致 flaky。
Playwright 将等待逻辑内置到每个 Locator 和 Action 中,从根本上解决这个问题。
三层自动等待体系
第一层:Locator 的自动等待
当你创建 Locator 时,Playwright 不立即查找元素,而是在你执行操作或断言时才开始查找并自动等待。
实例
// 创建 Locator(此时不会查找元素)
const btn = page.getByRole('button', { name: '延迟出现的按钮' });
// 点击时自动等待按钮出现(最多等待 action timeout 时间)
await btn.click();
const btn = page.getByRole('button', { name: '延迟出现的按钮' });
// 点击时自动等待按钮出现(最多等待 action timeout 时间)
await btn.click();
第二层:Action 的可操作性检查
在执行操作(click、fill 等)前,Playwright 确保元素:Attached → Visible → Stable → Receives Events → Enabled。
第三层:Assertion 的自动重试
断言失败后不会立即报错,而是持续重试直到条件满足或超时。
实例
// 这个断言会不断重试 5 秒,直到文本出现或超时
await expect(page.getByText('数据加载完成')).toBeVisible();
await expect(page.getByText('数据加载完成')).toBeVisible();
手动等待方法
在自动等待不够用的特殊场景下,Playwright 也提供了手动等待方法。
page.waitForTimeout(ms) ——固定等待
实例
// 固定等待 2 秒(一般不推荐)
await page.waitForTimeout(2000);
await page.waitForTimeout(2000);
尽量避免使用
waitForTimeout,固定等待在设备性能差异下极不稳定。优先使用后文的waitForURL、waitForResponse等事件驱动等待。
page.waitForURL() ——等待 URL 变化
实例
// 等待 URL 精确匹配
await page.waitForURL('https://www.runoob.com/dashboard');
// 等待 URL 包含特定模式
await page.waitForURL(/dashboard/);
await page.waitForURL('https://www.runoob.com/dashboard');
// 等待 URL 包含特定模式
await page.waitForURL(/dashboard/);
page.waitForLoadState() ——等待加载状态
实例
// 等待 load 事件
await page.waitForLoadState('load');
// 等待 DOMContentLoaded
await page.waitForLoadState('domcontentloaded');
// 等待网络空闲
await page.waitForLoadState('networkidle');
await page.waitForLoadState('load');
// 等待 DOMContentLoaded
await page.waitForLoadState('domcontentloaded');
// 等待网络空闲
await page.waitForLoadState('networkidle');
page.waitForResponse() ——等待网络响应
实例
// 等待特定 API 响应
const response = await page.waitForResponse(
resp => resp.url().includes('/api/data') && resp.status() === 200
);
// 获取响应数据
const data = await response.json();
const response = await page.waitForResponse(
resp => resp.url().includes('/api/data') && resp.status() === 200
);
// 获取响应数据
const data = await response.json();
page.waitForEvent() ——等待事件
实例
// 等待弹窗出现
const dialogPromise = page.waitForEvent('dialog');
await page.getByRole('button', { name: '删除' }).click();
const dialog = await dialogPromise;
await dialog.accept();
// 等待新页面打开
const pagePromise = page.context().waitForEvent('page');
await page.getByRole('link', { name: '新窗口打开' }).click();
const newPage = await pagePromise;
const dialogPromise = page.waitForEvent('dialog');
await page.getByRole('button', { name: '删除' }).click();
const dialog = await dialogPromise;
await dialog.accept();
// 等待新页面打开
const pagePromise = page.context().waitForEvent('page');
await page.getByRole('link', { name: '新窗口打开' }).click();
const newPage = await pagePromise;
超时配置详解
Playwright 有 4 种超时类型,分别作用于不同层面:
| 超时类型 | 配置文件位置 | 默认值 | 作用对象 |
|---|---|---|---|
| Test timeout | timeout | 30000ms | 整个测试用例 |
| Expect timeout | expect.timeout | 5000ms | 断言重试 |
| Action timeout | use.actionTimeout | 无(不限) | 元素操作(click、fill 等) |
| Navigation timeout | use.navigationTimeout | 无(不限) | 页面导航(goto、goBack 等) |
全局配置
实例
// 文件路径:playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// 每个测试的总超时时间(毫秒)
timeout: 60000,
// 断言自动重试的超时时间
expect: {
timeout: 10000,
},
use: {
// 每个操作(click、fill 等)的超时时间
actionTimeout: 15000,
// 每次导航的超时时间
navigationTimeout: 30000,
},
});
import { defineConfig } from '@playwright/test';
export default defineConfig({
// 每个测试的总超时时间(毫秒)
timeout: 60000,
// 断言自动重试的超时时间
expect: {
timeout: 10000,
},
use: {
// 每个操作(click、fill 等)的超时时间
actionTimeout: 15000,
// 每次导航的超时时间
navigationTimeout: 30000,
},
});
单次设置(优先级高于全局配置)
实例
// 单个测试设置总超时
test.setTimeout(120000);
// 单个操作设置超时
await page.getByRole('button').click({ timeout: 30000 });
// 单次导航设置超时
await page.goto('https://www.runoob.com/', { timeout: 60000 });
test.setTimeout(120000);
// 单个操作设置超时
await page.getByRole('button').click({ timeout: 30000 });
// 单次导航设置超时
await page.goto('https://www.runoob.com/', { timeout: 60000 });
超时优先级
单次设置 > 配置文件全局设置 > 默认值。
合理设置超时的建议
| 场景 | 建议超时 | 说明 |
|---|---|---|
| 本地开发 | 默认值 | 本地服务响应快,默认值足够 |
| CI 环境 | 2~3 倍默认值 | CI 机器性能波动大 |
| 慢速页面 | actionTimeout: 15000 | 大表单、复杂页面 |
| 第三方服务调用 | test.setTimeout(120000) | 外部 API 响应慢 |
