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

并行执行问题:Spring Cucumber注入PageObject时WebDriver会话管理

解决Cucumber并行测试中WebDriver会话隔离问题

问题根源

并行测试时线程间WebDriver实例共享,导致会话干扰,核心原因是WebDriver和PageObject的作用域未与Cucumber测试场景绑定,无法为每个场景提供独立实例。

解决方案步骤

1. 实现Cucumber场景级自定义作用域

创建Spring自定义作用域,将Bean生命周期绑定到Cucumber场景,利用ThreadLocal确保线程隔离(每个测试场景对应独立线程):

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.HashMap;
import java.util.Map;

public class CucumberScenarioScope implements Scope {
    private final ThreadLocal<Map<String, Object>> scenarioBeans = ThreadLocal.withInitial(HashMap::new);

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        return scenarioBeans.get().computeIfAbsent(name, k -> objectFactory.getObject());
    }

    @Override
    public Object remove(String name) {
        return scenarioBeans.get().remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {}

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }

    // 场景结束时清理当前线程的Bean实例
    public void clear() {
        scenarioBeans.get().clear();
        scenarioBeans.remove();
    }
}

2. 注册自定义作用域

在WebDriver配置类中注册上述场景作用域,让Spring识别并使用:

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import io.github.bonigarcia.wdm.WebDriverManager;

@Configuration
public class WebDriverConfig implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(org.springframework.beans.factory.support.BeanDefinitionRegistry registry) {}

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // 注册场景级作用域
        beanFactory.registerScope("cucumber-scenario", new CucumberScenarioScope());
    }

    @Bean
    @Scope("cucumber-scenario")
    @ConditionalOnProperty(name = "browser", havingValue = "chrome")
    public WebDriver chromeDriver(){
        ChromeOptions chromeOptions = new ChromeOptions();
        // 可选:添加无头模式、禁用GPU等并行友好配置
        // chromeOptions.addArguments("--headless=new", "--disable-gpu");
        WebDriverManager.chromedriver().setup();
        WebDriver driver = new ChromeDriver(chromeOptions);
        driver.manage().window().maximize();
        return driver;
    }
}

3. 调整PageObject的作用域

将PageObject的作用域改为cucumber-scenario,确保每个场景拥有独立的PageObject实例,同时修正重复注解问题:

import org.springframework.stereotype.Component;
import org.springframework.context.annotation.Scope;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.springframework.beans.factory.annotation.Value;

@Component
@Scope("cucumber-scenario")
public class Login extends BasePageObject {

    @FindBy(id = "username")
    private WebElement txt_username;
    @FindBy(id = "password")
    private WebElement txt_password;
    @FindBy(id = "login-btn-label")
    private WebElement btn_login;
    @Value("${internalURL}")
    private String URL;

    // 通过构造函数注入WebDriver,初始化页面对象
    public Login(WebDriver driver) {
        super(driver);
        PageFactory.initElements(driver, this);
    }

    public Login openLoginPage(String url) {
        this.driver.get(url != null ? url : this.URL);
        return this;
    }
}

4. 步骤定义类绑定场景作用域并清理资源

在步骤定义类中指定场景作用域,并在场景结束时销毁WebDriver、清理作用域资源:

import org.springframework.boot.test.context.SpringBootTest;
import io.cucumber.java.AfterScenario;
import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.openqa.selenium.WebDriver;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.ApplicationContext;

@SpringBootTest
@CucumberContextConfiguration
@Scope("cucumber-scenario")
public class CPLSteps {

    @Autowired
    private WebDriver driver;

    @Autowired
    private Login loginPage;

    @Autowired
    private ApplicationContext applicationContext;

    @AfterScenario
    public void tearDownScenario() {
        if (driver != null) {
            driver.quit();
            // 清理当前场景的Bean实例
            CucumberScenarioScope scope = (CucumberScenarioScope) ((ConfigurableApplicationContext) applicationContext)
                    .getBeanFactory().getRegisteredScope("cucumber-scenario");
            scope.clear();
        }
    }
}

5. 确认TestNG并行配置

确保TestNG以方法级并行运行(每个Cucumber场景对应一个Test方法),可在testng.xml中配置:

<suite name="Cucumber Parallel Suite" parallel="methods" thread-count="5">
    <test name="Feature Tests">
        <classes>
            <class name="com.intact.contactpl.web.ui.bdd.FeatureParallelTest"/>
        </classes>
    </test>
</suite>

关键说明

  • 自定义cucumber-scenario作用域通过ThreadLocal实现线程隔离,确保每个测试场景(线程)拥有独立的WebDriver和PageObject实例。
  • 必须在场景结束时手动销毁WebDriver并清理作用域资源,避免内存泄漏。
  • PageObject通过构造函数注入WebDriver,确保与当前场景的WebDriver实例绑定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:26:00