运行 使用 cucumber-junit-platform-engine 和 Selenium WebDriver 的测试打开了太多线程

Running tests with cucumber-junit-platform-engine and Selenium WebDriver opens too many threads

我尝试使用 cucumber-junit-platform-engine 将现有的 Maven 项目配置为 运行。

我使用 this 回购作为灵感。

我添加了所需的 Maven 依赖项,如在使用 spring-boot-starter-parent 版本 2.4.5 和 cucumber-jvm 版本 6.10.4 的链接项目中一样。

我将 junit-platform 属性设置如下:

cucumber.execution.parallel.enabled=true
cucumber.execution.parallel.config.strategy=fixed
cucumber.execution.parallel.config.fixed.parallelism=4

在 运行ner class 和 @SpringBootTest 中使用注释 @Cucumber 用于 classes 和步骤定义。

创建并行线程似乎工作正常,但问题是它在开始时创建所有线程并打开与场景数量一样多的浏览器 windows(驱动程序)(例如 51 而不是4).

我正在使用 CucumberHooks class 在场景前后添加逻辑,我猜它会干扰 运行ner 因为我正在使用注释:

import java.util.List;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventHandler;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestRunStarted;
import io.github.bonigarcia.wdm.WebDriverManager;

public class CucumberHooks implements ConcurrentEventListener {

@Autowired
private ScenarioContext scenarioContext;

@Before
public void beforeScenario(Scenario scenario) {
    scenarioContext.getNewDriverInstance();
    scenarioContext.setScenario(scenario);
    LOGGER.info("Driver initialized for scenario - {}", scenario.getName());
    ....
    <some business logic here>
    ....
}

@After
public void afterScenario() {
    Scenario scenario = scenarioContext.getScenario();
    WebDriver driver = scenarioContext.getDriver();

    takeErrorScreenshot(scenario, driver);
    LOGGER.info("Driver will close for scenario - {}", scenario.getName());

    driver.quit();
}

private void takeErrorScreenshot(Scenario scenario, WebDriver driver) {
    if (scenario.isFailed()) {
        final byte[] screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
        scenario.attach(screenshot, "image/png", "Failure");
    }
}

@Override
public void setEventPublisher(EventPublisher eventPublisher) {
    eventPublisher.registerHandlerFor(TestRunStarted.class, beforeAll);
}

private EventHandler<TestRunStarted> beforeAll = event -> {
    // something that needs doing before everything
    .....<some business logic here>....
    WebDriverManager.getInstance(DriverManagerType.CHROME).setup();
};

}

我尝试用 org.junit.jupiter.api 中的 @BeforeEach 替换 io.cucumber.java 中的 @Before 标签,但它不起作用。

我该如何解决这个问题?

事实证明,parallism 主要是一个建议。 Cucumber 使用 JUnit5s ForkJoinPoolHierarchicalTestExecutorService 构造一个 ForkJoinPool.

来自 ForkJoinPool 上的文档:

For applications that require separate or custom pools, a ForkJoinPool may be constructed with a given target parallelism level; by default, equal to the number of available processors. The pool attempts to maintain enough active (or available) threads by dynamically adding, suspending, or resuming internal worker threads, even if some tasks are stalled waiting to join others. However, no such adjustments are guaranteed in the face of blocked I/O or other unmanaged synchronization.

因此在 ForkJoinPool 中,当一个线程阻塞时,例如因为它开始与 Web 驱动程序进行异步通信,可能会启动另一个线程来保持并行性。

由于所有线程都在等待,更多的线程被添加到池中并且启动了更多的网络驱动程序。

这意味着您必须自己执行此操作,而不是依赖 ForkJoinPool 来限制网络驱动程序的数量。您可以使用像 Apache Commons Pool 这样的库或使用计数信号量实现基本池。

@Component
@ScenarioScope
public class ScenarioContext {

    private static final int MAX_CONCURRENT_WEB_DRIVERS = 1;
    private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_WEB_DRIVERS, true);

    private WebDriver driver;

    public WebDriver getDriver() {
        if (driver != null) {
            return driver;
        }

        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        try {
            driver = CustomChromeDriver.getInstance();
        } catch (Throwable t){
            semaphore.release();
            throw t;
        }
        return driver;
    }

    public void retireDriver() {
        if (driver == null) {
            return;
        }

        try {
            driver.quit();
        } finally {
            driver = null;
            semaphore.release();
        }
    }
}