Codegen 入门:实用指南

coding入门11 分钟阅读2026/7/3

我曾盯着我们 QA 团队用来追踪手动回归测试的那个有着 47 个标签页的 Excel 表格发呆。每次发布周期,总得有人花整整两天时间在应用上点点点,确认没有东西被改坏。当我问为什么不搞端到端自动化测试时,回答永远是那句:“我们没时间写啊。”

就在那时候,我开始研究 Playwright 的 codegen 工具。它的承诺很诱人——只要在你的应用上点来点去,就能直接生成一个真正能跑的测试文件。但我很快就发现,录制测试只是最简单的一步。让测试活过一个冲刺期,才是真正要命的地方。

以下就是我希望能有人在开始前就告诉我的那些坑。

让我走到这一步的问题

我们团队在一个面向用户的仪表盘上,E2E 测试覆盖率是零。每次部署都像是在经历一次小型心脏病发作。我以前用过 Cypress,但从零开始写测试,对于本来就捉襟见肘的团队来说,时间成本太高了。Playwright 的 codegen 似乎是个快速铺开覆盖率的好办法——把主流程录下来,提交到 CI 里,好歹弄张安全网。

好消息是:起步确实有这么快。坏消息是:你录下来的只是个起点,不是最终成品。

第一步:安装与基本检查

首先,确保你装好了 Playwright。如果你是从零开始:

npm init playwright@latest

跟着提示走(我选了 TypeScript,不过 codegen 也支持 JavaScript、Python、Java 和 C#)。搞定后,验证一下 codegen 能不能正常工作:

npx playwright codegen --help

你应该会看到一列选项。如果看到了,那就没问题了。

第二步:录制你的第一个测试

来录个简单的登录流程吧。运行:

npx playwright codegen https://example.com/login

会弹出两个窗口:一个是你可以操作的实体浏览器,另一个是 Playwright Inspector,它会盯着你的一举一动,并实时写出测试代码。

我点击了邮箱输入框,输入 user@test.com,点击密码输入框,输入 mypassword,然后点击“Sign In”。Inspector 立马生成了类似这样的代码:

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

test('test', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.locator('[data-testid="email-input"]').click();
  await page.locator('[data-testid="email-input"]').fill('user@test.com');
  await page.locator('[data-testid="password-input"]').click();
  await page.locator('[data-testid="password-input"]').fill('mypassword');
  await page.locator('[data-testid="submit-btn"]').click();
});

就这么简单。你可以把这段代码复制到文件里,运行 npx playwright test,跑通了。30 秒搞定一个能通过的测试。

我第一反应是:为什么大家不都这么干?

第三步:真正实用的命令行参数

在吐槽问题之前,先分享几个我常用、但基础教程往往会略过的 codegen 参数:

目标语言:

npx playwright codegen --target python https://example.com

默认是 JavaScript。我用 TypeScript 工作,所以我几乎总是用 --target playwright-test 来生成带正确类型的测试文件。

模拟移动设备:

npx playwright codegen --device="iPhone 14" https://example.com

这会设置视口、用户代理和触摸事件。如果你的应用有专门的移动端体验,这招很有用。

带登录状态录制:
这个我折腾了好一会儿才搞明白。如果你的应用需要登录,你肯定不想每次都把登录过程录进去。正确的做法是,先保存登录状态:

npx playwright codegen --save-storage=auth.json https://example.com/login

在录制过程中手动登录。会话状态(cookies、localStorage)就会保存到 auth.json 里。以后再录制时:

npx playwright codegen --load-storage=auth.json https://example.com/dashboard

这样你一上来就是登录状态了。对于录制登录墙后面的流程来说,这步至关重要,免得你的测试代码里塞满登录的样板代码。

配色方案和视口大小:

npx playwright codegen --color-scheme=dark --viewport-size="1280,720" https://example.com

当你的应用会根据主题或屏幕尺寸改变渲染效果时,这个很有用。

第四步:直面真相——测试的腐化

这是每个 codegen 教程都会跳过的部分。

某个周一我录了 5 个测试。到了周四,设计师把按钮从“Submit”改成了“Save”,开发重构了组件,把 data-testid="submit-btn" 改成了 data-testid="save-btn"。我的 5 个测试里挂了 2 个。

我重新录了一遍。花了 5 分钟。但下周,又一次改动又搞挂了两个。这时候我花在重录测试上的时间,已经比从头写稳定测试的时间还要长了。

这就是我说的“录制出来的测试会腐化”。Codegen 捕获的是你 UI 在某个瞬间的快照。它记录的是你点了哪里,而不是你想点什么。它没有语义理解。UI 一变,测试就挂,而 codegen 没法自动更新它——你要么重录,要么手动修定位器。

第五步:清理 Codegen 的输出

解决办法就是把 codegen 的输出当成初稿,而不是完成的测试。这是我总结出来的模式。

原始的 codegen 输出通常长这样:

await page.locator('internal:attr=[placeholder="Email address"]').click();
await page.locator('internal:attr=[placeholder="Email address"]').fill('user@test.com');
await page.locator('button:has-text("Sign In")').click();

这些定位器非常脆弱。占位符文本会变,按钮文案也会变。所以我会把它们清理成基于角色或 data-testid 的定位器:

await page.getByRole('textbox', { name: /email/i }).fill('user@test.com');
await page.getByRole('button', { name: /sign in/i }).click();

基于角色的定位器更有韧性,因为它们绑定的是无障碍语义,而不是视觉细节。按钮文案可能从“Sign In”变成“Log In”,但如果我用正则表达式 /sign in|log in/i,测试就能活下来。

此外,codegen 会先生成 .click() 再生成 .fill(),我会把它们合并成只有一个 .fill()——填充字段会自动聚焦,所以点击是多余的。

清理后的登录测试版本:

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

test('user can log in', async ({ page }) => {
  await page.goto('/login');
  await page.getByRole('textbox', { name: /email/i }).fill('user@test.com');
  await page.getByRole('textbox', { name: /password/i }).fill('mypassword');
  await page.getByRole('button', { name: /sign in/i }).click();
  await expect(page).toHaveURL('/dashboard');
});

注意我还在最后加了个断言。Codegen 只记录动作,但它不知道成功长什么样。这得你自己加。没有断言的测试,顶多算个只会瞎点的脚本。

第六步:何时使用 Codegen(何时不使用)

用了这套工作流几个月后,这是我的大实话:

适合用 codegen 的场景:

  • 你在从零开始搭建 E2E 覆盖率,需要赶紧拿点东西顶上
  • 你在探索一个新代码库,想了解页面结构——录制个流程,读读生成的定位器,能学到很多
  • 你需要复现一个 bug——录下精确步骤,存成文件,直接丢给开发
  • 你在为很少变动的稳定页面写一次性的冒烟测试

不适合用 codegen 的场景:

  • 你需要能活过好几个冲刺期、不用频繁维护的测试
  • 你的应用正在密集开发,UI 变动频繁
  • 你在为 CI/CD 搭建长期的测试套件——这种得手写,配合 Page Object 模式
  • 你觉得录制能代替理解——codegen 是个加速器,绝不能替代你写好选择器的能力

我踩坑学到的实操建议

  1. 一定要加断言。 Codegen 不会替你干这活。一个只会顺着流程点下去却不检查结果的测试,几乎毫无价值。

  2. 立刻清理定位器。 别把原始的 codegen 输出直接提交到代码库。花个五分钟把它转成基于角色或 data-testid 的选择器再提交。

  3. 带登录状态的流程用 --load-storage 每次都录登录会让测试又脆弱又慢。

  4. 别重录,要重构。 测试挂了的时候,克制住重录的冲动。打开文件,找到坏的定位器,改好它。重录会让你丢失之前手动优化的心血。

  5. 从一开始就用目标语言生成。 以后再把 JavaScript 的 codegen 输出转成 Python 简直痛苦面具。第一天就用 --target 吧。

诚实的局限性

Codegen 处理不好动态内容——如果你的页面异步加载数据,在数据出来前你就点了,录制出来的就会是不稳定的测试。你需要手动加显式等待,或者用 Playwright 自带自动等待的断言。

它也没法可靠地录制拖拽操作,像按住修饰键再点击这种复杂交互,生成的代码往往是错的。这些你得手动写代码。

最后,codegen 只会生成一个超长的测试函数。没有 Page Object,没有模块化,没有复用。如果你在 10 个测试里录了同样的登录流程,就会有 10 份重复的登录代码。把它们重构成共享的 fixture 或 Page Object,全靠你自己。

我的最终结论

我现在依然经常用 codegen,但我不再把它当成“创建测试”了。它是测试原型设计。我花 30 秒录个流程,清理定位器,加上断言,提取公共模式到 Page Object 里,然后提交。录制替我省去了找选择器和导航路径的繁琐工作;而手动清理保证了这个测试下个月还能正常跑。

那个 47 个标签页的表格早扔了。我们现在有 40 多个 E2E 测试,其中大部分一开始都是 codegen 录制的。只不过它们现在看起来已经不像录制品了——而这正是关键所在。

相关 Agent

C

光标编辑器

AI驱动的代码编辑器,支持智能补全和对话。

了解更多 →