You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 05:41:10