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

TestNG并发执行时WebDriver与FluentWait跨@Test方法为空问题

问题描述

我是Selenium WebDriver和TestNG的新手,当前开发的项目中存在两个Java类并发执行的场景。遇到的问题是:为每个类初始化的WebDriver和FluentWait无法在@Test方法间保持存活,触发如下错误:

java.lang.NullPointerException: Cannot invoke "org.openqa.selenium.WebDriver.navigate()" because the return value of "java.lang.ThreadLocal.get()" is null

相关代码

baseTest类

public class baseTest {

    protected static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
    protected static ThreadLocal<FluentWait<WebDriver>> wait = new ThreadLocal<>();
    protected SoftAssert softAssert;

    @BeforeSuite
    public void setUpSuite() {
        System.out.println("Executing method - setUpSuite");
    }

    @AfterSuite
    public void tearDownSuite() {
        System.out.println("Executing method - tearDownSuite");
        // Cleanup
        if (driver.get() != null) {
            driver.get().quit();
            driver.remove();
        }
        if (wait.get() != null) {
            wait.remove();
        }
    }

    @BeforeClass
    public void setUpClass() {
        System.out.println("Executing method - setUpClass");

        configProp config = new configProp();
        driver.set(config.initializeDriver());
        wait.set(config.initializeFluentWait(driver.get())); // Initialize FluentWait before the class

        // Perform login
        config.login(driver.get());
        System.out.println("Performed login");

        // Call the testPostmanAuth class
        System.out.println("Calling PostmanAuth");

        TestPostmanAuth postmanAuth = new TestPostmanAuth();
        try {
            postmanAuth.testAuthorizationLoginOnPostman();
        } catch (IOException | URISyntaxException e) {
            e.printStackTrace();
        }
    }

    @AfterClass
    public void tearDownClass() {
        System.out.println("Executing method - tearDownClass");

        // Cleanup for the current class
        if (driver.get() != null) {
            driver.get().quit();
            driver.remove();
        }
        if (wait.get() != null) {
            wait.remove();
        }
    }

    
    @BeforeMethod
    public void beforeMethod() {
        System.out.println("Executing method - beforeMethod");
        softAssert = new SoftAssert(); // Initialise SoftAssert before each test method

        // Check if driver and wait are initialised
        if (driver.get() == null || wait.get() == null) {
            throw new RuntimeException("WebDriver or FluentWait instance is not initialized.");
        }
    }

    @AfterMethod
    public void afterMethod() {
        System.out.println("Executing method - afterMethod");
        softAssert.assertAll(); // Ensure assertAll is called after each test method

    }

    // Getters for driver and wait
    public WebDriver getDriver() {
        return driver.get();
    }

    public FluentWait<WebDriver> getWait() {
        return wait.get();
    }
}

configProp类

public class configProp {
 
    public WebDriver initializeDriver() {
        System.setProperty("webdriver.gecko.driver",
                "URLGoesHere");
 
        FirefoxOptions options = new FirefoxOptions();
        options.setCapability("webSocketUrl", true); // Enable WebDriver BiDi
 
        WebDriver driver = new FirefoxDriver(options);
        driver.manage().window().maximize();
        return driver;
    }
 
    public FluentWait<WebDriver> initializeFluentWait(WebDriver driver) {
        // Initialise FluentWait
        FluentWait<WebDriver> wait = new FluentWait<>(driver).withTimeout(Duration.ofSeconds(20))
                .pollingEvery(Duration.ofMillis(500)).ignoring(NoSuchElementException.class);
        return wait;
    }
}

调用navigate的方法

public void switchToIframe() {
        WebElement iframe = wait.get().until(ExpectedConditions.visibilityOfElementLocated(By.tagName("iframe")));
        driver.get().switchTo().frame(iframe);
    }
    
    @Test(priority = 3, dependsOnMethods = {"nextRequestAssignNArrive" }, description = "Refreshes the page so processing can be done")
    public void refresh() throws InterruptedException {
        System.out.println("\nRefreshing the page after some time...");

        // Adding a sleep for 30 seconds before refreshing to make sure processing has
        // complete and incident is In Progress
        Thread.sleep(30 * 1000);

        // Refresh the page
        driver.get().navigate().refresh();

        System.out.println("Page refreshed...");

        Thread.sleep(10 * 1000); // just waiting for refresh to be done

        // make sure to switch to iframe after refresh
        switchToIframe();
    }

完整错误信息

checkIncident
java.lang.RuntimeException: WebDriver or FluentWait instance is not initialized.
    at Automation.baseTest.beforeMethod(baseTest.java:81)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1575)
... Removed 15 stack frames
(Checks incident number sent matches on website) 
问题原因
  • TestNG并发线程的ThreadLocal隔离问题
    你用static ThreadLocal存储WebDriver和FluentWait,TestNG并发执行时,每个测试类会在独立线程运行。@BeforeClass在当前类的线程中初始化ThreadLocal,但如果TestNG的线程池复用线程,当另一个测试类的方法被分配到已清理过ThreadLocal的线程时,新的@BeforeClass还没执行,beforeMethod检查就会发现ThreadLocal为空。

  • @AfterClass过早清空ThreadLocal
    @AfterClass会在当前测试类所有@Test方法执行完后,调用driver.remove()和wait.remove()清空ThreadLocal。如果线程被TestNG复用给其他测试类,新测试类的@BeforeClass还没初始化ThreadLocal,就会导致后续@Test方法的beforeMethod触发空指针或运行时异常。

  • 直接实例化测试类破坏TestNG生命周期
    在setUpClass里直接new TestPostmanAuth()调用方法,这个新实例脱离了TestNG的上下文管理,不会继承当前baseTest的ThreadLocal状态,可能干扰线程内的WebDriver实例状态,甚至提前触发资源清理。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:35:09