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

Specflow集成Playwright无法启动浏览器,求排查原因

问题:SpecFlow集成Playwright时出现NullReferenceException

问题现象

调试时访问this.Page抛出System.NullReferenceException异常,已尝试切换WebKit、Firefox浏览器,问题仍存在。此前基于NUnit的C#方案可正常运行,改用SpecFlow后出现该问题。

相关代码

Driver类代码

[Binding]
public class Driver : IDisposable
{
 private readonly Task<IPage> page;  //类似driver声明
 private IBrowser? browser;

 public Driver()
  {
    page = InitializePlaywright();
  }

  public IPage Page => page.Result;    
  private async Task<IPage> InitializePlaywright()
  {
    var playwright = await Playwright.CreateAsync();
    browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
    {
      Headless = false,
      Timeout = 30000
    });     
    var context = await browser.NewContextAsync();
    var page =  await context.NewPageAsync();
    return page;
  }
  public void Dispose()  
  {
    browser?.CloseAsync();
  }
}

步骤定义(StepDef)代码

[Binding]
internal class HomeSteps
{
  private readonly Driver driver;
  private readonly HomePage homePage;

  public HomeSteps(Driver driver)
  {
    this.driver = driver;
    homePage = new HomePage(driver.Page);
  }
  [Given(@"User logs into HomePage (.*)")]
  public async Task GivenUserLogsIntoHomePage(string user)
  {
    await homePage.LoginToHomePage(user);
  }
}

核心原因

Driver类构造函数中直接调用异步方法InitializePlaywright(),并将返回的Task<IPage>赋值给字段,访问Page属性时直接取page.Result会导致同步阻塞等待异步任务,而SpecFlow的依赖注入机制在实例化Driver时,易触发死锁或异步任务未完成就访问结果,最终导致page.Result抛出异常。此外,Dispose()方法调用CloseAsync()未等待,会导致浏览器资源无法正确释放。

解决步骤

  • 重构Driver类,改用异步初始化逻辑
    利用SpecFlow的[BeforeScenario]标记异步初始化方法,避免构造函数中处理异步任务导致的阻塞问题:

    [Binding(Scope = BindingScope.Scenario)]
    public class Driver : IDisposable
    {
        private IPage? _page;
        private IBrowser? _browser;
        private IPlaywright? _playwright;
    
        [BeforeScenario]
        public async Task InitializeAsync()
        {
            _playwright = await Playwright.CreateAsync();
            _browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
            {
                Headless = false,
                Timeout = 30000
            });
            var context = await _browser.NewContextAsync();
            _page = await context.NewPageAsync();
        }
    
        public IPage Page => _page ?? throw new InvalidOperationException("Driver未完成初始化");
    
        public async ValueTask DisposeAsync()
        {
            if (_browser != null)
            {
                await _browser.CloseAsync();
                await _browser.DisposeAsync();
            }
            _playwright?.Dispose();
        }
    
        public void Dispose()
        {
            DisposeAsync().AsTask().Wait();
        }
    }
    
  • 调整StepDef的初始化时机
    不在HomeSteps构造函数中直接初始化HomePage,改用[BeforeScenario]确保Driver的Page就绪后再初始化:

    [Binding]
    internal class HomeSteps
    {
        private readonly Driver _driver;
        private HomePage? _homePage;
    
        public HomeSteps(Driver driver)
        {
            _driver = driver;
        }
    
        [BeforeScenario]
        public void SetupHomePage()
        {
            _homePage = new HomePage(_driver.Page);
        }
    
        [Given(@"User logs into HomePage (.*)")]
        public async Task GivenUserLogsIntoHomePage(string user)
        {
            if (_homePage == null)
                throw new InvalidOperationException("HomePage未完成初始化");
            
            await _homePage.LoginToHomePage(user);
        }
    }
    
  • 设置Driver的生命周期为Scenario级别
    通过[Binding(Scope = BindingScope.Scenario)]标记Driver类,确保每个测试场景拥有独立的浏览器实例,避免跨场景资源冲突。


内容的提问来源于stack exchange,提问作者Abhed Patil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 08:19:53