πββοΈ Running Tests in Playwright: The Power of Parallel Execution
The Kitchen Analogy π³
Imagine youβre running a busy restaurant kitchen. You have 100 dishes to prepare for a big party. Would you cook them one by one? That would take forever!
Smart chefs work in parallel β one cooks pasta, another grills meat, a third prepares salads β all at the same time. Each chef has their own workspace, their own tools, and they donβt bump into each other.
Playwright tests work exactly like this kitchen! Instead of waiting for one test to finish before starting the next, Playwright runs multiple tests at the same time. Letβs explore how this magic works!
π Parallel Test Execution
What Is It?
Parallel execution means running multiple tests at the same time, not one after another.
The Story
Think of a race track with multiple lanes. Instead of one runner finishing before the next starts, all runners race at once. The race finishes much faster!
// Without parallel: β±οΈ Total = 10 + 10 + 10 = 30 seconds
test('login test', async ({ page }) => {
// Takes 10 seconds
});
test('signup test', async ({ page }) => {
// Takes 10 seconds
});
test('search test', async ({ page }) => {
// Takes 10 seconds
});
// With parallel: β±οΈ Total = ~10 seconds!
// All three run at the same time
Why It Matters
| Without Parallel | With Parallel |
|---|---|
| 100 tests Γ 10s = 1000s (16+ min) | 100 tests Γ· 4 workers = ~250s (4 min) |
| π΄ Slow feedback | π Fast feedback |
Real Life Magic
// playwright.config.js
export default {
// Enable parallel by default
fullyParallel: true,
};
π· Workers Configuration
What Are Workers?
Workers are like separate chefs in your kitchen. Each worker runs tests independently. More workers = more tests running at once!
The Story
Imagine you own a bakery. You have:
- 1 baker β Makes 10 cakes per hour
- 4 bakers β Make 40 cakes per hour!
Workers are your bakers. Each one works on different tests.
Setting Up Workers
// playwright.config.js
export default {
workers: 4, // Use 4 workers
};
Smart Worker Numbers
// Use half your CPU cores
workers: process.env.CI ? 2 : undefined,
// undefined = Playwright picks best number
// Command line override
// npx playwright test --workers=4
Visual: How Workers Divide Work
graph TD A["100 Tests"] --> B["Worker 1<br/>Tests 1-25"] A --> C["Worker 2<br/>Tests 26-50"] A --> D["Worker 3<br/>Tests 51-75"] A --> E["Worker 4<br/>Tests 76-100"] B --> F["β Done!"] C --> F D --> F E --> F
Quick Tip π‘
CI Environment: Use fewer workers (2-4) to avoid overwhelming servers. Local Machine: Let Playwright auto-detect (uses CPU cores).
π fullyParallel Mode
What Is It?
fullyParallel: true means every single test runs in parallel, even tests inside the same file!
The Story
Imagine a school with 3 classrooms (files). Each has 10 students (tests).
Without fullyParallel:
- Classroom 1 finishes all exams β then Classroom 2 β then Classroom 3
- Tests in same file run one-by-one
With fullyParallel:
- ALL 30 students take exams at the same time! π
- Tests in same file also run in parallel
Setting It Up
// playwright.config.js
export default {
fullyParallel: true, // Global setting
};
Per-File Control
// login.spec.js
import { test } from '@playwright/test';
// This file runs tests in parallel
test.describe.configure({ mode: 'parallel' });
test('login with email', async ({ page }) => {
// Runs at same time as below
});
test('login with google', async ({ page }) => {
// Runs at same time as above
});
Comparison Table
| Setting | Same File Tests | Different File Tests |
|---|---|---|
fullyParallel: false |
Sequential | Parallel |
fullyParallel: true |
Parallel | Parallel |
π Test Isolation
What Is It?
Test isolation means each test gets its own fresh environment. One test cannot affect another!
The Story
Think of hotel rooms. Each guest (test) gets their own room with:
- Fresh sheets (clean browser state)
- Clean bathroom (no leftover cookies)
- Their own TV (independent context)
What happens in Room 101 stays in Room 101!
How Playwright Does It
// Each test gets a FRESH page
test('test 1', async ({ page }) => {
await page.goto('/');
// This page belongs only to test 1
});
test('test 2', async ({ page }) => {
// Brand new page!
// Not affected by test 1 at all
});
Visual: Test Isolation
graph TD A["Test 1"] --> B["Browser Context 1<br/>πͺ Own Cookies<br/>π¦ Own Storage"] C["Test 2"] --> D["Browser Context 2<br/>πͺ Own Cookies<br/>π¦ Own Storage"] E["Test 3"] --> F["Browser Context 3<br/>πͺ Own Cookies<br/>π¦ Own Storage"]
Why Isolation Matters
// β BAD: Without isolation
test('login', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'test@example.com');
await page.click('button[type="submit"]');
// User is now logged in
});
test('view profile', async ({ page }) => {
// PROBLEM: Is user logged in or not?
// Depends on whether login ran first!
});
// β
GOOD: With isolation
test('view profile', async ({ page }) => {
// Each test sets up its own state
// No surprises, always predictable!
});
Key Benefits
- β Tests can run in any order
- β No test can break another test
- β Easy to debug single tests
- β Reliable in parallel mode
π Serial Test Execution
What Is It?
Sometimes tests MUST run in order. Serial mode forces tests to run one after another, not in parallel.
The Story
Think of a relay race. Runner 1 must pass the baton to Runner 2 before Runner 2 can start. You canβt run both at once!
When To Use Serial
- Test 2 depends on Test 1βs result
- Testing a multi-step workflow
- Database setup β use β cleanup
Setting Up Serial Mode
// checkout.spec.js
import { test } from '@playwright/test';
// These tests run ONE AT A TIME, in order
test.describe.configure({ mode: 'serial' });
test.describe('Checkout Flow', () => {
test('step 1: add to cart', async ({ page }) => {
await page.goto('/product/1');
await page.click('button.add-cart');
});
test('step 2: view cart', async ({ page }) => {
await page.goto('/cart');
// Depends on step 1!
});
test('step 3: checkout', async ({ page }) => {
await page.click('button.checkout');
// Depends on step 2!
});
});
Visual: Serial vs Parallel
graph LR subgraph Serial A1["Test 1"] --> A2["Test 2"] --> A3["Test 3"] end subgraph Parallel B1["Test 1"] B2["Test 2"] B3["Test 3"] end
Important Warning β οΈ
// If a serial test FAILS, all following tests SKIP!
test.describe.configure({ mode: 'serial' });
test('step 1', async () => {
// β FAILS
});
test('step 2', async () => {
// βοΈ SKIPPED (never runs)
});
test('step 3', async () => {
// βοΈ SKIPPED (never runs)
});
Best Practice π‘
Use serial mode sparingly. Most tests should be independent and parallel-friendly!
π― Quick Summary
| Concept | What It Does | When To Use |
|---|---|---|
| Parallel Execution | Run tests at same time | Always (default) |
| Workers | Number of parallel runners | Adjust for your machine |
| fullyParallel | Even same-file tests parallel | Most projects |
| Test Isolation | Fresh environment per test | Always (automatic) |
| Serial Mode | Force sequential order | Dependent workflows |
π Your Config Cheatsheet
// playwright.config.js
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Run all tests in parallel (recommended)
fullyParallel: true,
// Number of parallel workers
// CI: use 2 workers
// Local: auto-detect from CPU
workers: process.env.CI ? 2 : undefined,
// Each test gets fresh browser context
// (isolation is automatic!)
});
π You Did It!
You now understand how Playwright runs tests like a well-organized kitchen:
- π Parallel = Multiple tests at once = Fast!
- π· Workers = Your team of test runners
- π fullyParallel = Even same-file tests run together
- π Isolation = Each test is independent
- π Serial = Force order when needed
Go forth and run your tests at lightning speed! β‘
