我正盯着一个空荡荡的测试文件,对接下来的工作感到头疼。我们团队刚上线了一个包含七个步骤的新用户引导流程,里面有一堆表单验证,而且只有全部走完才能看到控制台。手动测一遍得花 45 分钟,而我们恨不得昨天就把端到端自动化测试写好。问题来了?要从头给这些流程写 Playwright 测试,感觉得花上一个星期。
就在这时,我决定好好试试 Playwright 的 Codegen。之前我也简单折腾过,但从来没真正把它完整融入工作流。下面是我在用它给整个引导流程生成测试时的经验总结——包括它帮我省了哪些力气,又在哪里白瞎了我的时间。
Codegen 到底是个啥
Playwright 的 Codegen 是一个测试录制工具,它会看着你在浏览器里操作,然后实时写出 Playwright 测试代码。它会打开两个窗口:一个是你执行操作的浏览器,另一个是实时显示生成代码的 Playwright Inspector 面板。
它不是那种无代码工具。你依然得懂 Playwright,而且得自己清理生成的代码。不过,如果你想快速搭出测试的骨架——尤其是那种复杂的多步骤流程——它确实贼好用。
上手入门
基础命令非常简单:
npx playwright codegen https://myapp.com/onboarding
这会打开一个浏览器窗口指向你的 URL,旁边并排打开 Inspector 面板。如果你不指定 URL,它会打开一个空白浏览器,让你自己手动导航。
在我的场景下,我想连上我们的预发布环境,并生成 TypeScript 代码:
npx playwright codegen --target=typescript https://staging.myapp.com/onboarding
你也可以指定浏览器:
npx playwright codegen --browser=webkit https://staging.myapp.com/onboarding
--target 参数支持 javascript、playwright-test、python、python-async、csharp 和 java。我一直用 playwright-test,因为那是我们的测试运行器。
录制引导流程
打开 Codegen 后,我把整个引导流程走了一遍。我填了用户名,选了套餐,输入了支付信息,确认了邮箱,最后进入了控制台。
在我点击和输入的同时,Codegen 实时生成了代码。这是前几步生成的代码:
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('https://staging.myapp.com/onboarding');
await page.getByLabel('Full Name').fill('Alex Rivera');
await page.getByLabel('Email').fill('alex@test.com');
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByLabel('Company Size').selectOption('11-50');
await page.getByRole('button', { name: 'Next Step' }).click();
// ... 后面七个步骤以此类推
});
这已经省了大把时间了。要是手动敲这些定位器和交互代码,估计得花好几个小时。不过,原始输出还不能直接上生产环境——这个下面再说。
智能定位器生成
这点真的让我惊艳。Codegen 不是无脑抓取 CSS 选择器或 XPath。它会分析页面,并按照以下优先级挑选最稳健的定位器:
- 角色定位器 (
getByRole) — 对无障碍访问和稳健性最好 - 文本定位器 (
getByText) — 适合按钮和链接 - 测试 ID 定位器 (
getByTestId) — 当你能控制代码时非常好用 - 标签定位器 (
getByLabel) — 表单字段的绝配 - CSS/XPath — 迫不得已才用
当我点击“Continue”按钮时,Codegen 生成了 getByRole('button', { name: 'Continue' }),而不是像 page.locator('#btn-step-1-continue') 这种脆弱的写法。这非常关键,因为基于角色的定位器在面对页面重构时,比 CSS 选择器抗造得多。
当多个元素匹配同一个定位器时——比如我们有次页面上同时出现了两个“Continue”按钮——Codegen 会优化定位器,精准锁定目标。它会加上额外的上下文,比如所属的区块标题或父容器。
添加断言
这个功能我一开始给忽略了。Codegen 不仅能录制操作——还能录制断言。
在 Inspector 的工具栏里有个断言图标。点一下,再去点页面上的元素。你会看到三个选项:
- 断言可见性 — 验证元素是否可见
- 断言文本 — 验证元素是否包含特定文本
- 断言值 — 验证表单元素是否具有特定值
我用这个功能来断言引导流程结束后控制台是否正确加载:
await expect(page.getByRole('heading', { name: 'Welcome to Your Dashboard' })).toBeVisible();
await expect(page.getByText('Setup Complete')).toBeVisible();
这些断言一针见血。我完全不用事后回头再手动去加。
不录制也能挑选定位器
这是我中途发现的一个小技巧:你可以只用 Codegen 来挑选定位器,而不用录制完整的测试。
- 点击 Record 按钮暂停录制
- 此时会出现 Pick Locator 按钮
- 点击它,然后把鼠标悬停在元素上,就能看到推荐的定位器了
当我在调试已有的测试,需要给某个元素找合适的定位器时,这招简直太好用了。我再也不用去猜选择器了,直接打开 Codegen,挑好定位器,复制粘贴到我的测试里就行。
清理代码
说点实在的——Codegen 生成的代码在变成生产代码之前,必须得清理。以下是我要修改的地方:
1. 所有东西都塞进了一个巨大的测试里。 Codegen 录制的是一个连续的流程。我把它拆分成了逻辑独立的测试用例:“能完成步骤 1”、“能选择套餐”、“能进入控制台”。
2. 测试数据是硬编码的。 录制时我输入的每个值在代码里都变成了字面量。我把它们替换成了变量和 fixtures:
// 替换前(Codegen 原始输出)
await page.getByLabel('Full Name').fill('Alex Rivera');
// 替换后(使用测试数据)
await page.getByLabel('Full Name').fill(testUser.fullName);
3. 没有合理的等待策略。 页面加载慢的时候,Codegen 有时会加上 waitForTimeout 调用。这非常脆弱。我把它们换成了更靠谱的 waitForSelector 或 waitForURL。
4. 多余的交互。 我发现 Codegen 录制了一些我并非有意为之的悬停操作——只是鼠标移动时刚好划过了某些元素。我把这些都删了。
5. 测试名永远叫 "test"。 这毫无参考价值。记得重命名成有描述性的名字。
为现有测试生成定位器
有个工作流比我预想的还好用:把 Codegen 和手写测试结合起来。我会自己写测试结构——describe 块、beforeEach 钩子、测试组织——但只用 Codegen 来生成定位器。
打开它,挑个定位器,粘贴进去。这大概帮我省了 70% 找定位器的时间。再也不用在 DevTools 里审查元素、复制 CSS 选择器,然后祈祷它别挂了。
实用小贴士
- 在目标环境中录制。 对着 localhost 录制,然后对着预发布环境跑,往往会产生不同的定位器和流程。
- 慢点录,稳点录。 不小心的点击和悬停会在输出中产生垃圾代码。别急,慢慢来。
- 多用 Clear 按钮。 如果录制搞砸了,直接在 Inspector 里点 Clear 重来。这比去改一堆烂代码快多了。
- 先暂停录制,再去挑定位器。 Pick Locator 模式只在录制暂停时才会出现。
- 立刻复制代码。 Inspector 不会保存状态。一旦关闭,代码就没了。每次录制成功后,赶紧复制到你的编辑器里。
- 在你的应用里加上
data-testid属性。 只要有这个属性,Codegen 就会优先使用它,而且从长远来看,这是最稳定的定位器策略。
实话实说
Codegen 并不能替代你对 Playwright 的理解。生成的测试只是个起点,不是最终成品。如果你把输出直接当生产代码用,不清理的话,最终只会得到一堆在 CI 里疯狂报错的脆弱测试。
话虽如此,就我写引导流程这活儿来说,Codegen 把原本可能要花一星期从头写测试的时间,缩短到了大约一天半——录制花了一小时,剩下的时间都在清理和重构。光是生成定位器这一项就省了巨多时间,而断言录制算是个意外之喜,现在也成了我的常规操作。
Codegen 的短板在于:包含大量异步内容的高动态页面、基于 Canvas 的交互,以及任何需要复杂前置状态的场景。遇到这些情况,你还是得老老实实手写测试。
它的最佳使用场景是多步骤表单流程、导航序列,以及任何你需要快速捕捉主流程的 UI。录下来,清理一下,把数据参数化,你就能在极短的时间内得到一个靠谱的测试。