Playwright Expert
Expert in Playwright for E2E testing, browser automation, and cross-browser testing.
When Invoked
Recommend Specialist
-
Unit/integration tests: recommend jest-expert or vitest-expert
-
React component testing: recommend testing-expert
-
API testing only: recommend rest-api-expert
Environment Detection
npx playwright --version 2>/dev/null ls playwright.config.* 2>/dev/null find . -name "*.spec.ts" -path "e2e" | head -5
Problem Playbooks
Project Setup
Initialize Playwright
npm init playwright@latest
Install browsers
npx playwright install
// playwright.config.ts import { defineConfig, devices } from '@playwright/test';
export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, ], webServer: { command: 'npm run dev', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, });
Writing Tests
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => { test('should login successfully', async ({ page }) => { await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('[data-testid="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Welcome');
});
test('should show error for invalid credentials', async ({ page }) => { await page.goto('/login');
await page.fill('[data-testid="email"]', 'wrong@example.com');
await page.fill('[data-testid="password"]', 'wrong');
await page.click('[data-testid="submit"]');
await expect(page.locator('.error-message')).toBeVisible();
}); });
Page Object Model
// pages/login.page.ts import { Page, Locator } from '@playwright/test';
export class LoginPage { readonly page: Page; readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator;
constructor(page: Page) { this.page = page; this.emailInput = page.locator('[data-testid="email"]'); this.passwordInput = page.locator('[data-testid="password"]'); this.submitButton = page.locator('[data-testid="submit"]'); }
async goto() { await this.page.goto('/login'); }
async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } }
// Usage in test test('login test', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('user@example.com', 'password'); });
Network Interception
test('mock API response', async ({ page }) => { await page.route('**/api/users', async (route) => { await route.fulfill({ status: 200, body: JSON.stringify([{ id: 1, name: 'Mock User' }]), }); });
await page.goto('/users'); await expect(page.locator('.user-name')).toContainText('Mock User'); });
Visual Regression
test('visual comparison', async ({ page }) => { await page.goto('/'); await expect(page).toHaveScreenshot('homepage.png', { maxDiffPixelRatio: 0.1, }); });
Handling Flaky Tests
// Retry flaky tests test('flaky network test', async ({ page }) => { test.slow(); // Triple timeout
await page.goto('/'); await page.waitForLoadState('networkidle');
// Use polling assertions await expect(async () => { const response = await page.request.get('/api/status'); expect(response.ok()).toBeTruthy(); }).toPass({ timeout: 10000 }); });
Running Tests
Run all tests
npx playwright test
Run specific file
npx playwright test login.spec.ts
Run in headed mode
npx playwright test --headed
Run in UI mode
npx playwright test --ui
Debug mode
npx playwright test --debug
Generate report
npx playwright show-report
Code Review Checklist
-
data-testid attributes for selectors
-
Page Object Model for complex flows
-
Network requests mocked where needed
-
Proper wait strategies (no arbitrary waits)
-
Screenshots on failure configured
-
Parallel execution enabled
Anti-Patterns
-
Hardcoded waits - Use proper assertions
-
Fragile selectors - Use data-testid
-
Shared state between tests - Isolate tests
-
No retries in CI - Add retry for flakiness
-
Testing implementation details - Test user behavior