首页
/ Playwright .NET 测试 Trace Viewer 完全指南:录制、打开与逐步分析测试执行轨迹

Playwright .NET 测试 Trace Viewer 完全指南:录制、打开与逐步分析测试执行轨迹

2026-09-06 17:13:32作者:韦蓉瑛

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 录制。基本模式是:

  1. 在测试的初始化阶段[SetUp] / [TestInitialize] / InitializeAsync)调用 Context.Tracing.StartAsync(...) 开始录制;
  2. 执行测试动作;
  3. 在测试的清理阶段[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 生态中,"仅在测试失败时导出"可通过在清理阶段判断测试结果、把 StopAsyncPath 设为 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(中间文件前缀)、snapshotDomsnapshotAriasnapshotScreenscreencast(对应 Screenshots)、live(实时写入而非结束时归档,便于边跑边看)。

服务端Tracing 录制器):start 会在 tracesDir 下按选项按需创建 resourcesscreencastscreenshotsaria 子目录,并初始化 {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 场景的区分非常重要;
  • StopAsyncPath 决定导出位置;不传 Path 则 trace 被直接丢弃,可用于"失败才导出"模式。

适用前提与后续学习

  • 上述示例基于 .NET 8(产物目录为 bin/Debug/net8.0/),测试框架需使用对应的 PageTest 基类包:Microsoft.Playwright.MSTestMicrosoft.Playwright.NUnitMicrosoft.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 入门指南
登录后查看全文
热门项目推荐
相关项目推荐