e2e-testing · v1.0.0 · 2026-09-15 · sha256 e484a5759c8f4200
e2e-testing v1.0.0A
Immutable. This exact content never changes and is served at /api/v1/blob/e484a5759c8f4200.
---
name: "e2e-testing"
description: 'Design and implement end-to-end tests that validate complete user workflows across the full stack. Use when building browser automation, testing user journeys, cross-browser validation, visual regression testing, or verifying system behavior from the user perspective.'
metadata:
author: "Frontier"
version: "1.0.0"
created: "2026-03-01"
updated: "2026-03-01"
compatibility:
frameworks: ["playwright", "cypress", "selenium", "puppeteer", "testcafe"]
languages: ["typescript", "javascript", "python", "csharp", "java"]
platforms: ["web", "mobile", "desktop", "api"]
---
# End-to-End Testing
> **Purpose**: Validate complete user workflows from UI through backend to database and back.
> **Scope**: Browser automation, user journey testing, cross-browser, visual regression, accessibility.
---
## When to Use This Skill
- Validating user-facing workflows end-to-end
- Building browser automation test suites
- Cross-browser and cross-device testing
- Visual regression testing for UI changes
- Smoke testing after deployments
- Accessibility compliance validation (WCAG 2.1 AA)
## When NOT to Use
- Testing isolated business logic (use unit tests)
- Testing API contract compliance only (use integration testing)
- Load testing under concurrent users (use performance testing)
- Security vulnerability scanning (use security testing)
## Prerequisites
- Application deployed to a test environment
- Test user accounts and credentials configured
- Browser automation framework installed
- Test data seeded or factories available
## Decision Tree
```
What e2e scenario are you testing?
+- User login/auth flow?
| -> Auth E2E (session, tokens, MFA, SSO)
+- Form submission / data entry?
| -> Form E2E (validation, submission, confirmation)
+- Multi-step workflow (checkout, wizard)?
| -> Workflow E2E (state transitions, progress, completion)
+- Search / filter / pagination?
| -> Data E2E (results, sorting, edge cases, empty states)
+- File upload / download?
| -> File E2E (size limits, formats, progress, errors)
+- Real-time updates (WebSocket, SSE)?
| -> Real-time E2E (connection, updates, reconnect)
+- Cross-browser compatibility?
| -> Matrix E2E (Chrome, Firefox, Safari, Edge, mobile)
+- Visual appearance?
| -> Visual regression (screenshot comparison, responsive)
```
---
## Framework Selection
| Framework | Best For | Language | Speed | Reliability |
|-----------|----------|----------|-------|-------------|
| **Playwright** | Modern web apps, cross-browser | TS/JS/Python/C#/Java | Fast | High |
| **Cypress** | Single-domain SPAs, dev experience | JS/TS | Medium | High |
| **Selenium** | Legacy apps, wide browser support | Any | Slow | Medium |
| **Puppeteer** | Chrome-only, PDF/screenshot | JS/TS | Fast | High |
| **TestCafe** | No WebDriver needed, simple setup | JS/TS | Medium | Medium |
**Recommendation**: Use **Playwright** as the default choice for new projects.
---
## Test Structure
### Page Object Model (POM)
```
e2e/
fixtures/ # Test data and setup
pages/ # Page objects (abstraction layer)
login.page.ts
dashboard.page.ts
checkout.page.ts
specs/ # Test specifications
auth.spec.ts
checkout.spec.ts
search.spec.ts
utils/ # Helpers, custom commands
playwright.config.ts
```
### Page Object Pattern
```typescript
// pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
// Locators - resilient selectors
private emailInput = () => this.page.getByLabel('Email');
private passwordInput = () => this.page.getByLabel('Password');
private submitButton = () => this.page.getByRole('button', { name: 'Sign in' });
private errorMessage = () => this.page.getByRole('alert');
// Actions
async login(email: string, password: string): Promise<void> {
await this.emailInput().fill(email);
await this.passwordInput().fill(password);
await this.submitButton().click();
}
// Assertions
async expectError(message: string): Promise<void> {
await expect(this.errorMessage()).toContainText(message);
}
}
```
### Test Specification
```typescript
// specs/auth.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test.describe('Authentication', () => {
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await page.goto('/login');
});
test('should login with valid credentials', async ({ page }) => {
await loginPage.login('user@test.com', 'ValidPass123!');
await expect(page).toHaveURL('/dashboard');
});
test('should show error for invalid credentials', async () => {
await loginPage.login('user@test.com', 'wrong');
await loginPage.expectError('Invalid email or password');
});
test('should redirect unauthenticated users to login', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveURL(/.*login/);
});
});
```
---
## Resilient Selectors
Priority order for element selection (most to least reliable):
| Priority | Selector Type | Example | Reliability |
|----------|--------------|---------|-------------|
| 1 | Test ID | `getByTestId('submit-btn')` | Highest |
| 2 | Role + name | `getByRole('button', { name: 'Submit' })` | High |
| 3 | Label | `getByLabel('Email address')` | High |
| 4 | Placeholder | `getByPlaceholder('Enter email')` | Medium |
| 5 | Text | `getByText('Welcome back')` | Medium |
| 6 | CSS class | `.btn-primary` | Low (fragile) |
| 7 | XPath | `//div[@class="form"]//button` | Lowest |
**Rule**: NEVER use CSS class or XPath selectors in new tests. Use semantic selectors (role, label, test ID).
---
## Cross-Browser Testing
### Browser Matrix
```typescript
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
],
});
```
---
## Visual Regression Testing
```typescript
test('dashboard renders correctly', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixelRatio: 0.01, // 1% tolerance
});
});
```
---
## Accessibility Testing
```typescript
import AxeBuilder from '@axe-core/playwright';
test('page should pass accessibility checks', async ({ page }) => {
await page.goto('/dashboard');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(results.violations).toEqual([]);
});
```
---
## Handling Flaky Tests
| Cause | Solution |
|-------|----------|
| Timing issues | Use `waitFor` / auto-waiting instead of `sleep` |
| Dynamic content | Wait for specific element states, not timers |
| Test data conflicts | Isolate test data per test, use factories |
| Network variability | Mock external services, use `route.fulfill()` |
| Animation | Disable animations in test mode |
| Race conditions | Use `expect` with built-in retry/timeout |
**Rule**: NEVER use `sleep()` or `wait(ms)` in e2e tests. Always wait for a specific condition.
---
## CI Integration
```yaml
# .github/workflows/e2e.yml
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
```
---
## Metrics and Reporting
| Metric | Target | Measurement |
|--------|--------|-------------|
| Pass rate | >= 95% | Passing tests / total tests |
| Execution time | < 15 min total suite | CI pipeline duration |
| Flaky rate | < 2% | Flaky tests / total tests |
| Coverage (user journeys) | 100% critical paths | Mapped to acceptance criteria |
---
## Core Rules
1. **Page Object Model** - Encapsulate page interactions in page objects; tests call methods, not raw selectors.
2. **Semantic Selectors Only** - Use `getByRole`, `getByLabel`, `getByTestId`; never use CSS class or XPath selectors in new tests.
3. **No Sleep Calls** - Never use `sleep()` or `wait(ms)`; always wait for a specific element state or network condition.
4. **Test Isolation** - Each test must be independent; no shared state, no execution-order dependencies.
5. **Critical Paths First** - Cover login, checkout, signup, and core workflows before edge cases.
6. **Environment Independence** - Tests must run against any environment via configuration (local, staging, CI).
7. **Stable Test Data** - Use factories and fixtures for test data; never depend on production data.
8. **Fail-Fast Reporting** - Upload trace/video artifacts on failure; generate HTML reports in CI.
9. **Cross-Browser Matrix** - Run against Chromium, Firefox, and WebKit at minimum; include one mobile viewport.
10. **Accessibility Built-In** - Include axe-core accessibility checks in every critical-path test.
---
## Anti-Patterns
| Don't | Do Instead |
|-------|------------|
| Test every minor UI detail in e2e | Focus on critical user workflows |
| Use `sleep()` for synchronization | Wait for specific conditions |
| Share state between tests | Isolate each test completely |
| Use fragile CSS selectors | Use semantic selectors (role, label, testid) |
| Run e2e tests only before release | Run on every PR in CI |
| Ignore flaky tests | Fix immediately or quarantine |
| Hard-code test data | Use factories and fixtures |
| Skip mobile testing | Include mobile viewports in matrix |