Java-Maven Cucumber并行测试:首个浏览器空白页问题排查
我有两个独立的Feature文件,用Maven命令执行并行测试时,会打开两个浏览器,但第一个浏览器显示空白页(无URL地址)且无任何操作,第二个浏览器正常启动并成功执行测试,导致第一个Feature文件测试失败、第二个通过。
提示:如果从maven-surefire-plugin中移除<parallel>classesAndMethods</parallel>和<useUnlimitedThreads>true</useUnlimitedThreads>两行,两个Feature文件会串行执行且全部成功,但不符合并行需求。
请问当前配置是否存在问题?
提供的代码内容
第一个Feature文件
Feature: test of a webpage one @desktop Scenario: Test landing page one Given Customer clicks something one When Customer clicks something two Then Customer clicks something three
第二个Feature文件
Feature: test of a webpage two @desktop Scenario: Test landing page two Given Customer clicks something one When Customer clicks something two Then Customer clicks something three
POM.xml中maven-surefire-plugin配置
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <includes> <include>**/TestRunner.java</include> </includes> <parallel>classesAndMethods</parallel> <useUnlimitedThreads>true</useUnlimitedThreads> </configuration> </plugin>
TestRunner类
import io.cucumber.junit.Cucumber; import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith(Cucumber.class) @CucumberOptions(features="src/test/java/features", plugin = {"pretty", "json:target/jsonReports/cucumber-report.json", "html:target/jsonReports/cucumber-report.html"}, glue= {"stepDefs"}, ) public class TestRunner { }
当前配置确实存在问题,核心原因是JUnit+Cucumber的组合与Surefire插件的并行策略不兼容,具体问题点如下:
并行策略不匹配
Cucumber的TestRunner本质是一个JUnit入口类,本身没有可并行执行的测试方法——它是通过加载Feature文件来运行场景的。Surefire的classesAndMethods策略会尝试并行执行TestRunner内的"方法",导致TestRunner被重复实例化,进而引发WebDriver实例冲突(比如两个线程共用未初始化的驱动,或初始化逻辑未做线程隔离)。报告文件冲突
两个并行线程同时写入同一个JSON/HTML报告文件,会导致文件损坏或内容覆盖,间接影响测试执行稳定性。无限制线程的风险
<useUnlimitedThreads>true</useUnlimitedThreads>会让线程数等于CPU核心数,过多WebDriver实例会消耗大量系统资源,加剧测试不稳定。
修复方案
方案1:改用Cucumber原生并行支持(推荐)
Cucumber本身支持场景/Feature级别的并行执行,无需依赖Surefire的并行配置,步骤如下:
更新TestRunner配置(Cucumber 6+版本)
改用JUnit Platform Suite注解,配置原生并行:import org.junit.platform.suite.api.ConfigurationParameter; import org.junit.platform.suite.api.IncludeEngines; import org.junit.platform.suite.api.SelectPackages; import org.junit.platform.suite.api.Suite; import static io.cucumber.junit.platform.engine.Constants.PARALLEL_CONFIG_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.THREAD_COUNT_CONFIG_PROPERTY_NAME; import static io.cucumber.junit.platform.engine.Constants.FEATURES_PROPERTY_NAME; @Suite @IncludeEngines("cucumber") @SelectPackages("stepDefs") @ConfigurationParameter(key = FEATURES_PROPERTY_NAME, value = "src/test/java/features") @ConfigurationParameter(key = PARALLEL_CONFIG_PROPERTY_NAME, value = "true") @ConfigurationParameter(key = THREAD_COUNT_CONFIG_PROPERTY_NAME, value = "2") // 按需调整线程数 public class TestRunner { }调整Surefire配置
移除并行相关配置,只保留TestRunner的包含规则:<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <includes> <include>**/TestRunner.java</include> </includes> </configuration> </plugin>
方案2:调整Surefire并行策略(适配性差,不推荐)
若坚持用Surefire并行,需将策略改为classes,并为每个Feature创建独立的TestRunner:
- 为第一个Feature创建
TestRunnerOne.java,指定features="src/test/java/features/feature1.feature" - 为第二个Feature创建
TestRunnerTwo.java,指定features="src/test/java/features/feature2.feature" - 修改Surefire配置:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <includes> <include>**/TestRunner*.java</include> </includes> <parallel>classes</parallel> <threadCount>2</threadCount> <!-- 禁用无限制线程,指定固定数量 --> </configuration> </plugin>
关键补充:WebDriver线程隔离
无论采用哪种并行方案,必须确保每个线程拥有独立的WebDriver实例,可通过ThreadLocal实现:
public class DriverFactory { 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(); } } }
在Step Definition中使用DriverFactory.getDriver()获取实例,并在场景结束后调用quitDriver()清理资源。
内容的提问来源于stack exchange,提问作者Word Place

