vibetunnel/web/docs/playwright-performance.md

408 lines
No EOL
9.7 KiB
Markdown

# Playwright Performance Optimization Guide
## Overview
This guide provides comprehensive strategies for optimizing Playwright test execution speed based on 2024-2025 best practices. Our goal is to achieve significant performance improvements while maintaining test reliability.
## Current Performance Baseline
Before optimization:
- 31 tests taking ~12+ minutes in CI
- Sequential execution with limited parallelization
- No sharding or distributed execution
- Full browser context creation for each test
## Optimization Strategies
### 1. Parallel Execution and Workers
**Impact**: 3-4x speed improvement
```typescript
// playwright.config.ts
export default defineConfig({
// Optimize worker count based on CI environment
workers: process.env.CI ? 4 : undefined,
// Enable full parallelization
fullyParallel: true,
// Limit failures to avoid wasting resources
maxFailures: process.env.CI ? 10 : undefined,
});
```
**Implementation**:
- Use CPU core count for optimal worker allocation
- Enable `fullyParallel` for test-level parallelization
- Set `maxFailures` to stop early on broken builds
### 2. Test Sharding for CI
**Impact**: 4-8x speed improvement with proper distribution
```yaml
# .github/workflows/playwright.yml
strategy:
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- run: pnpm exec playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
```
**Implementation**:
- Split tests across 4 shards for parallel CI execution
- Each shard runs on separate CI job
- Aggregate results after all shards complete
### 3. Browser Context Optimization
**Impact**: 20-30% speed improvement
```typescript
// fixtures/session-fixtures.ts
export const test = base.extend<{
authenticatedContext: BrowserContext;
}>({
authenticatedContext: async ({ browser }, use) => {
// Create context once per worker
const context = await browser.newContext({
storageState: 'tests/auth.json'
});
await use(context);
await context.close();
},
});
```
**Strategies**:
- Reuse authentication state across tests
- Share expensive setup within workers
- Use project-specific contexts for different test types
### 4. Smart Waiting and Selectors
**Impact**: 10-20% speed improvement
```typescript
// Bad: Static waits
await page.waitForTimeout(5000);
// Good: Dynamic waits
await page.waitForSelector('[data-testid="session-ready"]', {
state: 'visible',
timeout: 5000
});
// Better: Wait for specific conditions
await page.waitForFunction(() => {
const terminal = document.querySelector('vibe-terminal');
return terminal?.dataset.ready === 'true';
});
```
**Best Practices**:
- Never use static timeouts
- Use data-testid attributes for fast selection
- Avoid XPath selectors
- Wait for specific application states
### 5. Resource Blocking
**Impact**: 15-25% speed improvement
```typescript
// playwright.config.ts
export default defineConfig({
use: {
// Block unnecessary resources
launchOptions: {
args: ['--disable-web-security', '--disable-features=IsolateOrigins,site-per-process']
},
},
});
// In tests
await context.route('**/*.{png,jpg,jpeg,gif,svg,woff,woff2,ttf}', route => route.abort());
await context.route('**/analytics/**', route => route.abort());
```
### 6. Test Organization and Isolation
**Impact**: 30-40% improvement through smart grouping
```typescript
// Group related tests that can share setup
test.describe('Session Management', () => {
test.beforeAll(async ({ browser }) => {
// Expensive setup once per group
});
test('should create session', async ({ page }) => {
// Test implementation
});
test('should delete session', async ({ page }) => {
// Test implementation
});
});
```
### 7. Global Setup for Authentication
**Impact**: Saves 2-3 seconds per test
```typescript
// global-setup.ts
import { chromium } from '@playwright/test';
async function globalSetup() {
const browser = await chromium.launch();
const page = await browser.newPage();
// Perform authentication
await page.goto('http://localhost:4022');
await page.fill('[name="username"]', 'testuser');
await page.fill('[name="password"]', 'testpass');
await page.click('[type="submit"]');
// Save storage state
await page.context().storageState({ path: 'tests/auth.json' });
await browser.close();
}
export default globalSetup;
```
### 8. CI/CD Optimizations
**Impact**: 50% faster CI builds
```yaml
# Cache Playwright browsers
- uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
# Install only required browsers
- run: pnpm exec playwright install chromium
# Use Linux for CI (faster and cheaper)
runs-on: ubuntu-latest
```
### 9. Headless Mode
**Impact**: 20-30% speed improvement
```typescript
// playwright.config.ts
export default defineConfig({
use: {
headless: true, // Always true in CI
video: process.env.CI ? 'retain-on-failure' : 'off',
screenshot: 'only-on-failure',
},
});
```
### 10. Performance Monitoring
```typescript
// Add performance tracking
test.beforeEach(async ({ page }, testInfo) => {
const startTime = Date.now();
testInfo.attachments.push({
name: 'performance-metrics',
body: JSON.stringify({
test: testInfo.title,
startTime,
}),
contentType: 'application/json',
});
});
```
## Implementation Priority
1. **Phase 1 - Quick Wins** (1-2 hours)
- Enable parallel execution
- Switch to headless mode in CI
- Implement resource blocking
- Optimize selectors
2. **Phase 2 - Medium Impact** (2-4 hours)
- Implement test sharding
- Add global authentication setup
- Optimize browser contexts
- Group related tests
3. **Phase 3 - Advanced** (4-8 hours)
- Implement custom fixtures
- Add performance monitoring
- Optimize CI pipeline
- Fine-tune worker allocation
## Expected Results
With full implementation:
- **Local development**: 3-4x faster (3-4 minutes)
- **CI execution**: 6-8x faster (1.5-2 minutes)
- **Resource usage**: 40% reduction
- **Flakiness**: Significantly reduced
## Monitoring and Maintenance
1. Track test execution times in CI
2. Monitor flaky tests with retry analytics
3. Regular review of slow tests
4. Periodic selector optimization
5. Update Playwright version quarterly
## Common Pitfalls to Avoid
1. Over-parallelization causing resource contention
2. Sharing too much state between tests
3. Using static waits instead of dynamic conditions
4. Not considering CI environment limitations
5. Ignoring test isolation principles
## Sequential Execution Optimizations
Since VibeTunnel tests share system-level terminal resources and cannot run in parallel, we need different optimization strategies:
### 1. Browser Context and Page Reuse
**Impact**: Save 1-2s per test
```typescript
// fixtures/reusable-context.ts
let globalContext: BrowserContext | null = null;
let globalPage: Page | null = null;
export const test = base.extend({
context: async ({ browser }, use) => {
if (!globalContext) {
globalContext = await browser.newContext();
}
await use(globalContext);
// Don't close - reuse for next test
},
page: async ({ context }, use) => {
if (!globalPage || globalPage.isClosed()) {
globalPage = await context.newPage();
} else {
// Clear state for next test
await globalPage.goto('about:blank');
}
await use(globalPage);
// Don't close - reuse for next test
}
});
```
### 2. Smart Test Ordering
Order tests from least destructive to most destructive:
```typescript
test.describe.configure({ mode: 'serial' });
test.describe('1. Read operations', () => {
test('view sessions', async ({ page }) => {});
});
test.describe('2. Create operations', () => {
test('create sessions', async ({ page }) => {});
});
test.describe('3. Destructive operations', () => {
test('kill sessions', async ({ page }) => {});
});
```
### 3. Session Pool with Pre-creation
**Impact**: Save 2-3s per test
```typescript
test.beforeAll(async ({ page }) => {
const pool = new SessionPool(page);
await pool.initialize(5); // Create 5 sessions upfront
global.sessionPool = pool;
});
```
### 4. Aggressive Resource Blocking
```typescript
await context.route('**/*', (route) => {
const url = route.request().url();
const allowedPatterns = ['localhost:4022', '/api/', '.js', '.css'];
if (!allowedPatterns.some(pattern => url.includes(pattern))) {
route.abort();
} else {
route.continue();
}
});
```
### 5. Reduced Timeouts
```typescript
export const TEST_TIMEOUTS = {
QUICK: 1000, // Reduce from 3000
DEFAULT: 2000, // Reduce from 5000
LONG: 5000, // Reduce from 10000
};
```
### 6. Skip Animations
```typescript
await page.addStyleTag({
content: `
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
`
});
```
### Sequential Implementation Plan
**Phase 1 - Immediate (30 mins)**:
- Reduce all timeouts
- Enable headless mode
- Block unnecessary resources
- Skip animations
**Phase 2 - Quick Wins (1-2 hours)**:
- Implement browser/page reuse
- Add smart cleanup
- Optimize waiting strategies
**Phase 3 - Architecture (2-4 hours)**:
- Implement session pool
- Reorganize test order
- Add state persistence
### Expected Results for Sequential Tests
- **Current**: ~12+ minutes
- **Target**: ~3-5 minutes (3-4x improvement)
- **Key gains**:
- Page reuse: Save 1-2s per test
- Reduced timeouts: Save 30-60s total
- Resource blocking: Save 20-30% load time
- Session pool: Save 2-3s per test
## Conclusion
While parallel execution would provide the best performance gains, these sequential optimizations can still achieve significant improvements. Start with quick wins and progressively implement more advanced optimizations based on your specific needs.