Playwright+C#并行测试报错:InvalidOperationException与TargetClosedException
问题分析与解决方案
核心问题根源
你的测试并行执行失败主要由两个原因导致:
- 异步代码错误使用:直接调用
.Result阻塞异步方法,破坏异步执行上下文,在并行场景下引发资源竞争和时序混乱。 - 资源生命周期管理不当:页面/上下文的创建、销毁逻辑存在竞态条件,加上可能的非线程安全集合操作,导致TearDown阶段枚举集合时出错,以及截图时目标已关闭。
具体修复步骤
1. 修复异步代码的阻塞问题
你的代码中使用.Result直接获取异步操作结果,会导致线程阻塞,在并行测试时极易引发异步操作未完成就继续执行后续代码的问题。将所有异步调用改为await:
[Test] [AllureStep] public async Task UserNameEditable() { // 改用await创建上下文和页面,避免阻塞线程 var context = await Browser.NewContextAsync(new CustomBrowserNewContextOptions().Create()); var page = await context.NewPageAsync(); try { LoginPage loginPage = new LoginPage(page); await loginPage.VisitPage(_baseUrl); var userNameField = loginPage.GetUserNameLocator(); await Assertions.Expect(userNameField).ToBeEditableAsync(); await AllureScreenshot.TakeAllureScreenshot( page, "Username field is editable", Status.passed ); } catch (Exception ex) { await AllureScreenshot.TakeAllureScreenshot( page, "Username editable test failed", Status.failed ); Assert.Fail($"Username field on login page is not editable : {ex.Message}"); } finally { // 用finally确保无论成功失败都关闭页面和上下文,避免资源泄漏 await page.CloseAsync(); await context.CloseAsync(); } }
2. 确保资源的线程安全与生命周期
- 每个测试用例应独立创建**浏览器上下文(Context)**和页面,完全隔离测试资源;
- 使用
finally块统一处理资源关闭,避免异常场景下资源未释放; - 如果
BaseTestSetup中维护了测试资源集合(如跟踪创建的上下文),必须用线程安全集合(如ConcurrentBag<T>)代替普通List<T>,防止并行修改时引发枚举异常。
3. 修复TearDown阶段的非线程安全问题
第一个异常出现在BrowserTest.BrowserTearDown()的集合枚举中,说明Playwright.NUnit内部的资源跟踪集合被并行测试修改:
- 检查
BaseTestSetup是否重写了TearDown逻辑,若有则确保所有集合操作线程安全; - 若继承自
BrowserTest,不要自定义可能干扰资源跟踪的逻辑,让Playwright自行管理资源生命周期。
4. 验证Playwright并行配置
[Parallelizable(ParallelScope.All)]的设置没问题,但要确保:
- 测试类级别仅共享
Browser实例(Playwright的IBrowser本身是线程安全的); - 所有测试方法的上下文、页面等资源完全独立,不跨测试共享。
额外注意事项
- 若
JsonDataManager.Instance是单例,需确保其内部操作线程安全,避免并行测试时的状态冲突; - 截图操作必须在页面关闭前完成,
finally块的关闭逻辑能保证这一点。
内容的提问来源于stack exchange,提问作者Kedar Ghadge
相关产品推荐
相关产品推荐

