Answer
Playwright Performance Optimization
1. Maximize Parallel Execution
TypeScript
// playwright.config.ts
export default defineConfig({
fullyParallel: true, // All tests parallel
workers: process.env.CI
? 4
: Math.floor(require('os').cpus().length / 2), // Half CPU count locally
});
2. Reuse Authentication State (Biggest Win)
▸Without optimization: 500 tests × login (5s) = 41 minutes just on logins
TypeScript
// Login ONCE, reuse for all tests
projects: [
{ name: 'setup', testMatch: '*.setup.ts' },
{
name: 'e2e',
use: { storageState: '.auth/user.json' },
dependencies: ['setup'],
},
],
▸With storageState: login cost = 5 seconds total (not 5s × 500 tests)
3. Use API for Test Data Setup (Not UI)
TypeScript
// BAD — UI-based setup is slow
test.beforeEach(async ({ page }) => {
await page.goto('/products/new');
await page.fill('#name', 'Test Product');
await page.fill('#price', '99.99');
await page.click('#save');
// 3-5 seconds per test
});
// GOOD — API setup is fast
test.beforeEach(async ({ request }) => {
await request.post('/api/products', {
data: { name: 'Test Product', price: 99.99 },
headers: { Authorization: `Bearer ${process.env.ADMIN_TOKEN}` },
});
// 200ms per test
});
4. Block Unnecessary Network Requests
TypeScript
// Block analytics, ads, and images to speed up page load
await page.route('**/*.{png,jpg,jpeg,gif,webp,svg}', r => r.abort());
await page.route('**/google-analytics.com/**', r => r.abort());
await page.route('**/hotjar.com/**', r => r.abort());
await page.route('**/facebook.com/**', r => r.abort());
5. Use networkidle Sparingly
TypeScript
// BAD — networkidle waits for ALL network activity (500ms of silence)
await page.goto('/products', { waitUntil: 'networkidle' }); // Slow
// GOOD — wait for specific element instead
await page.goto('/products');
await page.locator('.product-card').first().waitFor();
6. Avoid page.waitForTimeout()
TypeScript
// BAD — arbitrary sleep
await page.waitForTimeout(3000);
// GOOD — wait for the actual thing you need
await page.locator('.modal').waitFor({ state: 'hidden' });
await expect(page.locator('.data-loaded')).toBeVisible();
7. Efficient Test Data Strategy
TypeScript
// Use unique data per test to avoid cleanup conflicts
const uniqueEmail = `user-${Date.now()}-${Math.random().toString(36).slice(2)}@test.com`;
// Use a test-specific database schema/namespace
process.env.DB_SCHEMA = `test_${testInfo.workerIndex}`;
8. Shard in CI
YAML
strategy:
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
8 shards × 4 workers = 32 tests running simultaneously.
Performance Checklist
| Optimization | Expected Speedup |
|---|---|
fullyParallel: true | 2-4x |
storageState auth reuse | 3-5x |
| API setup vs UI setup | 5-10x per test |
| Block unnecessary assets | 20-40% page load |
| Sharding (8 machines) | 8x |
Minimize networkidle | 10-30% per navigation |
