BaseTest and DriverFactory

DriverFactory With ThreadLocal

TestNG can run tests in parallel threads. A single static WebDriver would be shared by all threads and tests would type into each other's browsers. ThreadLocal<WebDriver> gives each thread its own driver.

public final class DriverFactory {
    private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();

    private DriverFactory() {}

    public static void initDriver(String browser, boolean headless) {
        WebDriver driver = switch (browser.toLowerCase()) {
            case "firefox" -> {
                FirefoxOptions o = new FirefoxOptions();
                if (headless) o.addArguments("-headless");
                yield new FirefoxDriver(o);
            }
            case "edge" -> new EdgeDriver();
            default -> {
                ChromeOptions o = new ChromeOptions();
                if (headless) o.addArguments("--headless=new");
                o.addArguments("--window-size=1920,1080");
                yield new ChromeDriver(o);          // Selenium Manager finds chromedriver
            }
        };
        DRIVER.set(driver);
    }

    public static WebDriver getDriver() { return DRIVER.get(); }

    public static void quitDriver() {
        if (DRIVER.get() != null) {
            DRIVER.get().quit();
            DRIVER.remove();                       // avoid memory leaks in thread pools
        }
    }
}

BaseTest

public abstract class BaseTest {
    protected WebDriver driver;

    @BeforeMethod(alwaysRun = true)
    @Parameters({"browser"})
    public void setUp(@Optional("chrome") String browser) {
        DriverFactory.initDriver(browser, ConfigReader.getBoolean("headless"));
        driver = DriverFactory.getDriver();
        driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
        driver.get(ConfigReader.get("baseUrl"));
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown() {
        DriverFactory.quitDriver();
    }
}

Note: alwaysRun = true makes the teardown run even when the test failed or a group filter is used, so no browser is left open on the CI machine.

ThreadLocal in Plain Java

Example

Three threads each store their own "driver" name. Results are collected and printed in a fixed order:

import java.util.*;
import java.util.concurrent.*;

public class Main {
    static final ThreadLocal<String> DRIVER = new ThreadLocal<>();

    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        List<Future<String>> results = new ArrayList<>();
        for (String test : List.of("LoginTest", "SearchTest", "CartTest")) {
            results.add(pool.submit(() -> {
                DRIVER.set("ChromeDriver for " + test);   // set in this thread only
                Thread.sleep(50);                          // other threads run meanwhile
                String seen = test + " sees -> " + DRIVER.get();
                DRIVER.remove();
                return seen;
            }));
        }
        for (Future<String> f : results) System.out.println(f.get());
        pool.shutdown();
        System.out.println("main thread sees -> " + DRIVER.get());
    }
}

Output

LoginTest sees -> ChromeDriver for LoginTest
SearchTest sees -> ChromeDriver for SearchTest
CartTest sees -> ChromeDriver for CartTest
main thread sees -> null