☰
Playwright Trace录制实战:从零搭建自动化测试可观测体系
2026/10/10 16:31:16 网站建设 项目流程

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[type="submit"]'); // 从商品列表进入详情 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_OPEN=always环境变量,失败用例的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文件,那问题通常出在浏览器进程被强行杀死(比如系统内存不足触发了OOM),trace还没来得及写盘。这种情况多发生在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错乱

在一个用例里手动创建了多个BrowserContext,trace默认只附加在测试主上下文上。如果你在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日志:

DEBUG=api,error npx playwright test tests/api --reporter=list

这个环境下,会在终端看到每个请求的发送和响应细节,配合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里会显示每一行源码,相当于把内部的分析过程直接回放给后续接手的人,比流程文档直观有效得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询