现在位置: 首页 > Playwright 教程 > 正文

Playwright 最佳实践

本章汇总 Playwright 测试编写和维护过程中最重要的最佳实践,帮助你编写稳定、可维护的测试。


测试哲学

测试用户可见行为

自动化测试应该验证最终用户能看到和交互的内容,而不是验证代码实现细节。

比如,测试应该验证页面上显示了正确的文本、按钮可以点击、表单提交后跳转到正确页面,而不是验证某个 JavaScript 函数的返回值或 DOM 的内部结构。

实例

// 不推荐:测试实现细节
expect(await page.evaluate(() => window.__store.getState().user.name))
  .toBe('runoob');

// 推荐:测试用户看到的内容
await expect(page.getByText('欢迎,runoob')).toBeVisible();

测试隔离

每个测试应该完全独立于其他测试,不依赖于前一个测试的状态。

Playwright 默认提供独立 Context,但你还应该确保不依赖测试的执行顺序。

实例

// 不推荐:测试之间相互依赖
let createdId;
test('创建资源', async ({ request }) => {
  const resp = await request.post('/api/items');
  createdId = (await resp.json()).id;  // 共享状态
});
test('使用创建的资源', async ({ page }) => {
  await page.goto(`/items/${createdId}`);  // 依赖上一个测试的结果
});

// 推荐:每个测试自给自足
test('创建并使用资源', async ({ request, page }) => {
  const resp = await request.post('/api/items');
  const id = (await resp.json()).id;
  await page.goto(`/items/${id}`);
  await expect(page.getByText('资源详情')).toBeVisible();
});

Locator 优先级原则

按照推荐的顺序使用 Locator,可以提高测试的稳定性。

优先级方法适用场景
1getByRole()元素具有明确的 ARIA 角色
2getByLabel()表单元素关联 label
3getByPlaceholder()输入框有 placeholder
4getByText()元素有明确文本
5getByAltText()图片有 alt 属性
6getByTitle()元素有 title 属性
7getByTestId()兜底方案
8locator()CSS/XPath,最后选择

尽量不使用 CSS 类名作为定位器。

类名容易因样式重构而变更,导致测试大面积失效。


避免不必要的等待

利用 Playwright 的自动等待机制,避免手写 sleepwaitForTimeout

实例

// 不推荐:硬编码等待
await page.waitForTimeout(3000);
await page.getByText('数据加载完成').click();

// 推荐:依赖自动等待和断言
await expect(page.getByText('数据加载中...')).toBeHidden({ timeout: 10000 });
await page.getByText('数据加载完成').click();

不要测试第三方服务

只测试你能控制的内容,不要依赖第三方服务的可用性。

实例

// 不推荐:依赖外部 CDN 或 API
await page.goto('https://www.runoob.com/');
// 页面可能加载了 Google Analytics、外部字体等

// 推荐:拦截第三方请求,Mock 外部服务
await page.route('**/*analytics*', route => route.abort());
await page.route('**/external-api/**', route => {
  route.fulfill({ json: { status: 'ok' } });
});

使用 expect.soft() 软断言

在检查多个独立条件时,软断言可以一次性报告所有失败,而不是在第一个失败处停止。

实例

// 推荐:软断言一次性检测所有字段
await expect.soft(page.getByLabel('用户名')).toBeVisible();
await expect.soft(page.getByLabel('密码')).toBeVisible();
await expect.soft(page.getByLabel('邮箱')).toBeVisible();
await expect.soft(page.getByRole('button', { name: '注册' })).toBeEnabled();
// 如果多个字段缺失,一次性全部报告

合理组织测试文件

实践说明
按功能模块分文件login.spec.tscheckout.spec.ts
使用 describe 分组将相关测试放在 test.describe()
用好 beforeEach重复的导航和准备步骤提取到 beforeEach
测试名称描述性好的名称能说明「做了什么」和「期望什么」
提取公共代码超过 3 个文件使用的 helper 提取到共享模块
不要过度 DRY2-3 行重复比过度抽象更容易维护

常见问题排错

测试不稳定(flaky)

不稳定测试的原因和解决方案:

原因解决方案
硬编码 sleep 不够长使用自动等待机制替代固定等待
依赖第三方服务使用 route() Mock 外部请求
动画导致元素位置变化在配置中 animations: 'disabled'
测试间数据残留确保每个测试独立创建和清理数据
时间相关逻辑使用 page.clock 固定时间

Locator 找不到元素

排查步骤:

  • 确认元素是否在 DOM 中(用 toBeAttached() 而非 toBeVisible()
  • 确认元素是否在视口内
  • 确认是否被 iframe 嵌套
  • 在 UI Mode 中使用 Pick Locator 检查定位器
  • 检查是否有多个匹配元素(Locator 默认要求严格模式)