Chapter 21Lesson 01~220 minutes

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.

ParallelismIsolationGrid capacityShardingSession ownership

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.

Concurrency without ownership creates shared-state bugs

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

Every worker needs a complete isolated path, not only a thread

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()
Grid 4.47 capacity baseline

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.

Queueing is evidence

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.

7. Sharding and parallelism solve different problems

Sharding partitions a test inventory across jobs or machines. Parallelism schedules work inside one job. A deterministic shard function must return the same assignment for the same test ID, shard count, and algorithm version. Do not use Python’s built-in hash() as a cross-process persistent shard key because its randomized seed can change assignments between interpreter runs.

import hashlib


def stable_shard(test_id: str, shard_count: int) -> int:
    digest = hashlib.sha256(test_id.encode("utf-8")).digest()
    return int.from_bytes(digest[:8], "big") % shard_count

for test_id in ["checkout.en", "checkout.fa", "profile.mobile"]:
    print(test_id, stable_shard(test_id, 3))

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?

Why is one WebDriver instance shared by several Python threads unsafe even if each thread visits a different URL?

What is the difference between a shard and a worker?

Why can adding workers make the suite slower?

What Selenium guideline directly supports parallel isolation?

Next lesson

Parallel Execution, Isolation, Concurrency, and Test Sharding: Guided Hands-On Workflow

Continue with Parallel Execution, Isolation, Concurrency, and Test Sharding: Guided Hands-On Workflow. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Official references and current-version notes

Version baseline — August 2026

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.