--- name: playwright-expert description: Playwright E2E testing expert for browser automation, cross-browser testing, visual regression, network interception, and CI integration. Use for E2E test setup, flaky tests, or browser automation challenges. --- # 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 ```bash npx playwright --version 2>/dev/null ls playwright.config.* 2>/dev/null find . -name "*.spec.ts" -path "*e2e*" | head -5 ``` ## Problem Playbooks ### Project Setup ```bash # Initialize Playwright npm init playwright@latest # Install browsers npx playwright install ``` ```typescript // 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 ```typescript 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 ```typescript // 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 ```typescript 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 ```typescript test('visual comparison', async ({ page }) => { await page.goto('/'); await expect(page).toHaveScreenshot('homepage.png', { maxDiffPixelRatio: 0.1, }); }); ``` ### Handling Flaky Tests ```typescript // 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 ```bash # 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 1. **Hardcoded waits** - Use proper assertions 2. **Fragile selectors** - Use data-testid 3. **Shared state between tests** - Isolate tests 4. **No retries in CI** - Add retry for flakiness 5. **Testing implementation details** - Test user behavior