--- name: e2e-testing description: End-to-end testing strategy using Playwright or similar frameworks. Covers critical user journeys, visual regression, and cross-browser testing. Use when setting up E2E tests, writing E2E suites, or troubleshooting E2E failures. --- # E2E Testing Skill Based on ECC e2e-testing and Playwright patterns. ## When to Use - Setting up E2E tests for a project - Writing E2E test scenarios - Fixing flaky E2E tests - User requests E2E test implementation ## Strategy ### What to Test E2E Test CRITICAL user journeys only. Not every function. E2E is slow and expensive. Prioritize: 1. User signup and login flow 2. Core feature: the ONE thing this app is built for 3. Payment / checkout (if applicable) 4. Key data creation, reading, update, deletion 5. Error flow: what happens when things break ### What NOT to Test E2E - Individual functions (unit tests) - Internal API contracts (integration tests) - UI component rendering (component tests) - Edge cases that are hard to trigger (unit tests) ## Setup Pattern ### Playwright (Recommended) ```typescript // tests/e2e/login.spec.ts import { test, expect } from '@playwright/test'; test.describe('Authentication', () => { test('user can login with valid credentials', async ({ page }) => { await page.goto('/login'); await page.fill('[name="email"]', 'user@example.com'); await page.fill('[name="password"]', 'correct-password'); await page.click('button[type="submit"]'); await expect(page).toHaveURL('/dashboard'); }); test('login fails with wrong password', async ({ page }) => { await page.goto('/login'); await page.fill('[name="email"]', 'user@example.com'); await page.fill('[name="password"]', 'wrong'); await page.click('button[type="submit"]'); await expect(page.locator('[data-testid="error"]')).toBeVisible(); }); }); ``` ### Page Object Model (For Complex Apps) ```typescript // tests/e2e/pages/LoginPage.ts export class LoginPage { constructor(private page: Page) {} async goto() { await this.page.goto('/login'); } async fillEmail(email: string) { await this.page.fill('[name="email"]', email); } async fillPassword(password: string) { await this.page.fill('[name="password"]', password); } async submit() { await this.page.click('button[type="submit"]'); } async getError() { return this.page.locator('[data-testid="error"]'); } } ``` ## Best Practices 1. **Independent tests**: Each test starts fresh (no shared state) 2. **Deterministic**: No flaky timing; use proper waits, not sleep 3. **Readable**: Test names describe behavior, not implementation 4. **Fast**: Parallelize when possible; limit browser contexts 5. **Data isolation**: Use test fixtures, not production data 6. **Screenshots on failure**: Configure in playwright.config.ts ```typescript // playwright.config.ts export default defineConfig({ use: { screenshot: 'only-on-failure', trace: 'retain-on-failure', }, fullyParallel: true, retries: process.env.CI ? 2 : 0, }); ``` ## Common Anti-Patterns - Testing every possible input combination (do unit tests instead) - Hard-coded sleep/waitFixed (use proper waits on DOM conditions) - Shared test data between tests (creates flakiness) - Testing implementation details instead of user behavior - Too many E2E tests (aim for 20-30 covering critical flows max) ## Report Format ``` ## E2E Test Report Tests: X pass / Y fail / Z skipped Critical flows covered: [list] Flaky tests detected: [list or "none"] Screenshots saved: [path or "none"] Browser: Chromium/Firefox/WebKit (version) Status: All critical flows verified / Issues found: [list] ```