Playwright C# SpecFlow:为每个场景配置独立浏览器的方案咨询
Playwright+C# 测试框架配置方案(解决"Page already closed"问题)
核心设计思路
你遇到的Page already closed问题,本质是静态实例共享导致的资源冲突——前一个场景关闭了浏览器/页面,后一个场景还在复用已释放的静态对象。下面是完全规避静态问题的框架实现方案:
1. Driver.cs(非静态资源管理类)
负责Playwright、Browser、Page的生命周期,全程用实例化对象而非静态成员:
using Microsoft.Playwright; public class Driver { public IPlaywright Playwright { get; private set; } public IBrowser Browser { get; private set; } public IPage Page { get; private set; } // 初始化浏览器和页面 public async Task InitializeAsync() { Playwright = await Playwright.CreateAsync(); Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = false, // 根据测试需求调整 SlowMo = 300 // 可选:慢动作便于调试 }); Page = await Browser.NewPageAsync(); } // 清理资源 public async Task CleanupAsync() { await Page.CloseAsync(); await Browser.CloseAsync(); Playwright.Dispose(); } }
2. Hooks类(场景级生命周期控制)
以SpecFlow为例,通过Hooks确保每个场景拥有独立的浏览器实例,场景开始初始化资源,结束后彻底清理:
using TechTalk.SpecFlow; [Binding] public class Hooks { private readonly ScenarioContext _scenarioContext; private Driver _driver; public Hooks(ScenarioContext scenarioContext) { _scenarioContext = scenarioContext; } [BeforeScenario] public async Task BeforeScenario() { // 为当前场景创建专属Driver实例 _driver = new Driver(); await _driver.InitializeAsync(); // 将Driver存入场景上下文,供步骤和页面类调用 _scenarioContext.Set(_driver, "CurrentDriver"); } [AfterScenario] public async Task AfterScenario() { // 取出当前场景的Driver并清理 var driver = _scenarioContext.Get<Driver>("CurrentDriver"); await driver.CleanupAsync(); } }
3. 页面类接收Driver实例
页面类通过构造函数注入当前场景的Driver,确保使用的是专属页面对象:
public class LoginPage { private readonly IPage _page; // 注入Driver,获取当前场景的Page public LoginPage(Driver driver) { _page = driver.Page; } public async Task EnterUsername(string username) { await _page.FillAsync("#username-input", username); } public async Task EnterPassword(string password) { await _page.FillAsync("#password-input", password); } public async Task ClickLoginBtn() { await _page.ClickAsync("#login-btn"); } }
4. 步骤定义中传递资源
从场景上下文取出Driver,初始化页面类,全程使用当前场景的专属资源:
using TechTalk.SpecFlow; [Binding] public class LoginSteps { private readonly ScenarioContext _scenarioContext; private LoginPage _loginPage; public LoginSteps(ScenarioContext scenarioContext) { _scenarioContext = scenarioContext; } [Given(@"我打开登录页面")] public async Task GivenIOpenTheLoginPage() { var driver = _scenarioContext.Get<Driver>("CurrentDriver"); _loginPage = new LoginPage(driver); await driver.Page.GotoAsync("https://your-domain.com/login"); } [When(@"输入用户名 ""(.*)""和密码 ""(.*)""")] public async Task WhenEnterUsernameAndPassword(string username, string password) { await _loginPage.EnterUsername(username); await _loginPage.EnterPassword(password); } [When(@"点击登录按钮")] public async Task WhenClickLoginButton() { await _loginPage.ClickLoginBtn(); } }
问题根源说明
之前的Page already closed错误,完全是因为静态Driver/Page被多个场景共享:前一个场景执行完后关闭了页面,后一个场景还在调用已被释放的静态对象。本方案通过场景级实例隔离+上下文传递彻底解决了这个问题,每个场景的浏览器、页面都是独立的,不会互相干扰。
内容的提问来源于stack exchange,提问作者Shiva Subramaniam
相关产品推荐
相关产品推荐

