Playwright .NET 测试 Trace Viewer 完全指南:录制、打开与逐步分析测试执行轨迹
Playwright Trace Viewer 是一个图形化调试工具,允许你回溯已录制测试(trace)中的每一步操作,可视化地查看每个动作发生前后页面的完整状态。本篇指南面向 .NET(C#)测试开发者,完整讲解如何通过 BrowserContext.Tracing API 在 MSTest、NUnit、xUnit 及 xUnit v3 下录制 trace,如何用 Playwright CLI 打开 trace 文件并逐步排查测试失败原因,并结合 Playwright 仓库源码剖析 trace 压缩包的生成机制与各录制选项的真实含义。
什么是 Trace Viewer
Trace Viewer 是一个 GUI 工具,用于探索你在测试运行过程中录制的 Playwright trace。所谓 trace,就是把一次测试执行过程中的页面快照、网络请求、控制台日志、源码位置等信息完整打包成的 .zip 档案。打开 trace 后,你可以:
- 前后穿行(go back and forward) 测试中的每个动作(action);
- 直观看到每个动作发生时页面发生了什么,包括动作前后的 DOM 状态对比;
- 在测试每一步检查 log(日志)、source(源码)与 network(网络请求);
- 由于 Trace Viewer 会创建 DOM 快照(snapshot),你可以与快照页面完整交互——点击元素、打开 DevTools 等,就像测试当时还活着一样。
读完本篇,你将掌握两项核心能力:如何录制一份 trace 与如何打开并分析 trace。
录制 trace:BrowserContext.Tracing API
在 .NET 环境下,trace 通过 BrowserContext.Tracing 属性暴露的 API 录制。基本模式是:
- 在测试的初始化阶段(
[SetUp]/[TestInitialize]/InitializeAsync)调用Context.Tracing.StartAsync(...)开始录制; - 执行测试动作;
- 在测试的清理阶段(
[TearDown]/[TestCleanup]/DisposeAsync)调用Context.Tracing.StopAsync(new() { Path = ... })停止录制,并把 trace 导出为指定路径的 zip 文件。
StartAsync 的四个常用参数:
| 参数 | 类型 | 作用 |
|---|---|---|
Title |
string |
在 Trace Viewer 中展示的 trace 名称,建议填"测试类名.测试方法名" |
Screenshots |
bool |
是否录制截图,用于构建时间轴预览的胶片条(film strip) |
Snapshots |
bool |
是否为每个动作捕获完整 DOM 快照(含网络活动),这是"可交互快照"的来源 |
Sources |
bool |
是否把源码文件打包进 trace,使 Source 面板能显示动作对应的代码行 |
MSTest 示例
使用 Microsoft.Playwright.MSTest 提供的 PageTest 基类:
using System.Text.RegularExpressions;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestClass]
public class ExampleTest : PageTest
{
[TestInitialize]
public async Task TestInitialize()
{
await Context.Tracing.StartAsync(new()
{
Title = $"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}",
Screenshots = true,
Snapshots = true,
Sources = true
});
}
[TestCleanup]
public async Task TestCleanup()
{
await Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
Environment.CurrentDirectory,
"playwright-traces",
$"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}.zip"
)
});
}
[TestMethod]
public async Task GetStartedLink()
{
// ...
}
}
NUnit 示例
NUnit 下通过 TestContext.CurrentContext 获取当前测试的类名、方法名与工作目录:
namespace PlaywrightTests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class Tests : PageTest
{
[SetUp]
public async Task Setup()
{
await Context.Tracing.StartAsync(new()
{
Title = $"{TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.Name}",
Screenshots = true,
Snapshots = true,
Sources = true
});
}
[TearDown]
public async Task TearDown()
{
await Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
TestContext.CurrentContext.WorkDirectory,
"playwright-traces",
$"{TestContext.CurrentContext.Test.ClassName}.{TestContext.CurrentContext.Test.Name}.zip"
)
});
}
[Test]
public async Task GetStartedLink()
{
// ..
}
}
xUnit / xUnit v3 示例
xUnit 不像 MSTest/NUnit 那样提供"当前测试名"的直接 API,因此官方文档采用一个自定义的 BeforeAfterTestAttribute,通过反射在测试前后缓存当前类名与方法名:
using System.Reflection;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit; // xUnit v3 改为 Microsoft.Playwright.Xunit.v3
using Xunit.Sdk;
namespace PlaywrightTests;
[WithTestName]
public class UnitTest1 : PageTest
{
public override async Task InitializeAsync()
{
await base.InitializeAsync().ConfigureAwait(false);
await Context.Tracing.StartAsync(new()
{
Title = $"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}",
Screenshots = true,
Snapshots = true,
Sources = true
});
}
public override async Task DisposeAsync()
{
await Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
Environment.CurrentDirectory,
"playwright-traces",
$"{WithTestNameAttribute.CurrentClassName}.{WithTestNameAttribute.CurrentTestName}.zip"
)
});
await base.DisposeAsync().ConfigureAwait(false);
}
[Fact]
public async Task GetStartedLink()
{
// ...
await Page.GotoAsync("https://playwright.dev/dotnet/docs/intro");
}
}
public class WithTestNameAttribute : BeforeAfterTestAttribute
{
public static string CurrentTestName = string.Empty;
public static string CurrentClassName = string.Empty;
public override void Before(MethodInfo methodInfo)
{
CurrentTestName = methodInfo.Name;
CurrentClassName = methodInfo.DeclaringType!.Name;
}
public override void After(MethodInfo methodInfo)
{
}
}
注意两个细节:xUnit 版本在 DisposeAsync 中先导出 trace、再调用 base.DisposeAsync(),保证关闭浏览器上下文之前 trace 已经落盘;xUnit v3 与 xUnit v2 示例的唯一差异是 PageTest 基类的命名空间(Microsoft.Playwright.Xunit vs Microsoft.Playwright.Xunit.v3)。
产物位置
按上述方式运行测试后,每个测试会生成一个 zip 文件,例如 PlaywrightTests.ExampleTest.GetStartedLink.zip,并被写入 bin/Debug/net8.0/playwright-traces/ 目录(即测试运行时的工作目录下 playwright-traces 子目录,Debug 构建对应 bin/Debug/net8.0/)。
打开 trace 文件
方式一:本地 Playwright CLI
使用 .NET 包生成的 CLI 脚本 playwright.ps1 执行 show-trace 子命令,务必传入 trace zip 的完整路径:
pwsh bin/Debug/net8.0/playwright.ps1 show-trace bin/Debug/net8.0/playwright-traces/PlaywrightTests.ExampleTest.GetStartedLink.zip
show-trace 命令在仓库中定义于 CLI 入口,其参数既可是一个本地 zip 文件路径,也可以是一个远程 URL(例如 CI 上托管的 trace 地址),这意味着你无需从 CI 下载文件就能直接查看远程 trace。
方式二:浏览器中打开
你也可以把 trace 上传到 trace.playwright.dev 在浏览器中打开。该站点是 Trace Viewer 的静态托管版本:trace 完全在浏览器端加载解析,不会向外传输任何数据。
打开之后能做什么
- 点击每一个动作或使用时间轴(timeline),查看该动作执行前后页面的状态;
- 检查测试每一步的 log、source 与 network;
- Trace Viewer 会为快照创建可交互的 DOM 副本,你可以完整操作快照页面、甚至打开 DevTools,复现当时的页面行为。
详细的各面板(Actions / Screenshots / Snapshots / Source / Log / Errors / Console / Network / Metadata)功能说明,可参考 Trace Viewer 完整指南,其中还介绍了 JS Test Runner 下"仅失败时录制 trace"(trace: 'retain-on-failure'、trace: 'on-first-retry')等配置。在 .NET 生态中,"仅在测试失败时导出"可通过在清理阶段判断测试结果、把 StopAsync 的 Path 设为 null 来实现(Path 为空时 trace 只被丢弃而不落盘,源码中对应 tracingStopChunk({ mode: 'discard' }),见 Tracing 客户端)。
源码解析:trace zip 是如何产生的
结合仓库源码可以看清上面 API 调用的底层链路,理解 StartAsync/StopAsync 各参数到底改变了什么。
客户端(Tracing 类):start 先把 snapshots 归一化为 { dom, aria, screen } 结构,再向服务端发送 tracingStart(携带 snapshotDom / snapshotAria / snapshotScreen / screencast / live),随后立即调用 tracingStartChunk 传入 title——这就是为什么 Title 是展示名称、Sources 则由客户端在打包时决定是否附带。stop 时走 _saveChunk:本地模式下先以 entries 模式取回 trace 条目,再调用 localUtils.zip 把 trace 文件、网络文件与(可选)源码打成一个 zip;远程模式下服务端直接产出 archive artifact,客户端再追加源码。
协议层(channels 定义):TracingTracingStartOptions 精确列出了录制选项的底层字段——name(中间文件前缀)、snapshotDom、snapshotAria、snapshotScreen、screencast(对应 Screenshots)、live(实时写入而非结束时归档,便于边跑边看)。
服务端(Tracing 录制器):start 会在 tracesDir 下按选项按需创建 resources、screencast、screenshots、aria 子目录,并初始化 {traceName}.trace 与 {traceName}.network 两个流文件;当 snapshotDom 开启时同步启动 HAR 追踪器(omitScripts 取决于 live 模式),源码中注释也说明了取舍:"Tracing is 10x bigger if we include scripts in every trace."——这正是理解 trace 体积与 Sources/snapshots 选项关系的关键。startChunk 会把 title 写入 context-options 事件(携带浏览器名、Playwright 版本、平台等元信息),这也是 Trace Viewer 中 Metadata 面板数据的来源。
从上述实现可以推断几点实践含义:
Snapshots = true(即 DOM 快照)是体积大头,同时它也是"可交互快照"的前提;只做快速网络问题排查时可考虑按需裁剪;Title不影响录制内容,只影响 Trace Viewer 中显示的名称,但对多 trace 场景的区分非常重要;StopAsync的Path决定导出位置;不传Path则 trace 被直接丢弃,可用于"失败才导出"模式。
适用前提与后续学习
- 上述示例基于 .NET 8(产物目录为
bin/Debug/net8.0/),测试框架需使用对应的PageTest基类包:Microsoft.Playwright.MSTest、Microsoft.Playwright.NUnit、Microsoft.Playwright.Xunit(v3 为Microsoft.Playwright.Xunit.v3); - CLI 打开 trace 需使用 .NET 包生成的
playwright.ps1(PowerShell 下以pwsh调用),或在具备 Node 环境时使用npx playwright show-trace; - 关于 MSTest、NUnit、xUnit、xUnit v3 四类基类的更多说明,参见 Test runners 文档;
- 若需要在 CI 中运行这些测试并归档 trace 产物,可继续阅读 CI 入门指南。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00