3.6 KiB
3.6 KiB
name, description
| name | description |
|---|---|
| e2e-testing | 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:
- User signup and login flow
- Core feature: the ONE thing this app is built for
- Payment / checkout (if applicable)
- Key data creation, reading, update, deletion
- 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)
// 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)
// 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
- Independent tests: Each test starts fresh (no shared state)
- Deterministic: No flaky timing; use proper waits, not sleep
- Readable: Test names describe behavior, not implementation
- Fast: Parallelize when possible; limit browser contexts
- Data isolation: Use test fixtures, not production data
- Screenshots on failure: Configure in playwright.config.ts
// 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]