首页 / Playwright 入门教程 / .NET 版入门与差异

Playwright 入门教程

.NET 版入门与差异

本教程共 59 篇 · 第 58 篇 · 更新于 2026-08-04 · 约 11 分钟阅读

Playwright.NETC#MSTestNUnitxUnitNuGet

58. .NET 版入门与差异

本节目标:在 .NET 项目里装好 Playwright,用 MSTest / NUnit / xUnit 任一框架跑通第一个测试。

.NET 版跟 Python 版思路一样:Playwright 只提供库,测试运行交给你熟悉的框架。

区别在于官方多给了一层基类。继承它,PageExpect 就白送给你了。

安装

先建一个测试项目。三种框架挑一个,命令不一样。

# MSTest
dotnet new mstest -n PlaywrightTests

# NUnit
dotnet new nunit -n PlaywrightTests

# xUnit
dotnet new xunit -n PlaywrightTests

进目录,装对应的 NuGet 包。包名跟框架一一对应,别装错

cd PlaywrightTests

dotnet add package Microsoft.Playwright.MSTest   # MSTest 用这个
dotnet add package Microsoft.Playwright.NUnit    # NUnit 用这个
dotnet add package Microsoft.Playwright.Xunit    # xUnit 用这个

这三个包都依赖核心包 Microsoft.Playwright,会自动带下来。只做纯脚本自动化、不写测试,才单独装核心包。

编译一次,然后下载浏览器:

dotnet build
pwsh bin/Debug/net8.0/playwright.ps1 install
Note

第二条命令要用 PowerShell(pwsh)。它是构建时生成的脚本,路径里的 net8.0 要换成你项目实际的目标框架。没装 PowerShell 的话,先 dotnet tool install --global PowerShell

Warning

必须先 dotnet build 再执行 playwright.ps1。没构建过就没有这个脚本文件,新手常在这里报「找不到路径」。

MSTest 集成

继承 PageTest 基类,测试方法里直接用 Page

using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;

namespace PlaywrightTests;

[TestClass]
public class ExampleTest : PageTest
{
    [TestMethod]
    public async Task HasTitle()
    {
        await Page.GotoAsync("https://playwright.dev");
        await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
    }

    [TestMethod]
    public async Task GetStartedLink()
    {
        await Page.GotoAsync("https://playwright.dev");
        await Page.GetByRole(AriaRole.Link, new() { Name = "Get started" }).ClickAsync();
        await Expect(Page.GetByRole(AriaRole.Heading, new() { Name = "Installation" })).ToBeVisibleAsync();
    }
}

PageExpect 都来自基类,不用自己创建、也不用手动关闭。每个测试方法自动拿到独立的浏览器上下文。

跑测试就是标准命令:

dotnet test

NUnit 集成

代码几乎一样,只有特性(Attribute)名字换了。

using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright;
using Microsoft.Playwright.NUnit;

namespace PlaywrightTests;

[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class ExampleTest : PageTest
{
    [Test]
    public async Task HasTitle()
    {
        await Page.GotoAsync("https://playwright.dev");
        await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
    }
}

[Parallelizable(ParallelScope.Self)] 是 NUnit 的并行开关。加上它,同一个类里的测试才能并发跑。

xUnit 集成

xUnit 用 [Fact] 标记测试,类上不需要额外特性。

using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Playwright;
using Microsoft.Playwright.Xunit;

namespace PlaywrightTests;

public class ExampleTest : PageTest
{
    [Fact]
    public async Task HasTitle()
    {
        await Page.GotoAsync("https://playwright.dev");
        await Expect(Page).ToHaveTitleAsync(new Regex("Playwright"));
    }
}

xUnit 默认按测试类并行,同一个类内部串行。这跟 NUnit 的默认行为正好相反。

Tip

1.62.x 同时支持 xUnit v2 和 xUnit v3。用 v3 的话,装 Microsoft.Playwright.Xunit.v3 这个包(注意末尾是小写 v3)。具体包名以 NuGet 上的实际发布为准。

四个基类怎么选

官方给了一组基类,粒度从粗到细。继承越具体的,拿到的现成东西越多。

基类提供什么什么时候用
PageTestPage + Context + Browser + Expect绝大多数 UI 测试,默认选它
ContextTestContext + Browser一个测试里要开多个页面
BrowserTestBrowser要自己控制上下文配置
PlaywrightTestPlaywright 实例只做 API 测试,或完全自定义

层级关系是包含式的:PageTest 里也能拿到 ContextBrowser

不确定就用 PageTest。它覆盖九成场景。

命名与传参差异

C# 版的写法差异集中在三点,习惯了就很规律。

方法名帕斯卡命名,全部带 Async 后缀

await Page.GotoAsync("https://example.com");
await Page.GetByLabel("用户名").FillAsync("admin");
await Page.GetByRole(AriaRole.Button, new() { Name = "登录" }).ClickAsync();

选项用对象初始化器,new() 可以省略类型名

// 完整写法
await Page.GotoAsync("https://example.com", new PageGotoOptions
{
    WaitUntil = WaitUntilState.NetworkIdle,
    Timeout = 15000,
});

// 简写:编译器能推断类型
await Page.GotoAsync("https://example.com", new() { WaitUntil = WaitUntilState.NetworkIdle });

枚举替代字符串。TS 里传 'button',C# 里传 AriaRole.Button。编译期就能查错,敲错了根本编译不过。

断言写法

Expect 来自基类,用法跟 TS 版对得上,只是要 await

await Expect(Page.GetByTestId("status")).ToHaveTextAsync("成功");
await Expect(Page.GetByRole(AriaRole.Button)).ToBeEnabledAsync();
await Expect(Page).ToHaveURLAsync(new Regex(".*dashboard"));

不继承基类时,从静态类里取:

using static Microsoft.Playwright.Assertions;

await Expect(locator).ToBeVisibleAsync();

跟 Python 版一样,这些断言会自动重试,别用 Assert.AreEqual 去判断页面状态。

定制上下文配置

想改视口、语言、设备模拟,重写基类的 ContextOptions() 方法。

public class MyTest : PageTest
{
    public override BrowserNewContextOptions ContextOptions()
    {
        return new BrowserNewContextOptions
        {
            Locale = "zh-CN",
            ViewportSize = new() { Width = 1440, Height = 900 },
            ColorScheme = ColorScheme.Dark,
        };
    }
}

这个方法对当前类的所有测试生效。想全局生效,就抽一个自己的基类,让测试类都继承它。

用配置文件控制运行

不改代码就想换浏览器、开有头模式,两条路。

环境变量,临时试一下最方便:

HEADED=1 dotnet test
BROWSER=webkit dotnet test

runsettings 文件,团队协作用这个更规范:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <Playwright>
    <BrowserName>chromium</BrowserName>
    <LaunchOptions>
      <Headless>false</Headless>
      <SlowMo>250</SlowMo>
    </LaunchOptions>
    <ExpectTimeout>10000</ExpectTimeout>
  </Playwright>
</RunSettings>
dotnet test --settings:.runsettings

也可以命令行直接传参覆盖:

dotnet test -- Playwright.BrowserName=firefox Playwright.LaunchOptions.Headless=false

并行执行

.NET 版没有内置并行,靠各测试框架自己的机制。

# NUnit:指定并发线程数
dotnet test -- NUnit.NumberOfTestWorkers=5

# MSTest:开启方法级并行
dotnet test -- MSTest.Parallelize.Workers=5 MSTest.Parallelize.Scope=method

xUnit 默认就按类并行,通常不用额外配置。

并发度别开太大。每个 worker 都要占一个浏览器上下文,机器扛不住反而更慢。

脱离测试框架直接用

只写自动化脚本、不做测试,就用核心包。这时候要自己管生命周期。

using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
    Headless = false,
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://playwright.dev");
await page.ScreenshotAsync(new() { Path = "example.png" });

usingawait using 负责释放资源,比手动写 CloseAsync() 稳妥。

Browser 要用 await using,因为它的释放动作本身是异步的。Playwright 用普通 using 就够。

新手常踩的三个坑

第一个:忘了 await C# 里漏掉 await,方法返回一个 Task,动作根本没执行。编译器只给警告不报错,测试却诡异地全绿。

Page.GotoAsync("https://example.com");        // ❌ 没 await,等于没跑
await Page.GotoAsync("https://example.com");  // ✅

第二个:测试方法没写成 async Task 写成 void 的话,测试框架不会等它跑完,断言失败也捕获不到。

[TestMethod]
public async Task MyTest() { }   // ✅ 一律用 async Task

[TestMethod]
public async void MyTest() { }   // ❌ async void 不可控

第三个:升级包后忘了重装浏览器。 NuGet 包版本变了,对应的浏览器二进制也换了。改完版本号,重新跑一遍 dotnet buildplaywright.ps1 install

跟 TS 版的对照速查

手边有 TS 文档想翻译成 C#,照这张表改就行。

TypeScriptC#
page.goto(url)await Page.GotoAsync(url)
page.getByText('登录')Page.GetByText("登录")
locator.click()await locator.ClickAsync()
expect(l).toHaveText('x')await Expect(l).ToHaveTextAsync("x")
{ timeout: 5000 }new() { Timeout = 5000 }
test.beforeEach[TestInitialize] / [SetUp]

规律就三条:方法加 Asyncawait、选项换成对象初始化器、钩子交给测试框架的特性。

小结

装包看框架:MSTest / NUnit / Xunit 三个包对应三种测试框架,别装混。

dotnet build 再跑 playwright.ps1 install,顺序反了会找不到脚本。

继承 PageTest 就能直接用 PageExpect,四个基类按需要挑。

方法名全带 Async、选项用对象初始化器、角色用枚举,这是 C# 版三个固定写法。

并行和配置都交给测试框架与 runsettings,Playwright 本身不管这些。