Parallel Execution, Isolation, Concurrency, and Test Sharding: Core Concepts and Mental Model
Chapter 20 gave Grid a deployable infrastructure boundary. Chapter 21 asks the next operational question: how many tests may execute at once without sharing browser, data, filesystem, AUT, or evidence state? Parallel execution is not a loop trick. It is a coordinated capacity-and-isolation system.
Learning objectives
- Separate test-runner workers from WebDriver sessions, Grid slots, AUT capacity, and artifact throughput.
- Explain why each concurrent test or worker needs an explicit browser-session owner.
- Model deterministic sharding separately from runtime concurrency.
- Inspect current runner, browser, session, Grid, and evidence state before increasing workers.
- Define a safe concurrency ceiling from measured bottlenecks rather than an arbitrary worker count.
1. Why “run six tests at once” is not one operation
A runner can schedule six functions concurrently even when the machine can safely host only two browsers. A Grid can expose eight slots while the AUT can safely process only three test identities at once. An artifact store can serialize writes even when browsers are healthy. Parallelism therefore has several independent control planes.
If two workers can issue commands through the same WebDriver instance, reuse the same account, write the same screenshot path, or mutate the same fixture record, the test suite has already lost isolation. Faster scheduling cannot repair that design.
2. The concurrency pipeline
The following diagram visualizes the relationships described in The concurrency pipeline. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.
flowchart TD I[Test inventory] --> S[Shard assignment] S --> W1[Worker A] S --> W2[Worker B] W1 --> D1[WebDriver session A] W2 --> D2[WebDriver session B] D1 --> G1[Grid slot / local browser] D2 --> G2[Grid slot / local browser] G1 --> A1[Isolated AUT data A] G2 --> A2[Isolated AUT data B] W1 --> E1[Evidence namespace A] W2 --> E2[Evidence namespace B]
The shard decides which tests belong to a job. The runner decides when those tests are scheduled. A WebDriver session is the browser-control state store and must have one clear owner. Grid decides whether a compatible slot exists. The AUT and fixture layer must give each test isolated data. Evidence must use a collision-proof namespace. These are distinct mechanisms even when one CI job configures all of them.
3. Objects and state stores that must remain distinct
The following table organizes the key choices and evidence for Objects and state stores that must remain distinct. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.
| Object/state | Owner | What concurrency changes | Failure if shared |
|---|---|---|---|
| Test inventory | Repository/test runner | Which test IDs exist | Duplicate/missing coverage if shard logic is unstable |
| Shard assignment | CI/runner | Which job owns each test ID | Overlap or gaps across jobs |
| Worker/thread/process | Test runner | How many tests are scheduled simultaneously | Scheduler pressure; not a browser by itself |
| WebDriver session | One test or one carefully scoped worker | Commands, cookies, windows, profile | Cross-test browser state and command races |
| Grid slot / local browser process | Grid/host | Actual browser capacity | Queueing, crashes, CPU/RAM saturation |
| AUT/test data | Fixture/application | Business-state isolation | Account/order/file collisions |
| Downloads/profile | Browser session/test | Per-session filesystem state | Stale files and cross-test leakage |
| Evidence namespace | Test/CI workspace | Screenshots, logs, reports | Parallel workers overwrite first-failure evidence |
4. Read-only inspection before adding workers
Prove what environment you actually have. For a local session,
record Selenium version, session ID, returned capabilities, CPU
count, and current evidence root. For Grid, inspect
/status, UI/GraphQL, Node max-session counts, and queue
behavior before raising runner workers.
import os
import selenium
from selenium import webdriver
print("selenium", selenium.__version__)
print("runner logical CPUs", os.cpu_count())
driver = webdriver.Chrome()
try:
print("session", driver.session_id)
print("browser", driver.capabilities.get("browserName"), driver.capabilities.get("browserVersion"))
print("platform", driver.capabilities.get("platformName"))
finally:
driver.quit()
A Grid Node defaults its maximum concurrent sessions to available
processors. Selenium recommends roughly one browser session per
processor and warns that --override-max-sessions can
reduce stability if resources are exhausted.
5. One concurrent unit, one WebDriver owner
The Selenium project explicitly recommends a new WebDriver instance
per test because isolation makes parallelization simpler. Python
does not provide Selenium Java’s
ThreadGuard protection, so ownership is architectural:
create the driver inside the worker/test that uses it, keep it local
to that scope, and always quit() in a
finally block.
from selenium import webdriver
def test_case(case_id):
driver = webdriver.Chrome() # created inside this test/worker
try:
driver.get("http://127.0.0.1:8765/work?case=" + case_id)
# assertions belong to this owner
finally:
driver.quit()
6. Effective concurrency is the smallest safe capacity
A useful operating model is:
effective concurrency ≤ min(runner workers, available
Grid/browser slots, safe AUT concurrency, isolated-data capacity,
artifact/resource capacity)
This is not a mathematical guarantee; it is a capacity checklist. If the runner has eight workers, Grid has four slots, and the AUT safely tolerates only three synthetic users, a ceiling of three is the first candidate—not eight and not four.
If workers exceed available Grid slots, new sessions may queue. A queue is not automatically a defect; it is evidence that scheduling demand exceeds immediate browser capacity. Persistent queue growth, timeouts, or resource saturation mean the operating point is wrong.
8. DevOps connection: throughput is an observable system property
Record worker count, Grid slot state, session IDs, wall time, queue time if remote, browser versions, failed-test artifacts, and AUT/resource observations. A speedup smaller than one is still useful evidence: it tells you startup cost or contention dominates. The goal is reliable feedback per unit of infrastructure, not the largest thread count.
Knowledge check
If a runner has 8 workers but Grid has 3 free slots, how many browser sessions can start immediately?
At most three, assuming all requests match those slots. Additional new-session requests may wait in the Grid queue.
Why is one WebDriver instance shared by several Python threads unsafe even if each thread visits a different URL?
The session is shared mutable browser state. Commands, windows, cookies, navigation, element references, and teardown can race across tests.
What is the difference between a shard and a worker?
A shard partitions the inventory; a worker is an execution unit that schedules tests. One shard can have multiple workers, and one worker executes tests from its assigned shard.
Why can adding workers make the suite slower?
Browser startup, CPU/RAM pressure, Grid queueing, AUT contention, artifact IO, or retries can exceed the system’s useful capacity.
What Selenium guideline directly supports parallel isolation?
Create a new WebDriver instance per test and avoid sharing test data/state.
Official references and current-version notes
- Selenium 4.47 release notes
- Selenium downloads — current stable client and Grid versions
- Avoid sharing state — Selenium test practices
- Test independency — Selenium test practices
- Getting started with Selenium Grid — capacity guidance
- Grid CLI options — max sessions and queue controls
- Python concurrent.futures — ThreadPoolExecutor
The mandatory examples pin Selenium Python to 4.47.0.
Selenium Server/Grid 4.47.0 is the matching stable
Grid baseline. Python examples use the standard-library
concurrent.futures module rather than a third-party
parallel-test plugin so worker ownership is visible.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.