首页 / Playwright 入门教程 / 最佳实践与常见反模式

Playwright 入门教程

最佳实践与常见反模式

本教程共 59 篇 · 第 55 篇 · 更新于 2026-08-04 · 约 9 分钟阅读

Playwright最佳实践反模式定位器Web优先断言测试隔离CI

55. 最佳实践与常见反模式

本节目标:学完你能避开 Playwright 测试里最典型的脆皮写法,写出抗改、好读、好维护的测试。

前面章节零散讲过一些原则,这章集中梳理:哪些是官方力推的 do,哪些是新手常踩的 don’t。照着做,测试不容易「昨天能跑今天挂」。

定位器:挑抗变的那个

用定位器定位元素优先用「用户看得见的属性」:getByRolegetByTextgetByLabel,再不济用 data-testid。别一上来就 CSS 类或 XPath——设计师一动样式,测试就崩。

// 👍 抗变
page.getByRole('button', { name: 'submit' });

// 👎 脆弱
page.locator('button.buttonIcon.episode-actions-later');
Tip

不确定用哪个?让 codegen 帮你挑。它优先 role、text、testid,还能在多个匹配里自动收窄。

用链式和过滤缩小范围

Locator 可以串起来,慢慢缩到目标。比一长串 CSS 选择器好读也好改:

await page
  .getByRole('listitem')
  .filter({ hasText: 'Product 2' })
  .getByRole('button', { name: 'Add to cart' })
  .click();

断言:坚持 Web 优先

Web 优先断言(如 toBeVisible())会等条件满足、自动重试。手动断言不等人,容易误报。

// 👍 自动等
await expect(page.getByText('welcome')).toBeVisible();

// 👎 不等,当下就查
expect(await page.getByText('welcome').isVisible()).toBe(true);
Warning

别把 await 写进 expect 里面。上面第二种写法,isVisible() 的结果是瞬间布尔值,断言不重试,元素晚半秒出现就挂。

测用户看得见的行为

测试该验证「用户最终看到/用到的」,别去测函数名、是不是数组、CSS 类名这类实现细节。用户只跟渲染出来的东西打交道,你的测试也该只看渲染结果。

测试之间要隔离

每个测试要独立:自己的存储、cookie、数据。隔离好了,可复现、好调试、不会一个挂了一串挂。

重复的登录步骤可以用 beforeEach 收口:

import { test } from '@playwright/test';

test.beforeEach(async ({ page }) => {
  await page.goto('https://github.com/login');
  await page.getByLabel('Username or email address').fill('username');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();
});

test('first', async ({ page }) => { /* 已登录 */ });
test('second', async ({ page }) => { /* 已登录 */ });
Note

登录这种重活,更优解是「共享登录态」:用 setup project 登一次,其余测试复用,省时间。

别测你控制不了的东西

第三方站点、外部接口别直接测。内容你控制不了,还可能有 cookie 弹窗把你测试卡死。要测外部依赖的返回,用网络 API 自己造:

await page.route('**/api/fetch_data_third_party_dependency', route => route.fulfill({
  status: 200,
  body: testData,
}));
await page.goto('https://example.com');

跨浏览器与 CI

在配置里一次配好多浏览器项目,覆盖 chromium、firefox、webkit,保证各用户都能用:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});

CI 上几条经验:

  • 用 Linux,便宜。本地随意。
  • 只装用得到的浏览器,比如只测 Chromium 就只装它。
  • Trace 设 'on-first-retry',失败才录,省性能。
  • 频繁跑,最好每次提交和 PR 都跑。

保持依赖新鲜

Playwright 月度发版。常更新,能趁新浏览器发布前抓出问题:

npm install -D @playwright/test@latest

查当前版本:npx playwright --version。配合 ESLint 的 no-floating-promises 规则,能逮出漏写的 await

其他顺手的小建议

  • 并行是默认的。同文件内测试顺序跑;想同文件并行,加 test.describe.configure({ mode: 'parallel' })
  • 软断言不立即中断,能一次列出多处不符:await expect.soft(...).toHaveText('Success')
  • 用工具:VS Code 插件、codegen、Trace Viewer、UI Mode,都是官方给的省力工具,别硬扛。

一句话:抗变的定位器 + Web-First 断言 + 测试隔离,是 Playwright 测试稳的根。能走正门就别翻窗。

小结

这章把前面零散的原则拢到一起:定位器优先 getByRolegetByText,别一上来就 CSS 类;断言坚持 Web-First,自动重试不误报;测试之间要隔离,别让一个挂了一串挂。第三方站点别直接测,用 page.route 自己造返回。CI 上 Trace 设 on-first-retry,只录失败的,省性能。这三条线守住了——抗变定位器、Web-First 断言、测试隔离——测试就不容易「昨天能跑今天挂」。