skip to content

Suite Execution

Running a browser suite at scale: many live sessions at once without corrupting each other, plus the runner and pipeline seams that open and close them.

on this pageshow

explore

questions

16

In Selenium, can two test threads safely share a single WebDriver instance?

level: juniorimportance: must knowfreq 76%

answer

  1. Ask who actually owns the browser
  2. The object is a handle, not state
  3. One session is one conversation
  4. Selenium's FAQ answers this in four words
  5. Per-thread instances, never a shared static

basics

~10 s

No. A Selenium WebDriver object carries no thread-safety guarantee, and one session drives one browser, so two threads issuing commands through it interleave into a single confused conversation. Give every thread its own driver.

solid answer

~40 s

Selenium's own FAQ states it plainly: `WebDriver` is not thread-safe. Nothing in the Java client synchronises anything either — `RemoteWebDriver` has no `synchronized` method, no `volatile` field and no lock. The deeper reason is that the object is only a handle: the real state, including the current top-level browsing context and the current URL, lives on the remote end under one session id. Two threads sharing that handle send commands into the same browser, so one thread's navigation moves the page the other was about to assert on. The rule is one driver per thread, in practice one per test case, created in that thread and quit in that thread. Serialising every call would make individual commands well-formed but still leaves one browser, and throws away the parallelism you wanted.

code

java · 29 lines
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class SharedPlanComparisonDriver {

  public static void main(String[] args) throws Exception {
    WebDriver shared = new ChromeDriver();
    ExecutorService pool = Executors.newFixedThreadPool(2);

    pool.submit(
        () -> {
          shared.get("https://plans.example.com/compare?tier=prepaid");
          return shared.findElement(By.id("plan-row-unlimited")).getText();
        });
    pool.submit(
        () -> {
          shared.get("https://plans.example.com/compare?tier=business");
          return shared.findElement(By.id("plan-row-starter")).getText();
        });

    pool.shutdown();
    pool.awaitTermination(60, TimeUnit.SECONDS);
    shared.quit();
  }
}

go deeper

for a junior

Be ready to state the rule without hedging: a driver is not thread-safe, so give each thread its own instance. Knowing that a driver talks to exactly one browser is enough to justify it.

for a middle

Explain where the state lives. The client object is a handle over an HTTP session id, and the current page and window belong to the remote end, which is why locking the client calls does not rescue sharing.

for a senior

An interviewer expects you to recognise the symptoms in a real run and name the structural fix, including where a driver reference tends to leak into shared scope and how you prove one session served two cases.

for a principal

Own the policy: how driver scope is enforced across a suite so the mistake cannot be made, and what the honest cost of one browser per parallel unit is against the wall-clock time it buys.

## The rule, and where Selenium writes it down Selenium's own FAQ answers this in one line: **WebDriver is not thread-safe**. It adds that if you can *serialise* access to the underlying instance you may share a reference across threads, that this is not advisable, and that you can instead instantiate one `WebDriver` per thread. That last clause is the whole of the guidance most suites need. It applies to every driver the Java client ships — `ChromeDriver`, `FirefoxDriver`, `EdgeDriver`, `SafariDriver` and `RemoteWebDriver`, which the local ones extend — because the hazard is not a bug in one implementation, it is the design. Nothing in the client quietly contradicts it. `RemoteWebDriver` contains no `synchronized` method, no `volatile` field and no `AtomicReference`; its `sessionId`, `executor` and `capabilities` are plain mutable fields, and `execute()` even renames the calling thread while a command is in flight. There is no lock to take and none is taken for you. ## The driver object is a handle, not the browser A `WebDriver` instance is a thin client over an HTTP conversation. Almost nothing you care about lives in the Java object; it lives on the **remote end**, keyed by one **session id**. The W3C WebDriver specification models a session as holding, among other things, a *current top-level browsing context*, a *current browsing context* and a timeouts configuration, and the commands are written against that state: navigation is `POST /session/{session id}/url`, finding is `POST /session/{session id}/element`. That is why sharing is worse than an ordinary unsynchronised field. Even if every Java field were perfectly guarded, two threads pushing commands at one session id are two voices in **one conversation with one browser**. That browser has one address bar and one focused window. Locking the client calls makes each individual command well-formed and still leaves the second thread staring at the first thread's page. ## What the interleaving actually looks like Take a telecom **plan-comparison table** — prepaid and business tariffs side by side, with columns for data allowance, monthly price and contract length. Two cases run at once through one shared driver: 1. Thread A navigates to the prepaid comparison URL and starts locating the unlimited-plan row. 2. Thread B navigates to the business comparison URL; the one browser goes there. 3. Thread A's `findElement` now runs against the business table and throws `NoSuchElementException` — or, worse, matches a row with the same id and asserts a business price against a prepaid expectation. 4. Thread B asserts on a monthly total that thread A's sort click has already reordered. The failure that reaches your report is never labelled "shared driver". It is a missing element, a wrong number, or a screenshot of a page nobody in that test ever visited. ## The three shapes, side by side | Shape | What a thread gets | What breaks | |---|---|---| | One shared driver, unguarded | one session, one browser, interleaved commands | wrong page, wrong row, `NoSuchWindowException` after another thread closes a window | | One shared driver, every call locked | well-formed single commands, still one browser | still one address bar, and every command now queues, so the run is serial anyway | | One driver per thread | its own session id, its own browser, its own state | nothing at this layer | ## The shape that works - **Create the driver on the thread that will use it**, and call `quit()` from that same thread. - **Scope it to the unit the runner parallelises** — commonly one driver per test case, sometimes one per worker. - **Never park a driver in a `static` field** that more than one thread can read; that is the most common way this defect enters a suite. - **Do not hand the driver to a background thread** for a "quick" screenshot or log grab while the owning thread is still driving. - **Do not hand a `WebElement` across threads either** — it carries a back-pointer to its driver, so using it is the same shared call in disguise. - **Treat `quit()` as destroying the object**, not resetting it: afterwards the client's session id is null and the next command fails with `NoSuchSessionException` carrying the message `Session ID is null. Using WebDriver after calling quit()?`. ## What the rule does not say It does not say Selenium cannot run in parallel — it parallelises very well, with one session per worker. It does not say sharing always throws: it frequently *appears* to work on a fast machine and fails only when a slow page opens a gap for the other thread's command to land in. And it does not say the remote end will save you. Selenium's Grid node routes each request to whichever slot holds that session id and adds no locking of its own, so two threads simply arrive at the same browser and take turns confusing it.

  • If you guard every call with a lock, is sharing one driver then safe?
    Individual commands become well-formed, but the browser does not become two browsers. One session has one current top-level browsing context and one address bar, so the second thread still finds the first thread's page loaded. You also lose the parallelism: every command queues behind the lock, so the run is serial with extra machinery bolted on.
  • Does it matter which thread creates the driver and which one quits it?
    Yes. Creating on one thread and using it on another is exactly the cross-thread access the rule forbids. Quitting from a different thread is worse: `quit()` nulls the client's session id, so a command already in flight on the owning thread surfaces as `NoSuchSessionException` with `Session ID is null. Using WebDriver after calling quit()?` rather than as a threading bug.
  • Is a WebElement returned by one thread usable from another thread?
    No. A `WebElement` is a reference into one session's browsing context and carries a back-pointer to the driver that produced it, so calling a method on it is the same shared-driver call in disguise. It fails the same way: if another thread has navigated, the reference is stale or resolves against the wrong row.

One driver is one phone line to one browser. Two people talking into the same line do not get two conversations; they get one garbled call, and neither can tell which half was theirs.

saying these in an interview costs you the question

  • Says a driver is thread-safe as long as threads touch different elements
  • Believes wrapping every driver call in a lock makes parallel sharing work
  • Keeps the driver in a static field and calls the suite parallel-ready
  • Thinks one browser can hold two independent test conversations at once
  • Blames flaky waits when a shared driver instance is the real cause
open as a page

In a Selenium suite, how does a ThreadLocal<WebDriver> holder work, and why must teardown call remove()?

level: middleimportance: must knowfreq 74%

basics

~20 s

A ThreadLocal holder stores one WebDriver per thread, so each parallel test gets its own browser session. Teardown must quit the driver and call remove, or the entry survives on a reused pool thread and the next test inherits it.

open as a page

A Selenium suite is green at two workers and flaky at six on one host - how do you tell a per-worker collision from memory pressure?

level: seniorimportance: must knowfreq 65%

basics

~20 s

A collision fails early and asymmetrically: the first worker survives and the rest die at session start or read each other's files. Memory pressure fails late and everywhere, worsens smoothly with worker count, and kills browsers mid-test.

open as a page

In a parallel Selenium run, cases throw NoSuchWindowException and failure screenshots show another test's page. What is the cause?

level: seniorimportance: must knowfreq 58%

basics

~10 s

Two or more threads are driving one Selenium session. Both symptoms are the same fact: every thread's command lands in one browser whose window and page another thread has already changed underneath it.

open as a page

How do you choose how many parallel Selenium browser workers a single machine should run?

level: principalimportance: must knowfreq 50%

basics

~20 s

Measure it on the real hardware; there is no formula. Each worker runs a whole browser process tree, so memory and CPU cap the count long before threads do. Ramp the workers up until per-case time degrades, then back off.

open as a page

In Selenium, what do the Chrome arguments --no-sandbox and --disable-dev-shm-usage do on a CI container?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Both are Chromium switches Selenium passes to the browser. The first turns off Chrome's process sandbox so it can start in a container without the needed kernel privileges. The second moves shared memory off a small /dev/shm.

open as a page

In Selenium, why does pinning one --user-data-dir in ChromeOptions break a parallel run?

level: juniorimportance: should knowfreq 40%

basics

~20 s

Chrome passes --user-data-dir straight through, and only one live browser may own a profile directory. Pinning the same path across workers makes every worker but one fail to start, and leaks state between the ones that run in turn.

open as a page

In a Selenium test class, why is the WebDriver started in the runner's setup hook rather than inside each test method?

level: juniorimportance: should knowfreq 64%

basics

~20 s

The setup hook runs automatically before every test, so each test gets a ready browser session without repeating the start-up code, and the matching teardown hook still runs after a test that failed part-way, closing the browser.

open as a page

In Selenium, why can a page render narrower under headless on a build agent than on a developer's machine?

level: middleimportance: should knowfreq 62%

basics

~20 s

Headless has no desktop to size itself against, so the browser opens at its own small fixed default window rather than at monitor size. Responsive breakpoints then fire and the page lays out as if on a small screen.

open as a page

In Selenium, how does each parallel worker's driver process avoid binding the same port?

level: middleimportance: should knowfreq 35%

basics

~20 s

Selenium's driver service builder defaults its port to zero, and build() replaces zero with a port proved free by binding a socket. Each worker that constructs its own driver therefore gets its own port, unless someone pins one with usingPort.

open as a page

In a Selenium suite, what breaks when the WebDriver is created in a per-test setup hook but quit in a class-level teardown hook?

level: middleimportance: should knowfreq 48%

basics

~20 s

Every test after the first orphans a browser session. The class-level teardown quits only the last driver the field still holds, so the earlier sessions stay alive, holding browser processes and Grid slots until something else reaps them.

open as a page

In Selenium's Java client, what does ThreadGuard.protect(driver) actually prevent?

level: middleimportance: should knowfreq 34%

basics

~20 s

It wraps a driver in a proxy that remembers the creating thread and throws a WebDriverException the moment any other thread calls a method on it. It detects cross-thread use; it does not make the driver thread-safe.

open as a page

In a parallel Selenium suite, a case's first command lands on the previous case's session — what does that prove about the ThreadLocal driver holder?

level: seniorimportance: should knowfreq 47%

basics

~20 s

It proves the previous case's driver was never removed from the holder. Runners hand cases to a pool of reused threads, so the entry survives the case that set it and the next case on that thread reads it.

open as a page

In Selenium 4, how would you decide between Selenium Manager resolving a driver per CI job and a pinned driver baked into the image?

level: principalimportance: should knowfreq 38%

basics

~20 s

Decide by what the job must guarantee. Selenium Manager resolves and downloads a matching driver at run time, which is convenient but depends on the network. A driver baked into the image and pinned makes the job repeatable offline.

open as a page

How would you design a ThreadLocal WebDriver holder so a forgotten remove() cannot leak a Selenium session?

level: principalimportance: should knowfreq 36%

basics

~20 s

Make the holder own both ends. Hide set and remove behind one start and stop pair whose stop quits inside a try and clears inside a finally, and make the getter throw rather than return null.

open as a page

A Selenium suite's failure listener finds a dead session when it tries to capture evidence — why, and where must that listener sit?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

Because the teardown hook already called quit, which deletes the session. Any command after that throws, so the capture step has to run before the quit, against the driver instance the failing test was actually using.

open as a page