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

