In Selenium, can two test threads safely share a single WebDriver instance?
answer
- Ask who actually owns the browser
- The object is a handle, not state
- One session is one conversation
- Selenium's FAQ answers this in four words
- Per-thread instances, never a shared static
basics
~10 sNo. 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 sSelenium'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 linesimport 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
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.
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.
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.
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