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

并行运行时使用Scenario.attach添加图片触发Cucumber异常问题排查

Cucumber并行运行时Scenario.attach报错原因分析与解决方案

原因分析

  1. 线程上下文不匹配:并行运行时,Cucumber的Scenario实例是与当前执行步骤的线程绑定的。如果在步骤执行完成后(比如异步任务、跨线程调用),或者通过全局静态变量复用Scenario对象,会导致调用attach时,当前线程没有活跃的测试步骤,触发IllegalStateException。串行运行时单线程环境下,Scenario始终绑定当前步骤,不会出现这个问题。
  2. 异步操作违规:如果截图或attach操作是异步执行的,异步线程不属于当前步骤的生命周期,调用Scenario.attach时会检测不到活跃步骤。
  3. 资源共享冲突:若全局存储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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 19:37:42