How do you run a Selenium suite in parallel across a grid without the tests interfering with each other?
Give each thread its own WebDriver session held in thread-local storage, remove every other piece of shared mutable state including test data and static fields, size the run to the grid's slots so requests queue rather than time out, and quit every session even on abnormal exit.
What the interviewer is scoring
- Does the candidate scope the WebDriver session per thread rather than share a driver field
- Whether shared state outside the driver is identified as the other half of the problem
- That grid capacity is understood to cause queueing and session-creation timeouts, not test failures
- Whether leaked sessions and cleanup after an abnormal exit are addressed
- Does the answer explain how a failure on a remote node is made diagnosable
Answer
The shared driver field is the first bug
A WebDriver instance is not thread-safe, and a session belongs to exactly one browser. So the base class with protected WebDriver driver assigned in a setup method is the defect that appears the moment you raise the thread count: depending on how the runner instantiates test classes, two threads either share one browser and interleave their clicks, or one thread's teardown quits the browser another thread is still using. The failures look like flakiness — an element vanishing mid-interaction, a session identifier that is suddenly unknown — and they are not flakiness, they are two tests in one browser.
The fix is to make the driver reference thread-scoped, held in a thread-local, created when a thread first needs one and removed when it is finished. Every page object and helper then obtains the driver from that holder rather than receiving it from a shared field, and no code path can reach another thread's session.
private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();
// remove() as well as quit(): without it the thread-local retains a dead
// session, and on a reused pool thread the next test picks it up.
static void tearDown() {
WebDriver d = DRIVER.get();
if (d != null) { d.quit(); DRIVER.remove(); }
}
Two details are commonly missed. quit ends the session and closes every window, while close closes the current window only, so a suite using close leaks sessions that continue to occupy grid slots. And thread pools reuse threads, so failing to call remove leaves the previous test's stale driver visible to the next test that lands on that thread.
The driver is only half the shared state
Isolating the browser does not isolate the test. Everything else the suite shares is now contended too, and this is where a suite that looks correctly parallelised still fails intermittently.
Static and singleton state is the obvious one: a static logged-in user, a cached configuration object mutated per test, a counter used to build names. Test data is the more consequential one, because two threads running against the same account or the same order couple through the database no matter how well the drivers are separated, and the symptom appears in whichever test read the row second. The rule that scales is that each test generates the identity of everything it creates and filters every assertion to records it owns.
Then there are resources with genuinely fixed identity. A download directory shared by eight browsers means eight tests writing into one folder and asserting on its contents. A fixed port for a stub server means the second thread fails to bind. A single test user's inbox means one thread reads another's verification email. Each of these needs to be parameterised per thread, and each of them is easy to miss because it works perfectly at a thread count of one.
Grid capacity is a queue, and the queue has a timeout
Selenium Grid 4 is a set of components — a router, a distributor, a session queue, a session map, an event bus and one or more nodes — which can all run in a single standalone process or be deployed separately. A node advertises a number of slots, and the total across nodes is your real concurrency limit. Requesting more sessions than there are slots does not fail immediately: requests wait in the session queue, and if a slot does not free up within the configured timeout, session creation fails.
That is the mechanism behind the most confusing symptom in grid runs. A suite configured for fifty threads against twenty slots produces failures that have nothing to do with the tests: sessions that never start, timeouts at the point of creation, and tests reported as failed before their first line ran. Raising the thread count in the runner does not raise capacity, and the effect of over-requesting is worse than the effect of matching, because the queue wait is added to every test's wall-clock time.
So set the runner's thread count from the grid's slot count rather than from optimism, and monitor the queue. If throughput is the problem, add nodes. Resource pressure on the nodes themselves is the other half of this: browsers are memory-hungry, and a node given more slots than its memory supports produces browser crashes that surface as unrelated test failures. Running nodes in containers, one browser per container, makes both the capacity accounting and the cleanup honest.
Sessions that leak, and runs that never end
Every created session must be quit, including on the paths where the test did not finish normally. A test killed by the runner's timeout, a thread that threw during setup after the driver was created, a cancelled pipeline: each leaves a browser holding a slot until the grid's own session timeout reclaims it. A suite that leaks a few sessions per run degrades the grid for everyone else, and the next run starts with less capacity than the last, which looks like the grid getting slower over time.
Create the driver in a hook the framework guarantees is paired with a teardown hook, rather than inside test code. Configure the grid's session timeout so abandoned sessions are reclaimed rather than held forever. And set explicit page-load and script timeouts, because a driver call that blocks indefinitely holds both a thread and a slot while achieving nothing.
Diagnosing a failure you did not watch
With a local browser you can look at it. On a grid you cannot, so the artefacts have to be arranged in advance or the failure is uninvestigable. Capture a screenshot at the point of failure and attach it to the report, take the page source, and pull the browser logs where the driver exposes them. Record which node and which browser version served the session, because a failure appearing on one node only is the fastest possible clue and is invisible if you do not log it.
The most valuable addition is a correlation identifier sent from the test as a request header and logged by the application, so a browser-side failure leads straight to the server's own account of what happened. Without it you are searching server logs over a window in which twenty other tests were also running.
Retries hide the coupling you have not fixed
The trap in every parallel suite is the automatic retry. When threads are raised and the failure rate rises, retrying failed tests makes the run green again, and the coupling that caused the failures stays in place and keeps costing time. Worse, it hides real product concurrency bugs, which are exactly the defects a parallel suite is uniquely placed to find, since two tests acting simultaneously is closer to production than one acting alone.
Prove isolation instead of retrying past it. Run the suite twice in a row without resetting anything, then run it with the thread count doubled, then compare the set of failures against a serial run. A suite that behaves identically in all of those has no shared state left, and only then is a retry policy a reasonable guard against genuine infrastructure noise rather than a way of not knowing.
Likely follow-ups
- A test passes serially and fails only with eight threads. How do you narrow that down?
- The grid offers twenty slots and your run requests fifty sessions at once. What happens?
- How do you get the screenshot and the browser console log off a remote node after a failure?
- Where do you stop adding parallelism and start deleting tests instead?
Related questions
- Your API suite runs on every commit against a shared database. How do you keep the tests from corrupting each other's data?hardAlso on test-isolation and parallel-execution5 min
- Walk me through waiting in Selenium: implicit versus explicit, why you get StaleElementReferenceException, and how page objects fit in.mediumAlso on selenium and webdriver5 min
- A test passes on your machine and fails in CI roughly once a week. How do you track it down?mediumAlso on test-isolation5 min
- Your test suite fails randomly about one run in five. How do you get it under control?hardAlso on test-isolation7 min
- A test passes on its own and fails when the whole suite runs. How do you track that down?mediumAlso on test-isolation5 min
- What belongs in a page object, and what should never go in one?easyAlso on selenium4 min
- A maintenance script deletes rows it should not have touched, and nobody notices for six hours. Walk me through the recovery.hardSame kind of round: scenario6 min
- A customer ports their number away to another operator. What has to happen on your side, and what usually goes wrong?hardSame kind of round: scenario6 min