改完代码怕线上崩:用 Playwright 给前端跑端到端测试

举报
海是岛思念的泪 发表于 2026/09/23 15:21:58 2026/09/23
【摘要】 每次发版前手点一遍主流程太慢,不点又怕改崩了。这篇用 Playwright 给前端加上端到端测试:从安装到写第一条用例,用 getByRole 这类语义化定位替代脆弱的 xpath,登录态怎么复用,最后把它挂进流水线里每次提交自动跑一遍。

有次我改了个按钮的 class 名,自测点了一下没问题就发了。结果线上登录流程直接挂——因为有个地方用类名选中了那个按钮。单元测试覆盖不到这种"页面到底还能不能跑通"的事。后来给项目加了 Playwright 端到端测试,提交代码自动跑一遍主流程,这类低级事故再没出过。这篇讲怎么从零搭起来。

image.png

一、先装起来

一条命令搞定初始化:

npm init playwright@latest

它会问你几个问题(TypeScript 还是 JS、测试目录放哪、要不要加 GitHub Actions 工作流),选完自动生成:

playwright.config.ts     # 配置:浏览器、baseURL、报告
tests/                   # 你的用例
tests-examples/          # 官方示例,看完可以删
package.json

跑一下自带的示例,它会自动下载浏览器:

npx playwright test

二、写第一条用例

语法很直白,就是"打开页面 → 操作 → 断言结果":

import { test, expect } from '@playwright/test'

test('用账号密码登录并跳到首页', async ({ page }) => {
  await page.goto('/login')

  await page.getByLabel('账号').fill('testuser')
  await page.getByLabel('密码').fill('123456')
  await page.getByRole('button', { name: '登录' }).click()

  await expect(page).toHaveURL(/dashboard/)
  await expect(page.getByText('欢迎回来')).toBeVisible()
})

注意 goto('/login') 用的是相对路径,域名在配置里统一给:

// playwright.config.ts
export default defineConfig({
  use: {
    baseURL: 'http://localhost:3000',
  },
})

这样测试环境、预发环境换个 baseURL 就能跑,不用改用例。

三、定位元素别再用 xpath

这是最能决定"这套测试能活多久"的地方。同样选一个按钮,写法差别巨大:

写法 说明 稳定度
locator('//div[3]/button') xpath 按结构数层数 极差,DOM 一变就废
locator('#login-btn') 靠 id / class 差,改个样式就崩
getByRole('button', { name: '登录' }) 按可访问性角色+名字 好,跟着用户看到的走
getByLabel('账号') 按 label 关联的输入框 好
getByTestId('login-submit') 按约定的测试标记 好,但要团队先约定

优先用 getByRole。它选的是"用户眼里的那个按钮",你把 div 换成 button、把 class 改个名,测试照样过。顺带一个好处:逼着你把语义化的可访问性属性补上,对无障碍也友好。

还有个特别省心的设计:Playwright 自带自动等待。expect(...).toBeVisible() 会一直重试直到超时(默认 5 秒),异步渲染的页面也不用你操心。所以千万别写 waitForTimeout(3000)——那只会让测试又慢又脆。

四、登录态复用:别每条用例都登一遍

大部分页面都要登录,每条用例都跑一遍登录流程,几十条用例能多花好几分钟。正确做法是先跑一个"登录专用"的用例,把登录状态存成文件,其余用例直接复用。

// tests/auth.setup.ts
import { test as setup } from '@playwright/test'

setup('先登录一次', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('账号').fill('testuser')
  await page.getByLabel('密码').fill('123456')
  await page.getByRole('button', { name: '登录' }).click()

  await page.waitForURL(/dashboard/)
  await page.context().storageState({ path: 'auth.json' }) // 把 cookie/localStorage 存下来
})

配置里声明依赖关系:

export default defineConfig({
  testDir: './tests',
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'], storageState: 'auth.json' },
      dependencies: ['setup'],   // 跑我之前先跑 setup
    },
  ],
})

auth.json 记得加进 .gitignore,里面是真实的登录凭据。

五、挂进流水线自动跑

本地能跑之后,真正的价值在"每次提交自动跑"。一份能用的配置长这样:

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,          // 防止有人把 .only 提交上来
  retries: process.env.CI ? 2 : 0,       // CI 上允许重试,抗抖动
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'retain-on-failure',          // 只在失败时录 trace
    screenshot: 'only-on-failure',
  },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
  webServer: {                            // 自动起前端服务
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
})

GitHub Actions 里加几个步骤:

- uses: actions/setup-node@v4
  with:
    node-version: 20
- run: npm ci
- run: npx playwright install --with-deps chromium   # Linux 上需要 sudo
- run: npx playwright test
- uses: actions/upload-artifact@v4
  if: ${{ !cancelled() }}
  with:
    name: playwright-report
    path: playwright-report/
    retention-days: 7

用华为云 CodeArts Pipeline 也一个道理,在流水线里加一条执行 shell 的步骤跑 npx playwright test,再把 playwright-report 目录归档成构建产物就行。

用例挂了要排查,看报告:

npx playwright show-report

失败的那条能直接打开 Trace Viewer——每一步操作的截图、DOM 快照、网络请求、控制台日志全在里面,基本不用在本地复现就能定位。

几个容易卡的地方

  • 别用 waitForTimeout。要等某个元素出现就用 expect(locator).toBeVisible(),要等接口返回就用 page.waitForResponse()。写死 sleep 是端到端测试变脆的头号原因。
  • 测试数据必须独立。每条用例自己造数据、自己清理。并行跑的时候如果两条用例共用一个账号改同一份数据,会随机失败,而且极难复现。
  • CI 上建议 workers: 1。本地多 worker 并行很快,但 CI 机器配置一般,并行容易超时抖动。稳比快重要。
  • trace 别开 'on'。全量录制体积大得吓人,retain-on-failure 或者 on-first-retry 性价比最高。
  • 端到端测试别贪多。按测试金字塔,它应该是最上面最少的那一层。守住登录、下单、支付这种主流程,写十几条就够;全靠它兜底,跑一次半小时,团队很快就会把它屏蔽掉——那等于没写。
  • 别对着生产环境跑。造出来的订单、发出去的通知都是真数据。单独准备一套测试环境,每次跑完重置。
  • --with-deps 在 Linux 上要 root。装系统依赖需要权限,容器里一般没问题,自己的虚拟机记得加 sudo。

总结

端到端测试解决的是一个很朴素的问题:代码改动之后,用户真正会走的那几条路还能不能通。Playwright 上手门槛不高——npm init playwright 一条命令,写用例几乎就是"点哪里、填什么、期待看到什么",选对 getByRole 这类定位方式之后也不容易脆。真正要花心思的是把它放进流水线里持续跑,以及控制住用例数量。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。