Documentation Index Fetch the complete documentation index at: https://mintlify.com/microsoft/playwright/llms.txt
Use this file to discover all available pages before exploring further.
Introduction
Playwright Test runs tests in parallel by default using multiple worker processes. This significantly reduces test execution time.
How Parallelization Works
From src/common/config.ts:121,240-252 and src/runner/testRunner.ts:88-115:
Playwright uses a worker-based parallelization model :
Test runner spawns multiple worker processes
Each worker runs tests sequentially
Workers run in parallel to each other
Tests are distributed across available workers
export default defineConfig ({
workers: 4 , // Run 4 tests in parallel
}) ;
Worker Configuration
Fixed Number of Workers
export default defineConfig ({
workers: 4 , // Always use 4 workers
}) ;
Percentage of CPU Cores
From src/common/config.ts:240-252:
export default defineConfig ({
workers: '50%' , // Use 50% of available CPU cores
}) ;
// Implementation:
function resolveWorkers ( workers : string | number ) : number {
if ( typeof workers === 'string' && workers . endsWith ( '%' )) {
const cpus = os . cpus (). length ;
return Math . max ( 1 , Math . floor ( cpus * ( parseInt ( workers , 10 ) / 100 )));
}
return workers ;
}
Automatic Detection
export default defineConfig ({
workers: undefined , // Auto-detect based on CPU cores
}) ;
Environment-Based Configuration
export default defineConfig ({
workers: process . env . CI ? 2 : undefined ,
// Use 2 workers in CI, auto-detect locally
}) ;
Parallel Modes
From src/common/test.ts:58 and src/common/testType.ts:46-51,155-168:
Default Mode
Tests within a file run sequentially:
test ( 'test 1' , async ({ page }) => {});
test ( 'test 2' , async ({ page }) => {});
// test 2 waits for test 1 to complete
Different files run in parallel:
tests/
login.spec.ts # Runs in worker 1
checkout.spec.ts # Runs in worker 2
search.spec.ts # Runs in worker 3
Fully Parallel Mode
Run all tests in parallel:
From src/common/config.ts:102:
export default defineConfig ({
fullyParallel: true ,
}) ;
Or per-project:
projects : [
{
name: 'chromium' ,
use: { browserName: 'chromium' },
fullyParallel: true ,
},
]
Describe Parallel
Make specific describe blocks run in parallel:
From src/common/testType.ts:47,155-158:
test . describe . parallel ( 'Parallel suite' , () => {
test ( 'test 1' , async ({ page }) => {});
test ( 'test 2' , async ({ page }) => {});
test ( 'test 3' , async ({ page }) => {});
// All three tests run in parallel
});
Serial Mode
Force tests to run serially:
From src/common/testType.ts:49,155-156:
test . describe . serial ( 'Serial suite' , () => {
test ( 'test 1' , async ({ page }) => {});
test ( 'test 2' , async ({ page }) => {});
// test 2 waits for test 1
});
describe.parallel cannot be nested inside describe.serial or default mode describe blocks.
Worker Isolation
Each worker maintains its own isolated environment:
Worker-Scoped Fixtures
Shared within a worker, isolated between workers:
const test = base . extend ({
database: [ async ({}, use ) => {
const db = await createDatabase ();
await use ( db );
await db . close ();
}, { scope: 'worker' }],
});
test ( 'test 1' , async ({ database }) => {
// Uses same database as test 2 if in same worker
});
test ( 'test 2' , async ({ database }) => {
// Reuses database from test 1 if in same worker
});
Browser Instance Sharing
From src/index.ts:104-126:
Browser instances are shared within workers:
browser : [ async ({ playwright , browserName }) => {
const browser = await playwright [ browserName ]. launch ();
await use ( browser );
await browser . close ({ reason: 'Test ended.' });
}, { scope: 'worker' }]
Each worker:
Launches one browser instance
Creates fresh contexts for each test
Reuses browser across tests
Controlling Parallelization
Project-Level Workers
From src/common/config.ts:212-215:
projects : [
{
name: 'setup' ,
testMatch: / . * \. setup \. ts/ ,
workers: 1 , // Always use 1 worker for setup
},
{
name: 'chromium' ,
use: { browserName: 'chromium' },
workers: 4 , // Use 4 workers for chromium tests
},
]
From src/common/testType.ts:186-209:
test . describe ( 'Suite' , () => {
test . describe . configure ({ mode: 'parallel' });
test ( 'test 1' , async ({ page }) => {});
test ( 'test 2' , async ({ page }) => {});
// Both run in parallel
});
test . describe ( 'Serial Suite' , () => {
test . describe . configure ({ mode: 'serial' });
test ( 'test 1' , async ({ page }) => {});
test ( 'test 2' , async ({ page }) => {});
// Run serially
});
Worker Index
Access worker index in tests:
test ( 'test' , async ({ page }, testInfo ) => {
console . log ( `Running in worker ${ testInfo . workerIndex } ` );
// Use worker index for port allocation
const port = 3000 + testInfo . workerIndex ;
await page . goto ( `http://localhost: ${ port } ` );
});
Sharding
Distribute tests across multiple machines:
From src/common/config.ts:116:
Configuration
export default defineConfig ({
shard: { total: 3 , current: 1 } ,
}) ;
CLI Usage
# Machine 1
npx playwright test --shard=1/3
# Machine 2
npx playwright test --shard=2/3
# Machine 3
npx playwright test --shard=3/3
CI/CD Example
# GitHub Actions
strategy :
matrix :
shardIndex : [ 1 , 2 , 3 , 4 ]
shardTotal : [ 4 ]
steps :
- name : Run tests
run : npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
Test Distribution
Tests are distributed to balance execution time:
// Playwright automatically distributes:
tests /
fast - test . spec . ts # 5 tests , 1 s each -> Worker 1
medium - test . spec . ts # 3 tests , 10 s each -> Worker 2
slow - test . spec . ts # 1 test , 30 s -> Worker 3
Parallel Execution Patterns
Independent Tests
Tests that don’t share state:
export default defineConfig ({
fullyParallel: true , // All tests run in parallel
}) ;
test ( 'user login' , async ({ page }) => {
// Independent
});
test ( 'product search' , async ({ page }) => {
// Independent
});
Shared Setup with Parallel Tests
test . describe ( 'E-commerce' , () => {
test . beforeEach ( async ({ page }) => {
await page . goto ( '/' );
await loginUser ( page );
});
// These run in parallel if fullyParallel is true
test ( 'view cart' , async ({ page }) => {});
test ( 'view wishlist' , async ({ page }) => {});
test ( 'view orders' , async ({ page }) => {});
});
Sequential Test Flow
test . describe . serial ( 'Checkout flow' , () => {
test ( 'add to cart' , async ({ page }) => {
// Must run first
});
test ( 'enter shipping' , async ({ page }) => {
// Depends on cart state
});
test ( 'complete payment' , async ({ page }) => {
// Depends on shipping
});
});
Optimal Worker Count
// Too few workers: underutilized CPU
workers : 1
// Optimal: balance CPU usage and overhead
workers : os . cpus (). length
// Too many workers: excessive overhead
workers : os . cpus (). length * 4
Test Organization
// Good: Fast tests together
tests /
unit / # Fast tests , high parallelization
integration / # Medium tests , moderate parallelization
e2e / # Slow tests , lower parallelization
// Configure per directory
projects : [
{ testDir: './tests/unit' , workers: 8 },
{ testDir: './tests/integration' , workers: 4 },
{ testDir: './tests/e2e' , workers: 2 },
]
Browser Reuse
Worker-scoped browser fixture reuses browsers:
// Efficient: Browser launched once per worker
test ( 'test 1' , async ({ browser }) => {
const context = await browser . newContext ();
// ...
});
test ( 'test 2' , async ({ browser }) => {
// Reuses same browser from test 1
});
Debugging Parallel Tests
Run Tests Serially
npx playwright test --workers=1
View Worker Output
npx playwright test --workers=1 --reporter=line
Test Info Debugging
test ( 'debug info' , async ({ page }, testInfo ) => {
console . log ({
workerIndex: testInfo . workerIndex ,
parallelIndex: testInfo . parallelIndex ,
retry: testInfo . retry ,
});
});
Common Pitfalls
Shared Mutable State
// BAD: Shared state between tests
let userId : string ;
test ( 'create user' , async ({ request }) => {
const response = await request . post ( '/users' );
userId = ( await response . json ()). id ; // Race condition!
});
test ( 'delete user' , async ({ request }) => {
await request . delete ( `/users/ ${ userId } ` ); // May run before create!
});
// GOOD: Independent tests
test ( 'create user' , async ({ request }) => {
const response = await request . post ( '/users' );
const userId = ( await response . json ()). id ;
await request . delete ( `/users/ ${ userId } ` );
});
File System Operations
// BAD: Same file across tests
test ( 'test 1' , async () => {
fs . writeFileSync ( 'data.json' , '{}' ); // Conflict!
});
test ( 'test 2' , async () => {
fs . writeFileSync ( 'data.json' , '{}' ); // Conflict!
});
// GOOD: Worker-specific files
test ( 'test 1' , async ({ }, testInfo ) => {
const file = `data- ${ testInfo . workerIndex } .json` ;
fs . writeFileSync ( file , '{}' );
});
Database Conflicts
// BAD: Shared database records
test ( 'update user' , async ({ request }) => {
await request . put ( '/users/1' , { name: 'New Name' });
});
test ( 'delete user' , async ({ request }) => {
await request . delete ( '/users/1' ); // Conflicts with update!
});
// GOOD: Unique records per test
test ( 'update user' , async ({ request }) => {
const user = await createUniqueUser ();
await request . put ( `/users/ ${ user . id } ` , { name: 'New Name' });
});
Best Practices
Design for parallelization : Make tests independent
Use appropriate worker count : Balance speed and resources
Avoid shared state : Each test should be self-contained
Use worker-scoped fixtures : For expensive setup
Leverage sharding : For very large test suites
Monitor CI resources : Adjust workers based on available CPU
Use serial mode sparingly : Only when necessary
Example: Optimal Configuration
import { defineConfig } from '@playwright/test' ;
import os from 'os' ;
export default defineConfig ({
// Use 50% of CPUs locally, 100% in CI
workers: process . env . CI ? '100%' : '50%' ,
// Enable full parallelization
fullyParallel: true ,
// Limit failures to stop early
maxFailures: process . env . CI ? 10 : undefined ,
projects: [
{
name: 'setup' ,
testMatch: / . * \. setup \. ts/ ,
workers: 1 , // Setup runs serially
},
{
name: 'chromium' ,
dependencies: [ 'setup' ],
use: { browserName: 'chromium' },
// Inherits workers from global config
},
] ,
}) ;
Next Steps
Retries Learn about test retries
Configuration Configure worker settings