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

Docker环境下结合Selenium Grid实现JUnit5版Cucumber测试分布式分发咨询

实现Cucumber JUnit5与Selenium Grid Docker容器的绑定调度

基础配置对齐

你已经掌握的cucumber.execution.parallel.enabled=true是并行调度的基础,只需要根据目标的并行粒度,补充对应的配置到junit-platform.properties文件中,即可让Cucumber JUnit5的调度单元匹配到你需要的拆分粒度,不需要额外引入TestNG相关的调度逻辑。

核心逻辑:端口分配与WebDriver初始化的隔离机制

TestNG场景下的端口传参逻辑,在Cucumber JUnit5生态下可以通过线程隔离上下文 + 线程安全端口池 + JUnit5扩展钩子复刻,完全避免多并行任务下的端口串用、WebDriver会话不匹配的问题:

  1. 搭建线程隔离的端口存储上下文,因为JUnit5的并行调度单元(不管是特性还是场景)都会运行在独立线程上,天然满足隔离要求:
    public class GridPortContext {
        // 线程隔离存储当前执行单元绑定的Grid节点端口
        private static final ThreadLocal<Integer> CURRENT_BOUND_PORT = new ThreadLocal<>();
        public static void setPort(int port) { CURRENT_BOUND_PORT.set(port); }
        public static int getPort() { return CURRENT_BOUND_PORT.get(); }
        public static void clear() { CURRENT_BOUND_PORT.remove(); }
    }
    
  2. 搭建线程安全的端口池,提前把docker-compose动态映射的所有Selenium节点端口存入队列,保证每个并行执行单元只会拿到一个独占的、未被其他任务占用的端口:
    // 端口池配置,从docker-compose动态读取映射端口后初始化
    public class GridPortPool {
        private static final Queue<Integer> AVAILABLE_PORTS = new ConcurrentLinkedQueue<>();
        public static void initPorts(List<Integer> mappedPorts) {
            AVAILABLE_PORTS.clear();
            AVAILABLE_PORTS.addAll(mappedPorts);
        }
        public static Integer allocatePort() {
            return AVAILABLE_PORTS.poll();
        }
        public static void releasePort(Integer port) {
            AVAILABLE_PORTS.offer(port);
        }
    }
    
  3. 实现JUnit5扩展钩子,在每个并行调度单元的生命周期内完成端口分配与释放,替换TestNG的参数注入逻辑:
    public class GridPortLifecycleExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
        @Override
        public void beforeTestExecution(ExtensionContext context) {
            // 执行前从端口池分配独占端口,存入线程上下文
            Integer allocatedPort = GridPortPool.allocatePort();
            GridPortContext.setPort(allocatedPort);
        }
        @Override
        public void afterTestExecution(ExtensionContext context) {
            // 执行完成后释放端口回到池子,清理线程上下文
            Integer usedPort = GridPortContext.getPort();
            GridPortPool.releasePort(usedPort);
            GridPortContext.clear();
        }
    }
    
  4. 按照分配的端口初始化RemoteWebDriver,完全复刻原TestNG案例的端口驱动逻辑:
    public class SeleniumDriverFactory {
        public static RemoteWebDriver createDriver() {
            int boundPort = GridPortContext.getPort();
            // 拼接当前绑定端口的Grid节点访问地址
            URL gridEndpoint = new URL("http://localhost:" + boundPort + "/wd/hub");
            ChromeOptions chromeOptions = new ChromeOptions();
            // 补充浏览器的无头、沙箱关闭等适合容器运行的参数
            chromeOptions.addArguments("--headless=new");
            chromeOptions.addArguments("--no-sandbox");
            return new RemoteWebDriver(gridEndpoint, chromeOptions);
        }
    }
    

按需求匹配调度粒度,实现两个核心目标

目标1:每个特性文件匹配独立容器执行

需要调整junit-platform.properties的配置,让调度单元绑定为整个特性文件,而非单个场景:

# 开启并行执行
cucumber.execution.parallel.enabled=true
# 固定并行数,和你拉起的Selenium容器数完全对齐
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=3
# 让整个特性文件内的所有场景共享同一个执行线程,绑定同一个端口
cucumber.execution.execution.mode=same_thread

同时调整生命周期钩子的触发时机,把端口分配的逻辑从单场景级移到特性级:通过Cucumber的BeforeAll钩子在特性启动时分配端口,在AfterAll钩子中释放端口,保证整个特性的所有场景都使用同一个Grid容器。

目标2:每个测试场景单独分配独立容器执行

调整junit-platform.properties的配置,让调度单元拆分为单个场景:

# 开启并行执行
cucumber.execution.parallel.enabled=true
# 动态调整并行度,匹配端口池的可用容量
cucumber.execution.parallel.config.strategy=dynamic
cucumber.execution.parallel.config.dynamic.factor=1
# 取消特性文件的线程绑定,让每个场景独立运行在单独线程
cucumber.execution.execution.mode=same_thread_per_scenario

此时保持之前的单场景级端口分配钩子即可,每个场景执行前会自动从端口池拿一个独占端口,对应独立的Selenium容器,执行完成后自动释放端口供其他场景使用。

Azure Pipeline环境适配注意事项

  • 用docker-compose拉起Selenium节点时,不要硬编码固定端口映射,改用动态端口范围配置,在管道任务启动时通过docker inspect读取所有节点的宿主机映射端口,动态注入到GridPortPool中,避免硬编码端口和容器实际映射不匹配的问题。
  • 给每个Selenium节点配置单会话上限(设置环境变量SE_NODE_MAX_SESSIONS=1),保证每个容器同一时间只承接一个测试任务,和端口独占的逻辑完全匹配。
  • 不要跨线程传递WebDriver实例,保持每个线程的WebDriver和绑定的端口一一对应,避免出现会话串接、操作不对应的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 01:24:00