Workers run independent tests together; shards divide the suite across machines; contexts isolate browser users.
suite → shards → workers → isolated contexts
Series: Playwright Zero to Expert | Demo app used throughout this series: saucedemo.com
Part 15 briefly introduced “workers” while explaining fixture scope, promising a full explanation later. This is that explanation — how Playwright actually runs potentially hundreds of tests quickly, and the real isolation guarantees that make it safe to do so without tests interfering with each other.
Workers and Parallel Execution
A worker is an independent process running a subset of your test suite. By default, Playwright automatically determines how many workers to run based on your machine’s available CPU cores — recall Part 16’s workers config option, which lets you override this explicitly.
Full Test Suite (60 tests)
│
┌───────────────┼───────────────┐
Worker 1 Worker 2 Worker 3
(20 tests) (20 tests) (20 tests)
runs sequentially runs sequentially runs sequentially
within itself within itself within itself
The key structural idea worth being precise about: tests running within a single worker still run one after another, sequentially. Parallelism happens across workers — three workers each running 20 tests sequentially finish the full 60-test suite in roughly the time it takes to run 20 tests, not 60, because those three groups of 20 are genuinely happening simultaneously.
// playwright.config.ts
export default defineConfig({
fullyParallel: true, // recall Part 6 — allows tests within the SAME FILE to also be distributed across workers
workers: process.env.CI ? 4 : undefined,
});
fullyParallel: true matters specifically because, without it, Playwright’s default behavior runs all tests within a single file sequentially in the same worker, even if other workers are sitting idle — fullyParallel allows individual tests to be distributed across workers regardless of which file they happen to live in, generally producing faster overall suite execution when you have many independent tests spread across relatively few files.
Test Isolation Under Parallel Execution
Here’s the question this whole part has been building toward, and it’s genuinely the most important thing to understand correctly: if many tests are running simultaneously, potentially against the very same shared application and even the same shared backend, how do you prevent them from interfering with each other?
Recall Part 6’s browser context isolation — this is the first, foundational layer: each individual test gets its own fresh browser context, so no test’s cookies, storage, or session state can leak into another test, regardless of how many are running in parallel. This solves browser-level isolation completely, on its own.
But it does not, on its own, solve data-level isolation — this is a genuinely important, often-missed distinction.
If two tests running in parallel both interact with the exact same shared backend resource — the same specific product’s inventory count, the same specific user account, the same row in a shared database table — browser context isolation does nothing to prevent them from stepping on each other’s toes at that shared, external layer.
This is precisely why Part 21’s emphasis on dynamic, uniquely-generated test data matters so directly here: a test creating its own uniquely-generated user, rather than reusing one shared, fixed account, is what actually protects it from parallel-execution interference at the data layer, complementing the browser-level isolation Playwright already gives you automatically.
Analogy: The Multi-Lane Highway Imagine commuting during rush hour:
- Sequential Execution (Single-Lane Road): Every car (test) must drive in a single file line. If one car is slow or breaks down, all traffic behind it blocks. It is safe, but slow.
- Parallel Workers (4-Lane Highway): You expand the road to 4 lanes. Now 4 cars travel simultaneously.
- Browser Context Isolation (Lane Markings): The painted white lines keep cars separated, preventing them from colliding (browser sessions don’t leak cookies).
- Data-Level Isolation (Separate Parking Destinations): Even with 4 lanes, if all 4 cars are trying to park in the exact same single parking space (updating the same database row), they will crash in the parking lot. You must assign each car its own unique parking address (dynamic Faker data) to remain safe.
- Sharded Execution (Multiple Highways): Splitting traffic across entirely separate highways in different cities (multiple CI virtual machine runner hosts).
📊 Visual Flowchart: Sequential vs. Parallel vs. Sharded Execution
Here is how test execution scales from a single worker up to multiple distributed machines:
graph TD
subgraph ModeSequential ["Mode 1: Sequential (1 Worker)"]
S1["Test 1"] --> S2["Test 2"] --> S3["Test 3"]
end
subgraph ModeParallel ["Mode 2: Parallel Workers (Single Host)"]
W1["Worker 1 (Lanes)"] --> T1["Test 1"]
W2["Worker 2 (Lanes)"] --> T2["Test 2"]
W3["Worker 3 (Lanes)"] --> T3["Test 3"]
end
subgraph ModeSharded ["Mode 3: Sharded (Multi-Machine CI Matrix)"]
Machine1["CI Machine 1 (Shard 1/2)"] --> Shard1["Runs Workers 1 & 2 (Tests 1-20)"]
Machine2["CI Machine 2 (Shard 2/2)"] --> Shard2["Runs Workers 3 & 4 (Tests 21-40)"]
end
Serial Execution — When You Genuinely Need It
Occasionally, a specific group of tests genuinely must run in a fixed, guaranteed order, rather than in parallel — perhaps because they deliberately build on each other’s state as a sequence (though this itself is often, honestly, a design smell worth questioning first — recall the isolation principles above). Playwright provides an explicit escape hatch for this:
test.describe.serial("checkout flow, step by step", () => {
test("step 1: add item to cart", async ({ page }) => {
/* ... */
});
test("step 2: proceed to checkout", async ({ page }) => {
/* ... */
});
test("step 3: complete payment", async ({ page }) => {
/* ... */
});
});
.serial guarantees these specific tests run in the exact order written, within the same worker, and — genuinely important to know — if one of them fails, Playwright skips the rest of that serial group entirely, rather than running tests that would almost certainly also fail against a base state their prerequisite step never successfully established. This should be reached for deliberately and sparingly, not as a default habit — most well-designed tests should be genuinely independent of each other, exactly the isolation principle this whole part has been building toward.
Sharding
For suites large enough that even full parallelization within one machine isn’t fast enough, sharding splits the entire suite across multiple separate machines entirely — genuinely useful in CI, where you might have several independent runner machines available simultaneously:
npx playwright test --shard=1/3 # this machine runs roughly the first third of the suite
npx playwright test --shard=2/3 # this machine runs roughly the second third
npx playwright test --shard=3/3 # this machine runs roughly the final third
Each shard is a completely separate process, potentially on a completely separate physical or virtual machine, each independently reporting its own results — a direct preview of Part 32’s CI/CD discussion, where multiple parallel CI jobs, each running one shard, can complete a suite that would take, say, thirty minutes on one machine in closer to ten minutes across three machines running simultaneously.
How It Works in a Real Test Run
A worker is an operating-system process that executes test files. Each test normally receives a fresh browser context, while sharding divides the suite across machines. These are three different levels: worker concurrency, test isolation, and distributed assignment.
Scaling exposes hidden shared state in accounts, files, ports, queues, and database records. Measure duration and failure rate while increasing workers; more concurrency can reduce throughput when CPU, memory, browser processes, or the tested environment becomes saturated.
Interview Questions
Q: What’s the difference between how tests run within a single worker versus across multiple workers?
Ans: Tests within a single worker run sequentially, one after another, in that worker’s own process. Parallelism happens across separate workers — multiple workers, each running their own sequential subset of tests, execute simultaneously, which is what actually reduces the total wall-clock time needed to complete the full suite.
Q: Does Playwright’s default browser context isolation fully protect parallel tests from interfering with each other? Why or why not?
Ans: It fully protects tests at the browser level — cookies, storage, and session state never leak between tests, since each gets its own fresh context. It does not, on its own, protect against interference at the data level — if multiple parallel tests interact with the same shared external resource, like the same database record or the same fixed user account, they can still genuinely interfere with each other there, regardless of their separate browser contexts.
Q: Why does using dynamically generated, unique test data (as covered in Part 21) matter specifically for safe parallel execution?
Ans: Because browser-level isolation alone doesn’t prevent parallel tests from colliding over shared external resources. If each test creates and uses its own uniquely generated data — a unique user, a unique record — rather than reusing shared, fixed values, it eliminates the risk of one test’s actions on a shared resource unexpectedly affecting another test running at the same time.
Q: When would you reach for test.describe.serial, and why should it be used sparingly rather than as a default?
Ans: It’s appropriate when a specific, small group of tests genuinely must run in a guaranteed, fixed order because they deliberately build on each other’s state as a sequence. It should be used sparingly because well-isolated, independent tests are generally the healthier default — a heavy reliance on serial execution can be a sign that tests aren’t properly isolated from each other, and it also means those specific tests lose the speed benefit of parallel execution, running one after another regardless of available workers.
Q: What is sharding, and how is it different from simply increasing the number of workers on one machine?
Ans: Sharding splits an entire test suite across multiple separate machines, each running its own independent portion of the suite and reporting results independently. Increasing workers scales parallelism within the resources of a single machine; sharding scales beyond a single machine’s limits entirely, letting you use multiple machines simultaneously in CI to reduce total execution time further than one machine’s available CPU cores alone could achieve.
Q: A suite of tests passes reliably when run one at a time, but several tests start failing intermittently only when run with multiple workers. What would you investigate?
Ans: I’d investigate whether those specific tests share some external, non-browser-isolated resource — a fixed test account, a shared database record, a shared file — that could be affected by another test running concurrently in a different worker. Since Playwright’s browser context isolation already handles browser-level state safely, this kind of parallel-execution-specific failure very often points toward a data-level isolation gap rather than a browser-level one, which is exactly the distinction this part focused on.
Exercises — Part 27
Understand: Explain, in your own words, why browser context isolation alone doesn’t guarantee full safety for tests running in parallel, using a concrete example involving a shared user account.
Simple Practice:
Take a set of independent tests you’ve written earlier in this series, run them with workers: 1 and note the total execution time, then run them again with a higher worker count (or the default, letting Playwright decide) and compare the total time.
Real-World Scenario:
Design three tests that would be genuinely safe to run in full parallel (each creating and using its own uniquely generated data), and contrast them with a hypothetical set of three tests that would NOT be safe to parallelize without using test.describe.serial, explaining specifically why each group falls into its respective category.
Challenge: Research Playwright’s sharding documentation and how it integrates with a CI system’s own native parallel job matrix (for instance, GitHub Actions’ matrix strategy, previewed properly in Part 32). Write, in your own words, a rough plan for splitting a hypothetical 300-test suite across 5 parallel CI machines using sharding.
Next: Part 28 — Flaky Tests
— the deepest dive yet into diagnosing intermittent failures, with a rigorous, deliberate methodology beyond “just add a retry.”
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed