并行运行时使用Scenario.attach添加图片触发Cucumber异常问题排查
Cucumber并行运行时Scenario.attach报错原因分析与解决方案
原因分析
- 线程上下文不匹配:并行运行时,Cucumber的
Scenario实例是与当前执行步骤的线程绑定的。如果在步骤执行完成后(比如异步任务、跨线程调用),或者通过全局静态变量复用Scenario对象,会导致调用attach时,当前线程没有活跃的测试步骤,触发IllegalStateException。串行运行时单线程环境下,Scenario始终绑定当前步骤,不会出现这个问题。 - 异步操作违规:如果截图或
attach操作是异步执行的,异步线程不属于当前步骤的生命周期,调用Scenario.attach时会检测不到活跃步骤。 - 资源共享冲突:若全局存储
Scenario或WebDriver实例,并行时多个线程会互相覆盖这些对象,导致调用attach时对应的场景已执行完毕。
解决方案
1. 用ThreadLocal实现Scenario线程安全存储
不要用静态变量存储Scenario,改用ThreadLocal绑定当前线程的实例:
public class ScenarioContext { private static final ThreadLocal<Scenario> SCENARIO_THREAD_LOCAL = new ThreadLocal<>(); public static void setScenario(Scenario scenario) { SCENARIO_THREAD_LOCAL.set(scenario); } public static Scenario getScenario() { return SCENARIO_THREAD_LOCAL.get(); } public static void clearScenario() { SCENARIO_THREAD_LOCAL.remove(); } }
在Before钩子中初始化绑定:
@Before public void setUp(Scenario scenario) { ScenarioContext.setScenario(scenario); // 其他初始化逻辑 }
在After钩子中清理:
@After public void tearDown() { ScenarioContext.clearScenario(); }
调用attach时从ThreadLocal获取实例:
@Then("错误消息显示") public void error_message_displayed() { // 同步截取全屏 byte[] img = Shutterbug.shootPage(DriverContext.getDriver(), ScrollStrategy.WHOLE_PAGE).getBytes(); Scenario scenario = ScenarioContext.getScenario(); if (scenario != null) { scenario.attach(img, "image/png", "错误消息截图"); } }
2. 确保attach操作在步骤生命周期内执行
禁止在异步任务、线程池或步骤方法执行完成后调用attach,所有截图和attach操作必须在当前步骤的方法体内同步完成,或者在Before/After钩子中执行(钩子与当前场景线程绑定)。
错误示例(异步调用):
@Then("错误消息显示") public void error_message_displayed() { CompletableFuture.runAsync(() -> { byte[] img = Shutterbug.shootPage(driver).getBytes(); scenario.attach(img, "image/png", ""); // 跨线程调用,步骤已结束 }); }
正确示例(同步执行):
@Then("错误消息显示") public void error_message_displayed() { byte[] img = Shutterbug.shootPage(DriverContext.getDriver(), ScrollStrategy.WHOLE_PAGE).getBytes(); Scenario scenario = ScenarioContext.getScenario(); scenario.attach(img, "image/png", "错误消息截图"); }
3. 配置正确的并行运行策略
确保TestNG与Cucumber的并行配置匹配,以场景为单位并行,避免类级别并行导致上下文混乱:
- TestNG配置(testng.xml):
<suite name="CucumberSuite" parallel="methods" thread-count="5"> <test name="CucumberTests"> <classes> <class name="io.cucumber.testng.TestNGCucumberRunner"/> </classes> </test> </suite>
- Cucumber配置(cucumber.properties):
cucumber.execution.parallel.enabled=true cucumber.execution.parallel.config.strategy=fixed cucumber.execution.parallel.config.fixed.parallelism=5
4. 用ThreadLocal隔离WebDriver实例
并行运行时,每个线程必须拥有独立的WebDriver实例,避免线程冲突:
public class DriverContext { private static final ThreadLocal<WebDriver> DRIVER_THREAD_LOCAL = new ThreadLocal<>(); public static WebDriver getDriver() { if (DRIVER_THREAD_LOCAL.get() == null) { ChromeOptions options = new ChromeOptions(); DRIVER_THREAD_LOCAL.set(new ChromeDriver(options)); } return DRIVER_THREAD_LOCAL.get(); } public static void quitDriver() { WebDriver driver = DRIVER_THREAD_LOCAL.get(); if (driver != null) { driver.quit(); DRIVER_THREAD_LOCAL.remove(); } } }
在After钩子中退出Driver:
@After public void tearDown() { DriverContext.quitDriver(); ScenarioContext.clearScenario(); }
内容的提问来源于stack exchange,提问作者user26414346
相关产品推荐
相关产品推荐

