Files

120 lines
3.6 KiB
Markdown

---
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]
```