用 Playwright codegen 录制生成测试代码:哪些必须自己改

2026-08-18

写第一个端到端测试时,最耗时的往往不是断言,而是「这个按钮到底该怎么定位」。你在页面上看到一个按钮,翻 DOM 找到一堆没有语义的 div,试了几个选择器,换来的是一个同时匹配到两个元素的结果。codegen 就是把这一段体力活交给工具:你在浏览器里点,它在旁边把代码写出来。

但它写出来的代码不是拿来就用的。哪些能直接留、哪些必须自己动手,取决于它内部那套定位器打分规则。这篇把命令、选项和那套规则一起讲清楚。

前置条件:先确认你用的是哪个语言绑定

codegen 是 Playwright CLI 的子命令,四个语言绑定的调用入口不一样。仓库 docs/src/codegen.md 里同一段功能给了四份命令,原样抄下来是这样:

# JavaScript / TypeScript
npx playwright codegen demo.playwright.dev/todomvc
# Python
playwright codegen demo.playwright.dev/todomvc
# Java
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen demo.playwright.dev/todomvc"
# C#(Windows 上同样是这条,注意是 pwsh 而不是 powershell)
pwsh bin/Debug/netX/playwright.ps1 codegen demo.playwright.dev/todomvc

Python 侧要先装好插件与浏览器,docs/src/intro-python.md 里写的是 pip install pytest-playwrightplaywright install。C# 那条命令里的 netX 是文档里的占位;顺带一提,docs/src/codegen-intro.md 里同一条命令写的是 net8.0docs/src/codegen.md 里写的是 netX——两处写法不一致,实际路径以你项目 bin/Debug/ 下真实存在的目录为准。

另外一个容易踩空的前置条件:docs/src/codegen.md 里「Generate tests in VS Code」整节标了 * langs: js,也就是 VS Code 扩展里的 Record newRecord at cursorPick locator 这套按钮式录制只对 JS 绑定给出了说明,其它绑定的文档里我们没有找到对应内容。用 Python 或 C# 的话,走的是命令行加 Playwright Inspector 那条路。

录制的几个常用选项,各自在改什么

codegen 的专属选项在 packages/playwright-core/src/cli/program.ts 里注册,只有三个:

  • -o, --output <file name>,把生成的脚本存成文件;
  • --target <language>,源码里逐字写明取值是 javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, csharp-xunit, java, java-junit,默认值由 codegenId() 给出,即 process.env.PW_LANG_NAME || 'playwright-test'(这是仓库当前代码里的默认值,随版本可能变动);
  • --test-id-attribute <attributeName>,指定用哪个属性生成 test id 选择器。

其余选项来自 commandWithOpenOptions() 这个共用函数,openscreenshotpdf 也共享同一批,包括 -b, --browser--device--viewport-size--color-scheme--lang--timezone--geolocation--proxy-server--save-har--save-storage--load-storage--user-data-dir--timeout 等。文档里的模拟类示例可以直接抄,比如:

npx playwright codegen --device="iPhone 13" playwright.dev
npx playwright codegen --viewport-size="800,600" playwright.dev

这里有个小坑值得单独说:program.ts--viewport-size 的选项描述举的例子是 "1280, 720"(逗号后带空格),而 browserActions.ts 解析失败时抛出的错误文案举的例子是 "800,600"(不带空格)。两处写法不一致,照文档里不带空格那种写最省事。Windows 的 PowerShell 里引号规则和 bash 不同,--viewport-size="800,600" 这种带引号的值建议整体加引号写成 "--viewport-size=800,600",避免被拆开。

--timeout 的描述里写明「timeout for Playwright actions in milliseconds, no timeout by default」——录制时默认不给动作设超时。

录登录态:--save-storage--load-storage

这是 codegen 最实用的一组选项。思路是把登录这一步单独录一次,把状态存下来,之后录别的用例时直接带着登录态开场。

npx playwright codegen github.com/microsoft/playwright --save-storage=auth.json
npx playwright codegen --load-storage=auth.json github.com/microsoft/playwright

代码里这两个选项落在 packages/playwright-core/src/cli/browserActions.ts--load-storage 直接被塞进 contextOptions.storageState--save-storage 则是在 closeBrowser() 里调 context.storageState({ path: options.saveStorage }),也就是会话结束时才写盘。文档写明它保存的是 cookies、localStorage 和 IndexedDB 数据。

文档同时给了一句必须照做的提醒:auth.json 含敏感信息,只在本地用,加进 .gitignore 或者用完就删。docs/src/auth.md 推荐的做法是建一个 playwright/.auth 目录并整个忽略掉。

还有第三条路 --user-data-dir,让浏览器用一个固定的用户数据目录,profile 里已有的登录态就能直接用上。但文档里紧跟着一个 warning 块:从 Chrome 136 起,默认的用户数据目录不能被自动化工具访问,必须另建一个专用目录。

一个藏在源码里、文档没提的细节:browserActions.tscodegen() 函数里有一行 dotenv.config({ path: 'playwright.env', quiet: true }),也就是 codegen 启动时会读当前目录下的 playwright.env;同一文件里的 open() 函数没有这一行。

以上为按仓库文档中的参数语义组合的示例,未经实测,以仓库最新内容与 --help 的实际输出为准。

生成的定位器为什么要手改

codegen 挑定位器的逻辑在 packages/injected/src/selectorGenerator.ts 里,入口是 generateSelector()。它给每个候选定位器打分,源码注释写得很直白:score: number; // Lower is better.。分数常量从上到下大致是这个次序(以下常量名与先后次序取自仓库当前代码,随版本可能变动):

候选类型常量
test id 属性kTestIdScore
role + 无障碍名kRoleWithNameScore
placeholder / label / alt / 文本 / title依次递增
CSS 的 idkCSSIdScore
标签名kCSSTagNameScore
nth 序号kNthScore
CSS 兜底链kCSSFallbackScore

真正要看的是最后两行:kNthScorekCSSFallbackScore 的数量级远远高过前面所有候选。所以当你在生成的代码里看到 .nth(2),或者一长串 div > div > span 这种 CSS 链,那不是它推荐这么写,而是它前面所有语义化候选都没找到东西可用,退到了兜底。 这两种一定要手改:要么去页面上补 aria-label、补 data-testid,要么自己换成 getByRole 加名字。

另外几处会让你「明明有 id 却没用上」的行为,也都在这个文件里:

  • buildNoTextCandidates() 里,拿到元素的 id 后先过一遍 isGuidLike(),判定是随机串就不作为候选。函数体是数字母大小写与数字之间的切换次数,切换太频繁就算 GUID 式。换句话说,形如随机串的 id 不会进入候选,能不能被拦下取决于这个字符类型切换的计数,而不是 id 的来源。仓库里没有写这条规则背后的取舍理由,这里只转述它的判定方式。
  • penalizeScoreForLength() 会给中间档位的候选按选择器长度加罚分。文本很长的按钮,用文本定位的分数会被压下去。
  • generateSelector() 里有一段「retarget」逻辑:如果你点的元素不是 input,textarea,select 也不是 contenteditable,它会往上找最近的 button,select,input,[role=button],[role=checkbox],[role=radio],a,[role=link] 且可见的祖先,改成定位那个。所以你点了按钮里的一段文字,生成出来的往往是整个按钮的定位器——这不是它认错了元素。

至于 --test-id-attribute,源码里 test id 是分数最低(也就是最优先)的一档,data-testiddata-test-iddata-test 这三个即使不是你配置的那一个,也会作为次一档候选参与打分。要让录制稳定,给关键元素加测试属性是性价比最高的一步。

边界:这些地方别指望它

  • 断言只有三种。文档明确列出可录的断言是 'assert visibility''assert text''assert value',分别断言可见、包含文本、具有某个值。别的断言得自己补。
  • --target 不同,生成的骨架不同packages/isomorphic/codegen/javascript.ts 里,test 目标走 generateTestHeader(),把上下文选项渲染成 test.use(...);standalone 目标走 generateStandaloneHeader(),生成 launch / newContext 那套。footer 也不一样:standalone 的 footer 在给了 --save-storage 时会多生成一行 context.storageState({ path: ... }),而 generateTestFooter() 只返回 });。Python 侧 packages/isomorphic/codegen/python.tsgenerateFooter() 在 pytest 目标下直接返回空串。同一条命令换个 --target,你拿到的代码结构是两回事。
  • session storage 不在保存范围内docs/src/auth.md 写明 Playwright 没有提供持久化 session storage 的 API,只给了一段自己 page.evaluate 存取的代码片段。如果你的应用把登录态放在 session storage,--save-storage 救不了你。
  • 非标准场景要换路子。文档写明如果你要在录制时用 BrowserContext.route 这类自定义设置,做法是自己写脚本调 Page.pause(),它会另开一个带录制控件的窗口,而不是走 codegen 命令。
  • 录制会开真实浏览器。它启动的是真实浏览器、加载真实站点、把真实的 cookies 和 localStorage 写进 auth.json。在录制真实业务系统的登录时,这个文件等价于一份可用的会话凭据,处理方式要照敏感文件来。安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

怎么确认自己配对了

  • 加上 -o 把结果落成文件,比如 npx playwright codegen -o tests/login.spec.ts <你的站点地址>,然后按你项目原本的方式跑这个测试文件,看它是不是能独立跑通。
  • 打开生成的文件,全文搜 .nth( 和长 CSS 串。搜到几处,就是有几处需要你回去补语义或补测试属性。
  • 录登录态时,结束录制后先确认 auth.json 真的生成了。按源码,这一步落在 closeBrowser() 里,进程没有走完关闭流程时这行代码就不会被执行到。
  • --load-storage 再起一次录制,页面开场就是已登录状态,才说明这一步接上了;否则多半是登录态落在了 session storage 里,或者站点的会话在服务端另有校验。
  • 想验证某个定位器改得对不对,用 Pick Locator:文档写明先按 'Record' 停止录制,'Pick Locator' 按钮才会出现,选中元素后可以在旁边的输入框里直接编辑定位器,页面上会高亮匹配到的元素。

本文依据 github.com/microsoft/playwright 仓库于 2026-08-18 的公开内容整理, 事实来自仓库内的文档与源码。我们没有对文中涉及的功能做过实测, 因此不涉及运行速度、稳定性与实际表现的任何描述。 该项目迭代频繁,文中涉及的 API 签名、配置项与默认值随版本变动,请以仓库最新内容为准。 本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。