What is Test Parallelism in Playwright
Test parallelism is the ability to execute multiple tests simultaneously on isolated workers, drastically reducing the total execution time of your suite. Playwright supports parallelism through shard strategies, allowing you to distribute your 1000+ tests across multiple processes and machines intelligently and predictably.
This article guides you through the shard strategies supported by Playwright, with practical configurations, trade-offs, and best practices to get the most out of your test suite.
Why Parallelism Matters
Without parallelism, a suite of 500 tests at 100ms each requires ~50 seconds sequentially. With parallelism (10 workers), the same time drops to ~5 seconds: 10x faster.
💡 Benefits of Parallelism
- Faster feedback: developers get test results in seconds, not minutes
- Efficient CI/CD: pipelines complete quickly, infrastructure costs reduced
- Scalability: adding tests doesn't linearly increase execution time
- Isolation: each worker has an isolated browser context, no cross-test contamination
Playwright Shard Strategies
Playwright offers three sharding strategies, each with different trade-offs between simplicity, uniform distribution, and fine-grained control. Let's explore them in detail.
1. Shard per Test File (Default)
The file-based sharding strategy distributes entire test files across different workers. Each file stays assigned to a single worker, guaranteeing cohesion.
{
"testDir": "./tests",
"fullyParallel": true,
"workers": 4,
"shard": [1, 4]
}
In your CI/CD (GitHub Actions, GitLab CI), launch 4 processes, each with a different shard:
jobs:
test:
strategy:
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
Pros: Simple, file cohesion maintained, perfect for <10 test files.
Cons: Unbalanced load if some files have 500 tests and others have 5.
2. Shard per Test (Fine-Grained)
Playwright distributes the load at the single test level, not at the file level. Each test is assigned independently to the shard where it runs, guaranteeing near-uniform distribution.
# Run only tests in shard 2 of 4
npx playwright test --shard=2/4
Internally, Playwright collects all tests (e.g., 800 tests), assigns each test to a shard using deterministic hashing, and each process executes only its shard's tests.
🔍 Technical Details
- Algorithm:
hash(test.file + test.title) % shardTotal === shardIndex - 1 - Result: uniform distribution regardless of file size
- Determinism: same tests always go to the same shard (build repeatability)
3. Shard per Project (Multi-Project)
If your suite uses Playwright projects (e.g., Chrome, Firefox, Safari browsers),
you can schedule each project on different workers using the --grep flag combined with sharding.
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: 4,
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
});
In CI, you can launch 3 containers: one for Chrome, one for Firefox, one for Safari. Each container executes the full suite against a different browser, in parallel.
Managing State Sharing Between Shards
When using parallelism, you can't share state between tests (database, cache) without risking race conditions. Here's the correct pattern:
// test.ts
let sharedCounter = 0; // ❌ Each shard has a different copy
test('test 1', async ({ page }) => {
sharedCounter++;
// Shard 1 reads 1, Shard 2 reads 1 (not 2!)
});
// tests/setup.ts
import { test as base } from '@playwright/test';
type Fixtures = {
shardData: Record;
};
export const test = base.extend({
shardData: async ({ }, use) => {
const shardId = process.env.SHARD_ID || '1';
const data = { shardId, uniqueId: `user_${shardId}_${Date.now()}` };
await use(data);
},
});
// test.ts
import { test } from './setup';
test('test 1', async ({ page, shardData }) => {
console.log(`Shard ${shardData.shardId}, user: ${shardData.uniqueId}`);
// Each shard generates its own data, no conflicts
});
Monitoring and Troubleshooting
Distributing Load Uniformly
If some shards take 2 minutes and others 30 seconds, the bottleneck is the slowest shard. Analyze execution times:
# HTML report shows timing per test
npx playwright show-report
# Alternative: grep the logs
grep "passed\|failed" trace.log | wc -l
echo "Total test count per shard"
If imbalance is excessive, consider:
- Tag slow tests:
@slow, isolate them in a dedicated shard - Internal parallelism: increase
workers: 8on fast workers - Dynamic rebalancing: use tools like Buildkite with
queueto assign tests on-demand
Best Practices
✅ Do's
- Use fine-grained sharding for suites >100 tests
- Isolate database/cache per shard (separate instances, different credentials)
- Monitor shard time in CI, rebalance if outlier >30% average
- Use
fullyParallel: trueto maximize parallelism - Retry only in CI with
retries: 2, no flakiness
❌ Don'ts
- Don't share state between shards (thread-unsafe database, cache conflicts)
- Don't use a fixed global seed (
Math.random()→ unpredictable tests) - Don't assume test execution order (parallelism = non-deterministic order)
Hybrid Strategies: Combining Sharding and Workers
True power emerges when you combine horizontal sharding (across machines/containers) with vertical parallelism (workers on a single machine).
jobs:
test:
strategy:
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ubuntu-latest
steps:
- run: npm ci
- run: npx playwright test \
--shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} \
--workers=2
# Each shard container has 2 local workers
# Total: 4 shards × 2 workers = 8 parallel workers
Complete Configuration in playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: process.env.CI ? 1 : 4,
timeout: 30 * 1000,
expect: { timeout: 5000 },
retries: process.env.CI ? 2 : 0,
reporter: [
['html'],
['github'],
['junit', { outputFile: 'junit.xml' }],
],
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
Conclusion
Test parallelism with shard strategies reduces feedback time from minutes to seconds, lowering CI costs and improving developer experience. Choose the right strategy (file-based for simplicity, fine-grained for uniform load, multi-project for cross-browser) and monitor shard load over time.
🎯 Key Points
- Playwright parallelism reduces execution time by 10x+ with correct sharding
- 3 shard strategies: file-based (simple), fine-grained (uniform), multi-project (cross-browser)
- Isolate state per shard, no shared database or cache across shards
- Monitor load distribution, rebalance if shard is an outlier
Frequently asked questions
What's the difference between file-based and fine-grained sharding?
File-based sharding distributes entire files across shards, fine-grained distributes individual tests. Fine-grained guarantees more uniform load, file-based is simpler for small suites.
Can I share a database between shards?
No. Each shard must have an isolated database instance or separate tables. Sharing a database causes race conditions and unpredictable flakiness.
How can I monitor if load distribution is uniform?
Use the HTML report (npx playwright show-report) to visualize timing per test. If one shard takes significantly longer, rebalance tags or increase workers on that shard.
What's the ideal number of shards and workers?
It depends on your suite size and CI budget. Rule of thumb: num_shard × workers ≤ 2×num_test. Start with 4 shards × 2 workers, monitor total time, scale from there.

Commenti
Caricamento commenti...
Sign in to comment