并行执行问题: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
相关产品推荐
相关产品推荐

