用 Playwright codegen 录制生成测试代码:哪些必须自己改
写第一个端到端测试时,最耗时的往往不是断言,而是「这个按钮到底该怎么定位」。你在页面上看到一个按钮,翻 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-playwright 再 playwright install。C# 那条命令里的 netX 是文档里的占位;顺带一提,docs/src/codegen-intro.md 里同一条命令写的是 net8.0 而 docs/src/codegen.md 里写的是 netX——两处写法不一致,实际路径以你项目 bin/Debug/ 下真实存在的目录为准。
另外一个容易踩空的前置条件:docs/src/codegen.md 里「Generate tests in VS Code」整节标了 * langs: js,也就是 VS Code 扩展里的 Record new、Record at cursor、Pick 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() 这个共用函数,open、screenshot、pdf 也共享同一批,包括 -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.ts 的 codegen() 函数里有一行 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 的 id | kCSSIdScore |
| 标签名 | kCSSTagNameScore |
nth 序号 | kNthScore |
| CSS 兜底链 | kCSSFallbackScore |
真正要看的是最后两行:kNthScore 和 kCSSFallbackScore 的数量级远远高过前面所有候选。所以当你在生成的代码里看到 .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-testid、data-test-id、data-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.ts的generateFooter()在 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 签名、配置项与默认值随版本变动,请以仓库最新内容为准。
本文不涉及浏览器版本矩阵与版本清单,相关信息请以官方发布说明为准。