
1. 项目概述为什么需要一个“开箱即用”的自动化模板如果你是一名C#开发者最近正在研究UI自动化那么“Playwright”这个名字你一定不陌生。它是由微软官方出品的一款现代化、跨浏览器、跨平台的Web自动化与测试框架。相比于老牌的SeleniumPlaywright在速度、稳定性、功能丰富度上都有显著提升尤其是它对现代Web应用如单页应用SPA的异步渲染、网络拦截、文件下载等场景支持得非常好。然而当你兴冲冲地打开官方文档准备用C#启动第一个Playwright项目时可能会遇到一系列“劝退”点从创建项目、安装NuGet包、配置浏览器驱动、编写第一个脚本到处理异步编程、管理浏览器上下文、实现页面对象模型POM每一步都可能踩坑。环境配置的繁琐、异步代码的初学门槛、以及缺乏一个清晰的最佳实践结构常常让开发者从“想试试”到“算了吧”。这正是“可直接运行的Playwright C# 自动化模板”项目的价值所在。它不是一个简单的代码片段而是一个精心设计的、生产就绪的解决方案起点。这个模板为你预设了所有必要的依赖、一个清晰的项目结构、一套经过验证的编码规范以及几个典型的自动化用例。你的目标不是从零开始造轮子而是拿到一个“发动机已经启动”的赛车直接开上赛道专注于实现你的业务逻辑。无论是用于Web自动化测试、数据抓取RPA、还是日常的重复性Web操作这个模板都能让你在几分钟内就搭建起一个健壮、可维护的自动化工程。2. 核心设计思路构建一个面向生产的自动化脚手架一个优秀的模板其价值远不止于“能运行”。它应该体现出一套经过实战检验的最佳实践让后续的开发和维护事半功倍。我们这个C# Playwright模板的核心设计围绕以下几个原则展开2.1 清晰的分层与职责分离模板采用了经典的分层架构但针对自动化场景做了优化。通常一个粗糙的脚本会把所有代码——浏览器启动、元素定位、业务操作、断言检查——都塞在一个方法里。这在小规模尝试时没问题但一旦脚本规模扩大就会变成难以维护的“面条代码”。我们的模板在创建之初就强制进行职责分离驱动层这是与Playwright .NET API直接交互的一层。我们在这里封装了浏览器Chromium, Firefox, WebKit的启动、上下文Context和页面Page的生命周期管理。例如模板会提供一个BrowserFixture类使用IAsyncLifetime如果用于测试或简单的静态/单例模式来确保浏览器实例在多个测试或操作间被高效地复用和清理而不是每次操作都打开关闭浏览器这能极大提升执行速度。页面对象层这是模板的精髓之一。我们为每个被测页面或主要页面组件创建一个对应的“页面对象”类。这个类封装了该页面的所有元素定位器使用Playwright强类型的ILocator和基本的页面操作如导航、输入、点击。例如一个LoginPage类会有UserNameInput、PasswordInput、SubmitButton这些属性以及LoginAsync(string username, string password)这个方法。这样做的好处是当页面UI发生变化时你只需要在一个地方页面对象类修改定位器所有使用该页面的脚本都会自动生效极大提高了代码的可维护性。业务逻辑层这一层由测试用例或自动化流程脚本组成。它们通过调用页面对象层提供的方法组合成完整的业务流。例如一个“用户登录并下单”的测试在业务逻辑层可能就是依次调用LoginPage.LoginAsync()、HomePage.SearchProduct()、ProductPage.AddToCart()、CheckoutPage.PlaceOrder()。这层代码读起来就像自然语言清晰易懂。工具与配置层模板会包含一个Configuration类用于从appsettings.json或环境变量中读取配置如基础URL、超时时间、是否启用无头模式、是否录制视频等。此外还会提供常用的工具方法比如等待特定条件成立的辅助方法、随机数据生成器、截图保存功能等。2.2 拥抱异步编程的最佳实践Playwright .NET API 几乎全部是异步的这是为了高效处理现代Web的异步特性。对于不熟悉async/await的C#开发者来说这是一道坎。模板不仅使用了异步还示范了正确用法一致的异步上下文确保从顶层的程序入口如测试框架的[Fact]方法或控制台应用的Main方法开始就使用async Task避免出现.Result或.Wait()这种导致死锁的同步阻塞写法。模板中的示例代码全部采用async/await模式。配置异步等待选项Playwright的许多操作如ClickAsync、FillAsync都接受一个PageClickOptions或FrameFillOptions参数里面可以设置超时、是否强制点击等。模板会示范如何合理设置这些选项比如将默认超时时间统一在配置中管理避免脚本因网络或性能波动而随机失败。并行执行支持通过合理使用Playwright的BrowserContext模板可以轻松支持测试用例的并行执行。每个独立的上下文Context拥有独立的Cookie、本地存储和会话相互隔离就像不同的浏览器会话。模板会展示如何利用这一特性来加速自动化套件的执行。2.3 内置的健壮性机制自动化脚本最怕“脆弱”——因为页面加载慢了一点、某个元素晚出现了半秒整个脚本就失败了。模板内置了几种机制来提升健壮性自动等待Playwright本身具有强大的自动等待机制在执行操作如点击前它会检查元素是否可见、可操作、稳定等。模板会充分利用这一点并教你如何编写自定义的等待条件例如等待某个Ajax请求完成、等待页面跳转到特定URL。重试逻辑对于某些非确定性的失败如网络瞬时波动模板可以在业务逻辑层或通过包装的辅助方法引入简单的重试机制。这不是盲目重试而是针对特定操作如获取动态数据在失败后等待片刻再试一次。丰富的报告与诊断模板集成了基本的日志记录如使用ILogger并在关键步骤失败、完成特定操作自动截图。更高级的配置还可以启用Playwright的追踪功能生成一个可以离线查看的、包含所有操作、网络请求和时间线的可视化报告这对于调试复杂的失败场景至关重要。3. 模板核心组件详解与实操要点现在让我们深入这个模板的内部看看各个核心部分是如何构建的以及在实际操作中需要注意什么。3.1 项目结构与依赖管理当你通过dotnet new命令或直接克隆模板仓库创建项目后你会看到一个清晰的结构PlaywrightAutomationTemplate/ ├── PlaywrightAutomationTemplate.csproj ├── appsettings.json ├── Pages/ │ ├── BasePage.cs │ ├── HomePage.cs │ └── LoginPage.cs ├── Tests/ (或 Features/) │ └── LoginTests.cs ├── Utilities/ │ ├── BrowserFixture.cs │ ├── Configuration.cs │ └── ScreenshotHelper.cs └── Program.cs (或全局的测试入口)csproj文件这是核心。它除了引用Microsoft.Playwright.NUnit或Microsoft.Playwright.MSTest如果你用测试框架以及Microsoft.Playwright主库之外关键的一步是包含了Playwright的CLI工具引用和Browser安装目标。ItemGroup PackageReference IncludeMicrosoft.Playwright.NUnit Version1.45.0 / PackageReference IncludeMicrosoft.Playwright Version1.45.0 / /ItemGroup ItemGroup PackageReference IncludeMicrosoft.Playwright.CLI Version1.45.0 PrivateAssetsall/PrivateAssets IncludeAssetsruntime; build; native; contentfiles; analyzers; buildtransitive/IncludeAssets /PackageReference /ItemGroup Target NamePlaywrightInstall AfterTargetsBuild Exec Commandpwsh bin\Debug\net8.0\playwright.ps1 install / /Target注意这里的AfterTargetsBuild是一个巧妙的设计。它意味着每次成功编译项目后会自动执行playwright install命令确保所需的浏览器Chromium, Firefox, WebKit已安装且版本匹配。这解决了“在我机器上能跑”的环境一致性问题。如果你在CI/CD流水线中可能需要单独运行这个安装步骤。appsettings.json存放所有可配置项。模板会预置一些最常用的{ Playwright: { BrowserType: chromium, // 可选chromium, firefox, webkit Headless: true, SlowMo: 0, // 操作间隔毫秒调试时可设为100-500 Timeout: 30000, // 全局超时毫秒 BaseUrl: https://your-test-site.com } }3.2 浏览器驱动与生命周期管理 (BrowserFixture)BrowserFixture是模板的基石它负责管理昂贵的浏览器资源。using Microsoft.Playwright; using Microsoft.Extensions.Configuration; namespace PlaywrightAutomationTemplate.Utilities; public class BrowserFixture : IAsyncDisposable { private IPlaywright? _playwright; private IBrowser? _browser; private IBrowserContext? _context; public IPage? Page { get; private set; } public async Task InitializeAsync() { // 1. 创建Playwright实例 _playwright await Playwright.CreateAsync(); // 2. 从配置读取参数 var config Configuration.GetPlaywrightConfig(); var launchOptions new BrowserTypeLaunchOptions { Headless config.Headless, SlowMo config.SlowMo }; // 3. 启动指定类型的浏览器 _browser await _playwright[config.BrowserType].LaunchAsync(launchOptions); // 4. 创建上下文Context这是实现并行和隔离的关键 var contextOptions new BrowserNewContextOptions { ViewportSize new ViewportSize { Width 1920, Height 1080 }, IgnoreHTTPSErrors true // 根据测试环境决定是否忽略HTTPS证书错误 }; _context await _browser.NewContextAsync(contextOptions); // 5. 创建页面Page Page await _context.NewPageAsync(); Page.SetDefaultTimeout(config.Timeout); // 设置页面级默认超时 } public async ValueTask DisposeAsync() { // 严格按照创建的反顺序关闭资源 if (Page ! null) await Page.CloseAsync(); if (_context ! null) await _context.CloseAsync(); if (_browser ! null) await _browser.CloseAsync(); _playwright?.Dispose(); } }实操要点与心得上下文Context vs 页面Page务必理解这两者的区别。一个浏览器实例可以有多个上下文一个上下文可以有多个页面。上下文提供了Cookie、本地存储的隔离。对于需要登录状态的并行测试为每个测试创建一个独立的上下文是最佳实践而不是共用页面。资源清理DisposeAsync中的关闭顺序很重要。必须先关页面再关上下文最后关浏览器。使用IAsyncDisposable接口可以方便地与using语句或测试框架的IAsyncLifetime集成确保资源即使发生异常也能被正确释放。视口设置在上下文中设置视口大小比在页面中通过Page.SetViewportSizeAsync更高效因为它会影响该上下文中所有新打开的页面。这有助于保证UI布局测试的一致性。超时设置Playwright有多个层级的超时全局、浏览器、上下文、页面、元素操作。模板在页面级设置一个合理的默认值如30秒对于特定需要更长或更短等待的操作应该在调用方法时通过Page.ClickAsync(selector, new PageClickOptions { Timeout 5000 })来覆盖。3.3 页面对象模型POM的实现 (BasePage与具体页面)BasePage是所有页面对象的基类它封装了公共方法和属性。using Microsoft.Playwright; namespace PlaywrightAutomationTemplate.Pages; public abstract class BasePage { protected IPage Page { get; } protected BasePage(IPage page) { Page page; } // 公共导航方法示例 public virtual async Task GoToAsync(string url) await Page.GotoAsync(url); // 公共等待方法示例等待页面包含特定文本 public async Task WaitForPageContainsTextAsync(string text, int timeout 30000) { var locator Page.GetByText(text, new PageGetByTextOptions { Exact false }); await locator.WaitForAsync(new LocatorWaitForOptions { Timeout timeout }); } // 公共元素获取辅助方法使用Playwright 1.44推荐的GetBy角色、文本等定位方式 protected ILocator GetByRole(AriaRole role, string? name null) Page.GetByRole(role, new PageGetByRoleOptions { Name name }); protected ILocator GetByText(string text) Page.GetByText(text); protected ILocator GetByPlaceholder(string placeholder) Page.GetByPlaceholder(placeholder); }具体页面类示例 (LoginPage.cs)namespace PlaywrightAutomationTemplate.Pages; public class LoginPage : BasePage { // 使用强类型定位器避免在代码中散落字符串选择器 private ILocator UserNameInput GetByPlaceholder(用户名/邮箱/手机号); private ILocator PasswordInput Page.Locator(#password); // 示例使用CSS选择器 private ILocator SubmitButton GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name 登录 }); private ILocator ErrorMessage Page.Locator(.alert-error); public LoginPage(IPage page) : base(page) { } // 核心业务方法登录 public async Taskbool LoginAsync(string username, string password) { await UserNameInput.FillAsync(username); await PasswordInput.FillAsync(password); await SubmitButton.ClickAsync(); // 等待页面跳转或出现成功/失败标识。这里以等待错误消息出现短时间为例判断是否登录失败。 try { // 如果2秒内出现了错误信息则认为登录失败 await ErrorMessage.WaitForAsync(new LocatorWaitForOptions { Timeout 2000 }); return false; // 登录失败 } catch (TimeoutException) { // 2秒内没等到错误信息假设登录成功实际项目中应有更可靠的判断如等待跳转到首页 return true; } } // 导航到登录页 public async Task GoToLoginPageAsync(string baseUrl) { await GoToAsync(${baseUrl}/login); // 可以增加一个等待确保登录页关键元素加载完成 await UserNameInput.WaitForAsync(); } }实操要点与心得定位器策略优先使用Playwright推荐的GetByRole、GetByText、GetByLabel、GetByPlaceholder等语义化定位方式。它们比CSS或XPath选择器更具可读性且对UI变化的适应性更强只要角色或文本不变。对于复杂或动态元素再考虑使用CSS或XPath。懒加载定位器注意我们在LoginPage中使用的是属性而不是字段来定义定位器。这是C#中实现“懒计算”的一种方式。每次访问UserNameInput属性时它都会实时计算并返回一个新的ILocator实例。这比在构造函数中初始化所有定位器更灵活也避免了在页面尚未加载完成时就尝试定位元素可能引发的异常。页面对象方法应返回什么这是一个设计决策。LoginAsync方法返回了一个bool表示成功与否。更常见的做法是让它返回下一个页面的对象例如public async TaskHomePage LoginAsync(...)这样可以实现流畅的调用链var homePage await loginPage.LoginAsync(...)。模板可以根据你的偏好提供不同风格的示例。等待的智慧在页面对象的方法内部必须包含足够的等待逻辑确保元素可交互后再操作。但要注意避免使用Task.Delay这种固定休眠而应使用Playwright内置的等待WaitForAsync,WaitForURLAsync或自定义的等待条件。4. 从模板到实战编写你的第一个自动化流程有了模板的基础设施编写自动化脚本就变得非常直观。假设我们要实现一个“在电商网站搜索商品并查看详情”的流程。4.1 定义额外的页面对象首先在Pages/目录下创建HomePage.cs和ProductPage.cs。HomePage.cs:public class HomePage : BasePage { private ILocator SearchInput GetByPlaceholder(搜索商品); private ILocator SearchButton GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name 搜索 }); private ILocator FirstProductLink Page.Locator(.product-list a).First; public HomePage(IPage page) : base(page) { } public async Task SearchProductAsync(string keyword) { await SearchInput.FillAsync(keyword); await SearchButton.ClickAsync(); // 等待搜索结果加载 await FirstProductLink.WaitForAsync(); } public async TaskProductPage ClickFirstProductAsync() { await FirstProductLink.ClickAsync(); // 假设点击后跳转到商品详情页返回该页面的对象 return new ProductPage(Page); } }ProductPage.cs:public class ProductPage : BasePage { private ILocator ProductTitle Page.Locator(h1.product-title); private ILocator AddToCartButton GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name 加入购物车 }); private ILocator SuccessMessage Page.GetByText(已成功加入购物车); public ProductPage(IPage page) : base(page) { } public async Taskstring GetProductTitleAsync() { return await ProductTitle.InnerTextAsync(); } public async Taskbool AddToCartAsync() { await AddToCartButton.ClickAsync(); await SuccessMessage.WaitForAsync(); return true; } }4.2 编写自动化流程脚本现在我们可以像搭积木一样组合这些页面对象。在Tests/目录下创建一个新的测试类或脚本文件。using Microsoft.Playwright.NUnit; // 如果使用NUnit using PlaywrightAutomationTemplate.Pages; using PlaywrightAutomationTemplate.Utilities; namespace PlaywrightAutomationTemplate.Tests; // 示例使用NUnit测试框架 public class ProductSearchTest { private IBrowserContext? _context; private IPage? _page; private IPlaywright? _playwright; private IBrowser? _browser; [SetUp] public async Task Setup() { // 使用模板提供的配置 var config Configuration.GetPlaywrightConfig(); _playwright await Playwright.CreateAsync(); _browser await _playwright[config.BrowserType].LaunchAsync(new BrowserTypeLaunchOptions { Headless config.Headless }); _context await _browser.NewContextAsync(); _page await _context.NewPageAsync(); await _page.GotoAsync(config.BaseUrl); } [TearDown] public async Task TearDown() { if (_page ! null) await _page.CloseAsync(); if (_context ! null) await _context.CloseAsync(); if (_browser ! null) await _browser.CloseAsync(); _playwright?.Dispose(); } [Test] public async Task CanSearchAndViewProductDetails() { // 1. 初始化页面对象 var homePage new HomePage(_page!); // 2. 执行搜索 await homePage.SearchProductAsync(笔记本电脑); // 3. 进入第一个商品详情页并获取页面对象 var productPage await homePage.ClickFirstProductAsync(); // 4. 在详情页进行操作和断言 var title await productPage.GetProductTitleAsync(); StringAssert.Contains(笔记本, title, 商品标题应包含搜索关键词); var added await productPage.AddToCartAsync(); Assert.IsTrue(added, 商品应能成功加入购物车); } }实操要点与心得测试框架集成模板示例使用了NUnit但你也可以轻松换成MSTest或xUnit。关键是将浏览器的生命周期管理SetUp/TearDown与测试框架的钩子正确绑定。更高级的用法是创建一个自定义的BaseTest类所有测试类都继承它这样SetUp/TearDown逻辑只需写一次。流畅的API调用链通过让页面对象方法返回下一个页面的对象我们形成了homePage.SearchProductAsync().ClickFirstProductAsync().AddToCartAsync()这样的链式调用代码非常清晰反映了用户的真实操作流。断言与验证自动化不仅仅是操作更重要的是验证。除了使用测试框架的AssertPlaywright本身也提供了一些断言如await Expect(locator).ToBeVisibleAsync()。模板可以展示这两种风格的混合使用。5. 进阶配置与最佳实践模板提供了基础但要用于真实项目还需要考虑更多。5.1 配置文件与环境隔离appsettings.json是基础但实际开发中我们可能有开发、测试、生产等多套环境。模板可以扩展支持appsettings.Development.json,appsettings.Staging.json等。通过环境变量ASPNETCORE_ENVIRONMENT或DOTNET_ENVIRONMENT来切换。Configuration类需要相应修改以支持这种模式。public static class Configuration { public static PlaywrightConfig GetPlaywrightConfig() { var config new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile(appsettings.json, optional: false) .AddJsonFile($appsettings.{Environment.GetEnvironmentVariable(DOTNET_ENVIRONMENT) ?? Production}.json, optional: true) // 环境特定配置 .AddEnvironmentVariables() // 支持环境变量覆盖 .Build(); return config.GetSection(Playwright).GetPlaywrightConfig() ?? new PlaywrightConfig(); } }这样在CI/CD流水线中你可以通过设置环境变量来注入测试服务器的URL、认证密钥等敏感信息而无需硬编码在代码中。5.2 并行执行与上下文隔离要提高自动化套件的执行速度并行化是关键。Playwright的BrowserContext天生支持并行。在测试框架中如NUnit、xUnit你可以通过属性控制并行度。[Parallelizable(ParallelScope.All)] // NUnit中允许所有测试并行 public class ParallelTests { // 每个测试方法都会获得自己独立的BrowserContext和Page [Test] public async Task Test1() { // 使用独立的Fixture或SetUp创建上下文 } [Test] public async Task Test2() { // 另一个独立的上下文不会与Test1的Cookie等冲突 } }重要提示并行时确保测试之间没有共享状态依赖如操作同一个数据库记录。每个测试都应该是独立的。5.3 录制与调试追踪Tracing和视频Playwright的追踪功能是调试复杂问题的神器。模板可以集成一个简单的包装器在测试开始和结束时自动启动/停止追踪。public async Task RunWithTracing(FuncTask testAction, string traceName) { await _context!.Tracing.StartAsync(new TracingStartOptions { Screenshots true, Snapshots true, Sources true }); try { await testAction(); } finally { var tracePath Path.Combine(traces, ${traceName}.zip); await _context.Tracing.StopAsync(new TracingStopOptions { Path tracePath }); // 可以在这里记录日志追踪文件已保存至 {tracePath} } }当测试失败时你可以打开这个.zip文件使用playwright show-trace命令以可视化时间线的形式回放整个测试过程查看每一步的屏幕截图、DOM快照、网络请求和日志。5.4 与CI/CD集成模板项目可以轻松集成到GitHub Actions、Azure DevOps、Jenkins等CI/CD平台。关键步骤通常包括安装.NET SDK和依赖使用dotnet restore。安装Playwright浏览器运行playwright install或playwright install --with-depsLinux环境下可能需要安装系统依赖。执行测试运行dotnet test。可以通过--logger参数指定输出格式如trx用于Azure DevOpsjunit用于Jenkins。上传产物将测试报告、截图、追踪文件等作为构建产物上传便于后续查看。一个简单的GitHub Actions工作流示例.github/workflows/playwright.ymlname: Playwright Tests on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Setup .NET uses: actions/setup-dotnetv4 with: dotnet-version: 8.0.x - name: Install dependencies run: dotnet restore - name: Install Playwright Browsers run: pwsh ./bin/Debug/net8.0/playwright.ps1 install chromium - name: Run tests run: dotnet test --logger trx --results-directory TestResults - name: Upload test results uses: actions/upload-artifactv4 if: always() with: name: test-results path: TestResults6. 常见问题排查与实战技巧即使有了完善的模板在实际运行中还是会遇到各种问题。这里记录了一些高频问题和解决思路。6.1 元素定位失败这是最常见的问题。问题TimeoutException: Timeout 30000ms exceeded.排查检查选择器使用浏览器开发者工具检查元素是否真的存在以及你使用的选择器CSS/XPath是否唯一匹配。Playwright Test Runner 提供了一个非常棒的playwright codegen命令可以录制操作并生成选择器是初学者的好帮手。检查iframe目标元素是否在iframe内如果是你需要先定位到iframe然后使用FrameLocator。var frame Page.FrameLocator(iframe[namecontent]); var innerButton frame.GetByRole(AriaRole.Button, new PageGetByRoleOptions { Name 提交 }); await innerButton.ClickAsync();检查动态内容元素是否是AJAX加载的在操作前需要等待元素出现。优先使用Locator.WaitForAsync()而不是Task.Delay。检查页面状态是否发生了未预期的导航或弹窗导致元素不在当前上下文中可以增加Page.WaitForLoadStateAsync(LoadState.NetworkIdle)等待网络空闲。6.2 异步操作未完成问题点击按钮后脚本立即执行下一步但页面实际上还在处理如提交表单导致后续操作失败。解决在点击导航或触发异步操作的按钮后使用Page.WaitForURLAsync()或Page.WaitForNavigationAsync()等待导航完成。对于非导航的异步操作如显示一个Toast提示等待特定的UI元素出现。await SubmitButton.ClickAsync(); // 等待跳转到成功页面 await Page.WaitForURLAsync(**/success); // 或者等待成功提示出现 await SuccessToast.WaitForAsync();6.3 浏览器启动或安装问题问题PlaywrightException: Executable doesnt exist at ...解决确保项目成功构建触发了PlaywrightInstall目标。可以手动运行dotnet build或playwright install。检查网络浏览器下载可能被墙或缓慢。可以尝试设置环境变量PLAYWRIGHT_DOWNLOAD_HOST为国内镜像源或者使用离线包。在Linux无头服务器上可能需要安装额外的系统依赖。运行playwright install-deps可以尝试安装。6.4 性能优化复用浏览器实例如前所述通过BrowserFixture在测试套件级别复用浏览器而不是每个测试都启动关闭。使用无头模式在CI/CD环境中务必使用Headless true这能节省大量资源和时间。避免不必要的等待用智能等待WaitFor*替代固定休眠Task.Delay。禁用非必要资源如果不需要图片、样式、字体等来验证功能可以在创建上下文时拦截并中止这些请求大幅提升速度。await _context.RouteAsync(**/*.{png,jpg,jpeg,svg,css,woff2}, route route.AbortAsync());6.5 处理认证与状态对于需要登录的测试有几种模式每次测试都登录最干净但最慢。适合测试登录流程本身。前置登录复用状态在测试套件开始时登录一次将认证状态如Cookie、localStorage保存下来然后在每个测试的SetUp中创建一个新的上下文并加载这个状态。Playwright的BrowserContext.StorageStateAsync()和BrowserNewContextOptions.StorageState可以完美支持。// 登录并保存状态 var storageState await _context.StorageStateAsync(); File.WriteAllText(state.json, storageState); // 在新测试中加载状态 var newContext await _browser.NewContextAsync(new BrowserNewContextOptions { StorageStatePath state.json });这个“可直接运行的Playwright C#自动化模板”为你扫清了从零开始的大部分障碍。它不仅仅是一堆代码文件更是一套经过设计的、可扩展的实践方案。你可以直接以此为基础快速构建起属于自己的、稳定高效的Web自动化项目。记住好的工具是成功的一半而另一半则来自于你对业务逻辑的深刻理解和持续优化的脚本设计。