
1. Trace录制能解决什么问题做自动化测试的朋友应该都有过这种经历用例跑挂了日志里只留下一句“element not found”或者一串超时异常但具体是页面没加载出来、弹窗挡住了、还是接口返回了脏数据完全靠猜。特别是接到一个不太熟悉的项目历史用例不是自己写的某一天突然开始批量红那种抓瞎的感觉真的很折磨人。Playwright的Trace录制功能简单说就是给每次测试执行装了一台“行车记录仪”。它会把页面每一个时刻的DOM快照、网络请求与响应体、控制台日志、JS异常、鼠标键盘操作、浏览器视口变化全部记录下来生成一个后缀为.zip的trace文件。用例结束之后用Trace Viewer打开这个文件就能像“回放录像”一样逐步查看每一个操作发生前后页面的真实状态连接口返回体的完整内容都能展开翻。这个功能的定位很明确它不是用来替代断言的而是用来解释“为什么断言失败”的。在本地调试、CI环境排查、跨团队交接用例时一个trace文件能省下大量反复复现和打日志的时间。适用人群也覆盖了整个测试链路——写用例的人用它自查脚本跑CI的人用它定位环境问题团队负责人可以用它卡测试质量甚至开发人员也能用trace去反查前端页面在自动化操作下暴露出来的隐藏异常。接下来我会把Trace的接入方式、配置参数、以及实际使用中的细节全部展开全程基于我自己在项目里踩过坑的真实经验。2. Trace的三种接入方式与选型逻辑2.1 “全局默认录制”与配置文件开关Playwright接入Trace最标准的方式是修改playwright.config.ts配置文件在use区块里加上trace选项。这个字段的取值逻辑比较直观下面是几个最常用的值import { defineConfig } from playwright/test; export default defineConfig({ use: { trace: on-first-retry, // 默认值第一次重试才记录trace // trace: retain-on-failure, // 失败才保留trace文件 // trace: on, // 所有用例都录制 // trace: off, // 完全关闭 }, });这四个模式的实际表现差异很大选型时需要结合用例规模和维护成本来权衡off所有用例都不产生trace文件执行速度最快磁盘占用最低但调试时没有任何依据。on每条用例无论成败都会录制。效果最完整但代价也很明显——原本1秒跑完的用例可能变成3到4秒且每个用例都会产生一个trace文件长时间跑下来磁盘会被撑满。只适合用例总量少、且你暂时不想精细化管理的阶段。retain-on-failure平时跑用例不记录任何trace只有用例失败时Playwright会自动为失败的用例补录一份完整trace。这个模式结合了性能和可调查性是绝大多数项目进入稳定期之后的最佳选择。on-first-retry如果用例配置了重试retries参数则在第一次重试时自动开启trace记录。这个模式的好处在于既拿到了失败现场证据又不影响第一遍正常执行的速度。从实际项目经验来看我建议团队从on或retain-on-failure起步等跑了一段时间用例稳定之后切到on-first-retry配合CI中的重试策略使用。这个路径比较平滑不至于一上来就遇到“为什么trace文件是空的”这种问题就手忙脚乱。2.2 在Context级别精细化控制很多初次接触Playwright的人容易忽略一个事实use里的trace配置是作用于浏览器上下文BrowserContext的。Playwright测试用例在执行时默认每个测试都会新建一个独立的上下文因此这个配置能无缝覆盖所有用例。但如果你脱离Test Runner用原生API写脚本或者需要在一个文件里串联多个业务场景且只想给其中某段关键步骤录制trace就必须使用Context级别的API手动控制。import { chromium } from playwright; const browser await chromium.launch(); const context await browser.newContext({ trace: on, // 在创建上下文时开启trace }); const page await context.newPage(); await page.goto(https://example.com); // 业务操作... await context.close(); // 上下文关闭后trace文件才会写入 await browser.close();这里有个容易踩的细节context.close()调用之后trace文件才会真正落盘。如果你在脚本中途打印了trace.zip的目录发现文件不存在不要慌先确认上下文有没有正常关闭。对于不是使用Playwright Test、而是用Mocha或Jest来组织测试的场景在beforeEach中为每个测试创建带trace的context在afterEach中统一关闭是更符合工程化的做法。2.3 手动start/stop分段录制Trace录制也支持在测试过程中动态开启和关闭典型场景就是一个长流程用例里只有某个模块容易出问题你想精准地只录制那一段避免整条用例的trace文件过于庞大。import { test, expect } from playwright/test; test(订单提交流程-只录制支付环节, async ({ page }) { await page.goto(https://example.com/login); // 登录不做trace await page.fill(#username, testuser); await page.fill(#password, password123); await page.click(button[typesubmit]); // 从商品列表进入详情 await page.click(.product-item); // 开始录制关键环节 await page.context()?.tracing?.start({ name: payment-step, screenshots: true, snapshots: true, sources: true, }); await page.click(#checkout-btn); await page.fill(#cardNumber, 1234567890123456); await page.click(#confirm-payment); await expect(page.locator(.success-tip)).toBeVisible(); // 结束录制 await page.context()?.tracing?.stop({ path: ./traces/payment-step.zip, }); });需要特别注意的是tracing.start()必须在同一个BrowserContext上进行不能跨上下文调用。每次start之后必须严格配对一次stop否则stop时可能只会写入最后一次开启阶段的trace前面的内容会丢失。我实际测试过多次这个行为不会有异常提示但产生的trace文件就是不符合预期体验比较隐蔽需要留意。3. 配置参数与Trace Viewer的使用3.1 trace字段的完整参数说明trace字段除了支持字符串形式的模式开关还能接收一个对象来精确控制录制内容。这个功能在需要控制trace体积时特别关键。import { defineConfig } from playwright/test; export default defineConfig({ use: { trace: { mode: retain-on-failure, screenshots: true, // 录制屏幕截图建议开启 snapshots: true, // 录制DOM快照核心功能保持开启 sources: true, // 记录测试源代码个人建议开启 }, }, });参数含义拆解screenshots录制过程中每个操作后保存浏览器视口的像素级截图。开启后视图预览可以呈现“电影式”操作回放排查界面样式类问题非常直观。代价是trace文件体积明显变大。snapshots录制每个操作前页面的DOM结构快照。这是Trace Viewer里最有价值的部分——可以点击任意操作步骤查看那一瞬间页面的完整HTML结构从而精确判断某个元素在操作时处于什么状态。如果只保留一个参数我会选snapshots。sources在trace中附加测试脚本的源代码上下文。启用后Trace Viewer点击某个步骤时能够显示对应的源码行。这个参数对团队内部排查非常有帮助基本没有负面影响建议保留。还有一种写法是在代码中创建上下文时传参const context await browser.newContext({ trace: { mode: on, screenshots: true, snapshots: true, sources: true, }, });这个写法在自由脚本模式和自定义Runner里用得更多配置文件的写法则主要服务于Playwright Test。3.2 打开Trace Viewer的几种方式录制完成之后最重要的一步就是高效查看trace。Playwright提供了命令行工具和浏览器界面两种方式。在终端直接运行npx playwright show-trace path/to/trace.zip执行后会自动打开默认浏览器加载Trace Viewer界面。如果你的trace文件还没生成也可以先不带参数运行然后通过界面左侧的按钮手动导入trace.zip文件。还有一种非常方便的用法是针对上次失败用例自动打开npx playwright show-report这个命令打开的是测试报告页面报告里失败用例的位置会直接嵌入Trace Viewer的入口点击即可查看不需要再手动寻找zip文件路径。如果你在CI中配置了PLAYWRIGHT_HTML_OPENalways环境变量失败用例的trace查看链路会更顺畅。3.3 Trace Viewer界面核心区域解析Trace Viewer打开后整个界面分成三个核心区域左侧是操作步骤时间轴中间是页面快照预览区右侧是网络请求列表。操作步骤时间轴Action list按执行顺序展示所有动作包括跳转、点击、填写、断言、等待等。点击任意步骤中间区就会切换到那个瞬间的页面状态。每步左侧还会显示一个“before/after”切换按钮可在操作前后两种状态中快速对比。有些步骤下会出现一个时间戳箭头点击后能查看该操作期间浏览器发出的所有网络请求。页面快照区展示页面的截图和DOM结构树。这里不仅可以看还能直接在HTML结构里搜索关键词。我在排查“按钮是否被遮罩覆盖”这类问题时就是直接在快照里搜索按钮的debug id查看其祖先元素的z-index和position属性几秒钟就能定位原因。网络请求面板以瀑布流形式展示每一步操作触发的全部请求。点击任意请求右侧分栏会显示请求头、请求体、响应头、响应体响应体支持格式化JSON展示以及源码格式查看。这个面板我基本每次都开——很多失败场景根本不用看代码瞄一眼接口状态码和响应内容就知道是数据问题还是断言写错了。还有两个重要的附加功能Console日志和Errors面板。Trace会自动记录所有浏览器控制台输出包括未被页面捕获的异常。想排查“点击后页面无任何反应”的问题可以直接在Console面板里找到对应的报错堆栈点击之后还能定位到具体是哪个DOM节点上报的错。4. 在CI环境中对接Trace与报告4.1 配置报告的trace文件目录项目进入持续集成阶段后trace的产出物管理就成为一个不可回避的问题——如果不做设置trace文件会直接散落在CI工作区的临时目录里build一旦结束全部丢失等于白录。我通常会在playwright.config.ts中做如下配置import { defineConfig } from playwright/test; export default defineConfig({ testDir: ./tests, reporter: [ [html, { open: never }], [json, { outputFile: test-results/results.json }], ], use: { trace: { mode: retain-on-failure, screenshots: true, snapshots: true, sources: true, }, }, outputDir: test-results/artifacts, });这里有一个容易被忽略的坑Playwright默认的outputDir是test-results如果你同时在里面放HTML报告、json报告和trace文件后续清理和归档时很容易把报告文件混在一起。我的习惯是给trace单独设置一个artifacts子目录配合CI的artifact上传机制统一打包。在GitLab CI中常见的做法是playwright-tests: stage: test script: - npx playwright install --with-deps chromium - npx playwright test artifacts: when: always paths: - test-results/artifacts/ expire_in: 1 week这样不管构建成功还是失败trace文件都会作为CI产物保留下来即使本地复现不出来也能直接从CI页面下载trace文件回来分析。4.2 失败用例自动保留trace的联动逻辑很多项目的用例其实只有失败才需要深究所以retain-on-failure模式成为首选。搭配retries参数使用时行为需要理清import { defineConfig } from playwright/test; export default defineConfig({ retries: 2, use: { trace: { mode: retain-on-failure, }, }, });当retries设为2某条用例第一次失败、第二次失败、第三次通过时Playwright默认只保留最终结果的trace。第一次、第二次失败时产生的trace会被追加到最终生成的trace里打开Trace Viewer时可以在时间轴顶部切换到以往重试序列中查看不同次尝试的完整记录。这为判断“是环境偶发导致失败还是脚本本身不稳定”提供了直接依据——如果不同重试尝试中页面行为变化比较大多对比几个trace快照就能看出来是不是前后端数据状态不一致导致的。4.3 大项目限流按需录制关键用例如果一个项目的用例总量超过500条全量开trace会让CI的总执行时间显著拉长。按照一套成熟的工程实践我建议按用例的关键程度做区分冒烟用例全部开启trace这部分是发布前最快暴露问题的防线。核心流程用例支付、登录、下单开启retain-on-failure。低频边缘用例增删改查的普通分支全关trace甚至关闭重试把执行时间省下来。具体配置上可以用testMatch区分不同的配置文件// playwright.smoke.config.ts import { defineConfig } from ./playwright.base.config; export default defineConfig({ ...baseConfig, testMatch: /smoke\.spec\.ts/, use: { ...baseConfig.use, trace: on, }, });整个项目的执行策略可以设计为日常全量跑用基础配置冒烟测试用独立配置关键路径的回归用retain-on-failure配置。这样既能保证调试能力又不让trace成为CI的负重。5. 常见问题与调试实录5.1 trace文件没有生成这是最多人遇到的第一个问题。现象很简单用例跑了失败也看到了但test-results目录里没有trace.zip。常见原因就一种——trace模式没配对。如果你配置的是retain-on-failure但用例实际上通过了当然不会生成文件。这种情况不是bug而是你没有理解模式的触发逻辑。还有一种情况是使用了on模式用例正常执行完毕但你在CI里只看测试结果却没有配artifacts上传trace文件其实已经生成了只是不在了。本地排查时可以直接跑一条用例再检查outputDir两种情况都能区分。如果在本地跑命令之后test-results/artifacts目录里只有一个空的目录没有zip文件那问题通常出在浏览器进程被强行杀死比如系统内存不足触发了OOMtrace还没来得及写盘。这种情况多发生在docker容器内存设置过小的CI环境中建议检查容器的内存限制。5.2 Trace Viewer打开后只见空白页面正常生成的trace文件一般都能在Trace Viewer里正常打开如果你看到空白页面或一堆乱码先确认两件事这个zip文件是否真的由Playwright生成的如果是在上传下载过程中被压缩工具二次处理过zip的内部结构可能损坏Trace Viewer会加载失败。文件的路径里是否包含中文或空格命令行模式下这会导致资源加载200但内容异常建议把trace文件重命名为全英文路径再尝试。用命令行打开trace时如果一直卡在加载界面还可以尝试直接以Web模式启动npx playwright show-trace --host 0.0.0.0 --port 8080 trace.zip然后用浏览器访问对应的服务页面。这种方式在需要把trace分享给别人查看时也同样适用——只要对方网络能通不需要安装Playwright环境也能打开查看。5.3 trace文件过大截图堆积导致体积好几MB单条用例的trace文件动辄几十MB在复杂业务系统里并不少见但如果你发现一条普通用例的trace就超过10MB通常是snapshots参数记录了过多的页面快照或者页面本身包含大量图片和动态内容导致网络响应体过大。体积控制方案从粗到细有三档配置层面修改playwright.config.ts把trace对象里的screenshots设为false这个开关对体积影响最大。截图关闭后Trace Viewer依然能查看DOM快照和网络请求。代码层面使用tracing.start()和tracing.stop()手动控制只在关键步骤录制。分类策略配置多个项目project将核心用例和普通用例拆分核心用例开trace普通用例关trace。5.4 多上下文场景下trace错乱在一个用例里手动创建了多个BrowserContexttrace默认只附加在测试主上下文上。如果你在tracing.start()时没有指定上下文就开始操作整个trace的内容会比较乱。解决办法是在每个上下文创建时先显式开启const contextA await browser.newContext(); await contextA.tracing.start({ snapshots: true, screenshots: true }); // 操作A await contextA.tracing.stop({ path: trace-context-a.zip }); const contextB await browser.newContext(); await contextB.tracing.start({ snapshots: true, screenshots: true }); // 操作B await contextB.tracing.stop({ path: trace-context-b.zip });多个上下文之间保持完全隔离是最稳妥的做法不要试图在多个context之间共享一个tracing。5.5 API测试也想要trace怎么办有人会认为API测试没有UI操作用不上trace。但Playwright的APIRequestContext也有自己的错误调试需求比如想查看某个请求时到底发生了什么。这个场景不需要浏览器trace直接用API测试自带的日志策略就行const requestContext await playwright.request.newContext({ extraHTTPHeaders: { Authorization: Bearer ${token}, }, }); // 手动输出请求和响应信息 const response await requestContext.post(https://api.example.com/v1/order); console.log(response.status()); console.log(await response.text());更优雅的做法是开启Playwright的debug日志DEBUGapi,error npx playwright test tests/api --reporterlist这个环境下会在终端看到每个请求的发送和响应细节配合trace里记录的网络请求面板排查API相关问题时效率很高。6. 配置体验优化与使用建议在项目里把Trace真正用顺需要的是一套完整的调优方案我从实际使用经验出发整理了几个优化方向。6.1 控制trace体积的高阶手段trace文件的核心组成是快照数据和网络请求记录。如果项目页面接口返回了大量文本数据trace体积会急剧膨胀。我处理这种情况时常用两种组合只录制关键步骤前文提到的tracing.start/stop方式放弃全流程录制。用配置文件给项目设置不同的trace选项。大项目如果要保持全量录制建议给不同测试环境预设不同体积的策略——比如在本地开发环境开启on模式方便调试在CI环境切换成retain-on-failure以控制总产物规模。另外.gitignore文件记得加上trace输出目录避免误提交到代码仓库。特别是test-results和playwright-report这两个目录我见过不止一次同事把包含敏感页面数据的trace文件推上远程仓库的案例这个安全隐患比想象中更容易发生务必重视。6.2 与截图自动重试策略的配合trace并不是唯一的调试手段Playwright的失败截图和视频录制在定位问题时的专注点位各有不同。我的使用习惯是截图适合快速一眼定位页面是否渲染异常或是否弹了错误提示。视频适合观察界面卡顿、加载顺序、关键动效等时间维度的异常。trace适合深入网络的请求明细、DOM状态变化以及跨步骤操作逻辑。这三者是互补关系同时使用时不会冲突。但也要注意开启视频录制会显著增加CI产物大小。我的建议是普通项目只保留截图加trace视频只在极少数需要给客户演示“自动化如何操作”的场景下才开启。6.3 团队协作中的trace分享流程如果团队里多人协作测试trace文件流转是一个需要规范的事情。我目前比较推荐的操作流程本地跑完失败用例后先在Trace Viewer里自行确认失败原因能自己定位的直接修不用拉别人来一起看。需要协作的把trace.zip上传到团队网盘或CI artifact平台附上一句话的记录用例编号、失败环境、已排查方向。谨慎使用第三方文件分享工具——HTML报告和trace文件里包含的页面接口细节可能属于敏感信息不要因为图方便往外传。对于远程协作的场景我多次直接用ip和端口起show-trace给同事临时查看局域网环境下体验顺畅且避免了文件外传。6.4 记录trace时的内存与进程管理最后提一个较少有人注意但挺重要的问题录制trace的浏览器进程相比普通自动化测试占用内存大约多30%到50%特别是在页面本身较重、且开启截图和快照时。CI环境内存紧张的时候建议给执行机预留至少2GB的可用内存用于trace录制否则极易出现浏览器被强制杀死、整个测试进程崩溃的情况连带trace文件都没了。我的个人建议是在一个稳定的环境里先无线程跑一条最重场景的用例观察执行机的剩余内存再反推并发线程数。内存不够的时候优先减少并发数量而不是减少trace内容——因为这样做了之后偶尔还是能踩到内存溢出的边界。7. 三种实用场景下的完整配置参考7.1 小型个人项目的低成本配置规模小、用例少、没有CI机器人的个人项目把trace作为默认调试工具就好import { defineConfig } from playwright/test; export default defineConfig({ retries: 0, use: { trace: on, screenshot: only-on-failure, }, outputDir: test-results/artifacts, });这种方式最简单直接但要注意及时清理本地trace文件以便给磁盘腾出空间。7.2 中型团队的标准协作配置适合5到10人的测试小组强调稳定性与排查能力共存import { defineConfig } from playwright/test; export default defineConfig({ retries: 1, workers: process.env.CI ? 4 : undefined, use: { trace: retain-on-failure, screenshot: only-on-failure, video: retain-on-failure, }, reporter: [ [html, { open: never }], [list], ], outputDir: test-results/artifacts, });这个配置下失败用例同时产出截图、视频、trace三件套排查任何问题基本都有素材可用。重试次数设1代表大多数偶发问题能自动通过留下的失败都是必须处理的真实缺陷。7.3 大规模分级录制配置用例上千级别的项目就不能再用一套配置走天下了需要按项目划分不同的录制策略import { defineConfig } from playwright/test; export default defineConfig({ projects: [ { name: critical-smoke, testMatch: /critical\.spec\.ts/, retries: 2, use: { trace: on, }, }, { name: key-business, testMatch: /business\/.*\.spec\.ts/, retries: 1, use: { trace: retain-on-failure, }, }, { name: regression, testMatch: /regression\/.*\.spec\.ts/, retries: 0, use: { trace: off, }, }, ], reporter: [ [html, { open: never }], [json, { outputFile: test-results/results.json }], ], });分级之后无论CI的执行时间还是调试效率体验都会明显改善。真正的核心用例永远有足够证据可查而低风险用例跑起来又不会被trace拖慢速度。8. 使用Trace录制的最终心得把Playwright Trace用好靠的并不是某个配置项的灵光一现而是一整套在项目里反复磨合出来的使用流程。我个人最大的体会是Trace不是测试完成后的锦上添花它就是测试流程中最核心的可观测性基建。刚开始接触这个功能时我曾经以为trace只是把页面截图多存了几张直到有一次调试一个第三代支付页面用例在点击确认支付后断言失败凭肉眼根本看不出两个步骤之间发生了什么。打开Trace Viewer之后才发现页面在点击确认后发出了一次请求但响应体里返回的code字段一直是-1而不是前端表面上看起来的“支付成功”提示。这类问题如果不看请求响应体靠截图和重试很难定位到根因而trace几乎是一帧不落地还原了完整的操作现场。在团队协作中我也明显感受到trace的沟通价值。以前测试报一个bug开发第一句话通常是“怎么复现的”现在直接丢一个trace文件过去开发自己打开查看操作步骤和东半球流量面板几分钟就能确认是前端问题还是后端问题沟通效率提升非常明显。最后再分享一个小技巧在录制trace时可以刻意在用例脚本里加上一些带有业务语义的断言注释比如“等待前端返回完整订单数据后再点击下一步”。因为Trace Viewer里会显示每一行源码相当于把内部的分析过程直接回放给后续接手的人比流程文档直观有效得多。