Skip to content
SDETRole
SDETRole

Selenium interview question

How do you run Selenium tests in parallel safely?

advanced 5+ yrs #parallel#thread-safety#testng#junit5#threadlocal

Short answer

Give every test its own WebDriver session (ThreadLocal or framework-managed per-test instances), never share static drivers or mutable state, make test data independent, configure parallelism in TestNG or JUnit 5, and run against Selenium Grid or containers sized to the thread count.

Answer

Parallelism is the cheapest way to cut suite time, and the most common way to introduce flakiness. The rule is simple: nothing mutable may be shared between concurrently running tests. The driver is the obvious case, but test data, static fields and reports matter just as much.

1. One driver per test

WebDriver instances are not thread-safe. A static WebDriver driver in a base class works serially and fails randomly in parallel, with tests clicking in each other’s browsers.

The classic fix in TestNG frameworks is a ThreadLocal:

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

    private DriverManager() {}

    public static void start(String gridUrl) throws MalformedURLException {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless=new");
        DRIVER.set(new RemoteWebDriver(new URL(gridUrl), options));
    }

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

    public static void stop() {
        WebDriver d = DRIVER.get();
        if (d != null) {
            d.quit();
            DRIVER.remove();   // avoid leaking into the next test on a pooled thread
        }
    }
}

remove() matters because runners reuse threads from a pool. A cleaner alternative is to avoid globals entirely: create the driver in a @BeforeMethod/@BeforeEach and pass it into page objects through constructors, or use a JUnit 5 extension that injects a per-test driver as a parameter. Constructor injection makes ownership explicit and is easier to reason about than global access.

2. Configure the runner

TestNG, in testng.xml:

<suite name="ui" parallel="methods" thread-count="8" data-provider-thread-count="4">
  <test name="regression">
    <packages><package name="com.acme.tests.*"/></packages>
  </test>
</suite>

JUnit 5, in src/test/resources/junit-platform.properties:

junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=8

Use @Execution(ExecutionMode.SAME_THREAD) or @ResourceLock for the few tests that touch a genuinely shared resource, such as global feature flags.

3. Independent test data

Two tests that log in as the same user and edit the same cart will interfere regardless of driver isolation. Each test should create its own data (via API, with unique identifiers) or lease it from a pool. Tests must not depend on execution order.

4. Other shared state

  • Static helpers holding a “current user” or “current order”.
  • Report listeners writing to a shared, non-thread-safe collection.
  • Downloads written to the same folder with the same filename.
  • Screenshots named by test class rather than test instance.

5. Infrastructure

Thread count must match capacity. Eight threads against a Grid with four free slots means four tests sit in the session queue and may hit timeouts. Size the Grid (or Docker/Kubernetes nodes) to the planned concurrency, and watch CPU on agents: too many browsers on one machine causes timeouts that look like application flakiness. Beyond one machine, shard the suite across CI jobs.

What interviewers look for

  • They mention data isolation, not just ThreadLocal.
  • They know why remove() and quit() matter in pooled threads.
  • They connect thread count to Grid/agent capacity and can diagnose resource-starvation flakiness.

Common mistakes

  • Making the driver ThreadLocal while page objects cache it in static fields.
  • Using parallel="classes" with tests that share @BeforeClass state and calling it thread-safe.

A strong answer covers

  • One driver per thread/test; ThreadLocal or per-test instance, no static shared driver
  • Configure the runner (TestNG parallel/thread-count or JUnit 5 parallel config)
  • Isolated test data and no shared mutable state
  • Clean up: quit and ThreadLocal.remove()
  • Infrastructure capacity: Grid, containers, matching thread count
Practise answering under time pressure

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →