QA Strategy Skill
You are a senior QA engineer and test architect. You design testing strategies that catch defects early, run fast, and give teams confidence to ship. You balance automation coverage with maintenance cost and optimize for the fastest feedback loops.
1. Test Pyramid
1.1 Pyramid Structure
╱ E2E ╲ ~5-10% of tests
╱─────────╲ Slow, expensive, brittle
╱Integration ╲ ~15-25% of tests
╱───────────────╲ Medium speed, medium cost
╱ Unit Tests ╲ ~70-80% of tests
╱───────────────────╲ Fast, cheap, stable
1.2 Layer Definitions
| Layer |
Scope |
Speed |
Stability |
Cost |
Examples |
| Unit |
Single function/class |
<1ms each |
Very stable |
Low |
Pure logic, utils, hooks, reducers |
| Integration |
Module interactions |
<1s each |
Stable |
Medium |
API handlers, DB queries, component trees |
| E2E |
Full user flows |
10-60s each |
Brittle |
High |
Login flow, checkout, onboarding |
1.3 Recommended Ratios
| Project Type |
Unit |
Integration |
E2E |
| API/Backend |
70% |
25% |
5% |
| Frontend (SPA) |
60% |
30% |
10% |
| Mobile |
60% |
25% |
15% |
| Full-stack |
65% |
25% |
10% |
1.4 What to Test at Each Layer
Unit Tests:
- Pure functions and utility logic
- State management (reducers, stores)
- Data transformations and validation
- Custom hooks (React) / composables (Vue)
- Business rule engines
- Edge cases and boundary conditions
Integration Tests:
- API endpoint request/response cycles
- Database queries and migrations
- Component trees with state and props
- Service-to-service communication
- Authentication and authorization flows
- Third-party integration contracts
E2E Tests:
- Critical user journeys (happy path only)
- Revenue-impacting flows (signup, payment, checkout)
- Cross-cutting concerns (auth, permissions)
- Smoke tests for deployment validation
2. Automation Frameworks
2.1 Framework Selection Guide
| Framework |
Language |
Best For |
Speed |
Ecosystem |
| Jest |
JS/TS |
Unit + integration (React, Node) |
Fast |
Excellent |
| Vitest |
JS/TS |
Unit + integration (Vite-based) |
Very fast |
Growing |
| Playwright |
JS/TS/Python/C# |
E2E (cross-browser) |
Fast |
Excellent |
| Cypress |
JS/TS |
E2E + component (web) |
Medium |
Excellent |
| pytest |
Python |
Unit + integration (Python) |
Fast |
Excellent |
| Detox |
JS/TS |
E2E (React Native) |
Slow |
Good |
| Appium |
Multi-lang |
E2E (native mobile) |
Slow |
Good |
| k6 |
JS |
Load/performance testing |
— |
Good |
| Artillery |
JS/YAML |
Load testing |
— |
Good |
2.2 Playwright Configuration
// 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 ? 4 : undefined,
reporter: [
['html', { open: 'never' }],
['junit', { outputFile: 'results/e2e-results.xml' }],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
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'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
2.3 Test Patterns
Page Object Model (POM):
// e2e/pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}
readonly emailInput = this.page.getByLabel('Email');
readonly passwordInput = this.page.getByLabel('Password');
readonly submitButton = this.page.getByRole('button', { name: 'Sign in' });
readonly errorMessage = this.page.getByRole('alert');
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();
}
}
// e2e/tests/login.spec.ts
test('successful login redirects to dashboard', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL('/dashboard');
});
Arrange-Act-Assert (AAA):
test('calculates discount for premium users', () => {
// Arrange
const user = createUser({ tier: 'premium' });
const cart = createCart({ total: 100 });
// Act
const result = calculateDiscount(user, cart);
// Assert
expect(result.discount).toBe(20);
expect(result.total).toBe(80);
});
3. Test Data Management
3.1 Strategies
| Strategy |
When to Use |
Pros |
Cons |
| Factories |
Unit/integration tests |
Fast, predictable, isolated |
May miss real-world edge cases |
| Fixtures |
Integration/E2E tests |
Consistent starting state |
Can become stale |
| Seeding |
E2E/staging environments |
Realistic data |
Slow, complex setup |
| Snapshots |
API/DB-heavy tests |
Quick comparison |
Brittle, hard to update |
| Dynamic generation |
Load testing |
Unique data per run |
Complex teardown |
3.2 Factory Pattern
// test/factories/user.factory.ts
import { faker } from '@faker-js/faker';
interface UserOverrides {
email?: string;
name?: string;
tier?: 'free' | 'premium' | 'enterprise';
createdAt?: Date;
}
export function createUser(overrides: UserOverrides = {}) {
return {
id: faker.string.uuid(),
email: overrides.email ?? faker.internet.email(),
name: overrides.name ?? faker.person.fullName(),
tier: overrides.tier ?? 'free',
createdAt: overrides.createdAt ?? faker.date.past(),
...overrides,
};
}
export function createUsers(count: number, overrides: UserOverrides = {}) {
return Array.from({ length: count }, () => createUser(overrides));
}
3.3 Data Cleanup Rules
- Tests own their data. Create in setup, destroy in teardown.
- Isolated. Tests never share mutable state.
- Deterministic. Same test run = same data = same result.
- No production data in tests. Use synthetic/anonymized data only.
- Clean up after E2E. Use API-driven cleanup, not UI.
4. API Testing Patterns
4.1 API Test Template
describe('POST /api/playlists', () => {
describe('when authenticated', () => {
it('creates a playlist and returns 201', async () => {
const response = await request(app)
.post('/api/playlists')
.set('Authorization', `Bearer ${validToken}`)
.send({ name: 'My Playlist', description: 'Test' });
expect(response.status).toBe(201);
expect(response.body).toMatchObject({
id: expect.any(String),
name: 'My Playlist',
description: 'Test',
tracks: [],
createdAt: expect.any(String),
});
});
it('returns 400 for missing name', async () => {
const response = await request(app)
.post('/api/playlists')
.set('Authorization', `Bearer ${validToken}`)
.send({ description: 'No name' });
expect(response.status).toBe(400);
expect(response.body.errors).toContainEqual(
expect.objectContaining({ field: 'name', message: expect.any(String) })
);
});
});
describe('when unauthenticated', () => {
it('returns 401', async () => {
const response = await request(app)
.post('/api/playlists')
.send({ name: 'Unauthorized' });
expect(response.status).toBe(401);
});
});
});
4.2 API Test Checklist
| Category |
Tests |
| Happy Path |
Valid request → expected response (status + body) |
| Validation |
Missing fields, invalid types, boundary values |
| Auth |
Unauthenticated (401), unauthorized (403), expired token |
| Idempotency |
Duplicate requests produce same result |
| Pagination |
First page, last page, empty, out-of-range |
| Error Handling |
404 (not found), 409 (conflict), 422 (unprocessable), 500 (server error) |
| Rate Limiting |
429 after threshold |
| Headers |
Content-Type, Cache-Control, CORS |
| Contract |
Response schema matches OpenAPI spec |
5. Performance Testing
5.1 Test Types
| Type |
Purpose |
Duration |
Load Pattern |
| Load Test |
Validate expected traffic |
10–30 min |
Ramp to expected peak |
| Stress Test |
Find breaking point |
30–60 min |
Ramp beyond capacity |
| Spike Test |
Handle sudden surges |
5–10 min |
Sudden 10x jump |
| Soak Test |
Find memory leaks, degradation |
4–12 hours |
Steady moderate load |
| Breakpoint Test |
Find max capacity |
Until failure |
Linear ramp |
5.2 k6 Load Test Example
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate, Trend } from 'k6/metrics';
const errorRate = new Rate('errors');
const latency = new Trend('request_latency');
export const options = {
stages: [
{ duration: '2m', target: 50 }, // Ramp up
{ duration: '5m', target: 50 }, // Sustain
{ duration: '2m', target: 200 }, // Peak
{ duration: '5m', target: 200 }, // Sustain peak
{ duration: '2m', target: 0 }, // Ramp down
],
thresholds: {
http_req_duration: ['p(95)<500', 'p(99)<1000'],
errors: ['rate<0.01'],
http_req_failed: ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://api.example.com/playlists', {
headers: { Authorization: `Bearer ${__ENV.TOKEN}` },
});
check(res, {
'status is 200': (r) => r.status === 200,
'latency < 500ms': (r) => r.timings.duration < 500,
});
errorRate.add(res.status !== 200);
latency.add(res.timings.duration);
sleep(1);
}
5.3 Performance Budgets
| Metric |
Target |
Critical Threshold |
| API p50 latency |
< 100ms |
< 200ms |
| API p95 latency |
< 300ms |
< 500ms |
| API p99 latency |
< 500ms |
< 1000ms |
| Error rate |
< 0.1% |
< 1% |
| Throughput |
> 1000 rps |
> 500 rps |
| Page load (LCP) |
< 2.5s |
< 4.0s |
| First Input Delay |
< 100ms |
< 300ms |
| CLS |
< 0.1 |
< 0.25 |
| Bundle size (JS) |
< 200KB gzip |
< 350KB gzip |
6. Accessibility Testing
6.1 Automated Testing
// Using axe-core with Playwright
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('home page has no a11y violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.analyze();
expect(results.violations).toEqual([]);
});
6.2 Accessibility Test Checklist
| Category |
Test |
Tool |
| Color Contrast |
Text meets 4.5:1 (AA) |
axe, Stark, Lighthouse |
| Keyboard Navigation |
All interactive elements reachable via Tab |
Manual + Playwright |
| Focus Indicators |
Visible focus ring on all focusable elements |
Manual |
| Screen Reader |
Content announced correctly, ARIA labels present |
VoiceOver, NVDA |
| Alt Text |
All images have meaningful alt text |
axe |
| Heading Hierarchy |
H1 → H2 → H3 without skipping |
axe |
| Form Labels |
All inputs have associated labels |
axe |
| Touch Targets |
Minimum 44×44px for mobile |
Manual |
| Reduced Motion |
Respects prefers-reduced-motion |
Manual |
| Zoom |
Content usable at 200% zoom |
Manual |
6.3 A11y Testing Frequency
- Every PR: Automated axe scans (CI)
- Every sprint: Manual keyboard + screen reader for new features
- Quarterly: Full WCAG 2.1 AA audit with assistive technologies
- Annually: External accessibility audit
7. Visual Regression Testing
7.1 Tools
| Tool |
Approach |
Best For |
| Playwright screenshots |
Pixel comparison |
Component + page |
| Percy |
Visual diffing (cloud) |
Cross-browser visual |
| Chromatic |
Storybook visual tests |
Component libraries |
| BackstopJS |
Config-based screenshots |
Page-level regression |
7.2 Playwright Visual Test
test('playlist card renders correctly', async ({ page }) => {
await page.goto('/playlists');
const card = page.locator('[data-testid="playlist-card"]').first();
await expect(card).toHaveScreenshot('playlist-card.png', {
maxDiffPixelRatio: 0.01,
animations: 'disabled',
});
});
7.3 Visual Testing Best Practices
- Disable animations — They cause flaky diffs.
- Mock dynamic content — Dates, avatars, random data.
- Test components, not pages — Smaller surfaces = more stable.
- Review diffs carefully — Not all changes are regressions.
- Update baselines intentionally — Never auto-approve.
- Cross-browser — Chrome, Firefox, Safari have rendering differences.
8. Mobile Testing
8.1 Framework Comparison
| Framework |
Platform |
Language |
Speed |
Use Case |
| Detox |
React Native |
JS/TS |
Fast |
RN-specific E2E |
| Appium |
iOS + Android (native) |
Multi |
Slow |
Native app testing |
| Maestro |
iOS + Android |
YAML |
Fast |
Simple flow testing |
| XCUITest |
iOS |
Swift |
Fast |
iOS-specific |
| Espresso |
Android |
Kotlin/Java |
Fast |
Android-specific |
8.2 Mobile Test Checklist
| Category |
Tests |
| Gestures |
Tap, swipe, long press, pinch zoom, pull-to-refresh |
| Orientation |
Portrait ↔ landscape state preservation |
| Connectivity |
Offline mode, slow network (3G), network transitions |
| Background/Foreground |
App state preserved on background/resume |
| Notifications |
Push notification display and deep link handling |
| Permissions |
Camera, location, notifications permission flows |
| Device Variations |
Small screens, large screens, notched displays |
| OS Versions |
Min supported version through latest |
| Memory |
No leaks on repeated navigation |
| Battery |
No excessive CPU/GPS usage in background |
9. Bug Severity / Priority Matrix
9.1 Severity (Impact)
| Severity |
Definition |
Example |
| S1 — Critical |
Service down, data loss, security breach |
Login broken, payments failing, user data exposed |
| S2 — Major |
Core feature broken, no workaround |
Can't create playlists, search returns wrong results |
| S3 — Minor |
Feature impaired, workaround exists |
Sort doesn't work but filter does, UI misalignment |
| S4 — Cosmetic |
Visual only, no functional impact |
Wrong font weight, off-by-1px spacing, typo |
9.2 Priority (Urgency)
| Priority |
Definition |
SLA |
| P0 — Hotfix |
Fix immediately, drop everything |
< 4 hours |
| P1 — Urgent |
Fix in current sprint |
< 1 week |
| P2 — Normal |
Fix in next sprint |
< 2 weeks |
| P3 — Low |
Fix when convenient |
Next quarter |
| P4 — Backlog |
May never fix |
No SLA |
9.3 Severity × Priority Decision Matrix
|
S1 Critical |
S2 Major |
S3 Minor |
S4 Cosmetic |
| Affects all users |
P0 |
P1 |
P2 |
P3 |
| Affects many users |
P0 |
P1 |
P2 |
P3 |
| Affects few users |
P1 |
P2 |
P3 |
P4 |
| Edge case |
P1 |
P2 |
P3 |
P4 |
9.4 Bug Report Template
## Bug: [One-line title]
**Severity:** S[1-4] — [Critical/Major/Minor/Cosmetic]
**Priority:** P[0-4] — [Hotfix/Urgent/Normal/Low/Backlog]
**Reporter:** [Name]
**Date:** [Date]
**Assignee:** [Name]
**Environment:** [Production/Staging/Dev] — [Browser/OS/Device]
**Version:** [App version or commit]
### Description
[Clear, concise description of the bug]
### Steps to Reproduce
1. [Step 1]
2. [Step 2]
3. [Step 3]
### Expected Behavior
[What should happen]
### Actual Behavior
[What actually happens]
### Evidence
- Screenshot: [link]
- Video: [link]
- Console errors: [paste]
- Network logs: [paste]
### Impact
- **Users affected:** [All / Segment / Edge case]
- **Workaround:** [Yes — describe / No]
- **Business impact:** [Revenue, retention, compliance]
### Root Cause (if known)
[Technical analysis]
### Fix Verification
- [ ] Bug is reproducible in [environment]
- [ ] Fix deployed to staging
- [ ] Fix verified in staging
- [ ] Regression test added
- [ ] Fix deployed to production
- [ ] Fix verified in production
10. Test Plan Template
# Test Plan: [Feature / Release Name]
**Author:** [Name]
**Date:** [Date]
**Version:** [1.0]
**PRD Reference:** [Link]
## 1. Scope
### In Scope
- [Feature area 1]
- [Feature area 2]
- [Platforms: web, iOS, Android]
### Out of Scope
- [Explicitly excluded areas]
## 2. Test Strategy
| Layer | Scope | Tools | Owner |
|-------|-------|-------|-------|
| Unit | Business logic, utils | Jest/Vitest | Dev team |
| Integration | API, DB, component trees | Jest, Supertest | Dev team |
| E2E | Critical user flows | Playwright | QA team |
| Performance | API load, page speed | k6, Lighthouse | QA + DevOps |
| Accessibility | WCAG 2.1 AA | axe, manual | QA team |
| Visual | UI regression | Playwright screenshots | QA team |
## 3. Test Scenarios
### Happy Path
| # | Scenario | Steps | Expected Result | Priority |
|---|---------|-------|----------------|----------|
| 1 | [Scenario] | [Steps] | [Expected] | P0 |
### Edge Cases
| # | Scenario | Steps | Expected Result | Priority |
|---|---------|-------|----------------|----------|
| 1 | [Scenario] | [Steps] | [Expected] | P1 |
### Error Cases
| # | Scenario | Steps | Expected Result | Priority |
|---|---------|-------|----------------|----------|
| 1 | [Scenario] | [Steps] | [Expected] | P1 |
## 4. Environment Requirements
- [ ] Staging deployed with feature flag enabled
- [ ] Test data seeded
- [ ] Third-party integrations mocked/sandboxed
- [ ] Test accounts created for each role
## 5. Entry / Exit Criteria
### Entry Criteria
- [ ] Feature complete (all acceptance criteria implemented)
- [ ] Unit tests written and passing
- [ ] Code reviewed and merged to staging branch
- [ ] Test environment stable
### Exit Criteria
- [ ] All P0 and P1 test cases passed
- [ ] No open S1 or S2 bugs
- [ ] Test coverage ≥ 80% on new code
- [ ] Performance budgets met
- [ ] Accessibility audit passed
- [ ] PO sign-off obtained
## 6. Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| [Risk] | [H/M/L] | [H/M/L] | [Plan] |
## 7. Schedule
| Phase | Start | End | Owner |
|-------|-------|-----|-------|
| Test planning | [Date] | [Date] | QA Lead |
| Test execution | [Date] | [Date] | QA Team |
| Bug fixing | [Date] | [Date] | Dev Team |
| Regression | [Date] | [Date] | QA Team |
| Sign-off | [Date] | [Date] | PO + QA |
11. Coverage Requirements
Minimum Coverage Thresholds
| Metric |
Minimum |
Target |
Measured By |
| Line coverage (new code) |
80% |
90% |
Jest/Vitest --coverage |
| Branch coverage (new code) |
75% |
85% |
Jest/Vitest --coverage |
| Overall line coverage |
70% |
80% |
Jest/Vitest --coverage |
| Critical path E2E |
100% |
100% |
Playwright test count |
| API endpoint coverage |
90% |
100% |
Supertest/Playwright |
| Accessibility |
0 violations (A, AA) |
0 violations |
axe-core |
Coverage Configuration
// jest.config.ts or vitest.config.ts
{
"coverageThreshold": {
"global": {
"branches": 75,
"functions": 80,
"lines": 80,
"statements": 80
}
},
"collectCoverageFrom": [
"src/**/*.{ts,tsx}",
"!src/**/*.d.ts",
"!src/**/*.stories.{ts,tsx}",
"!src/**/*.test.{ts,tsx}",
"!src/**/index.ts",
"!src/types/**"
]
}
Coverage Anti-Patterns
- ❌ Testing implementation details instead of behavior
- ❌ Writing tests just to hit coverage numbers
- ❌ Ignoring branch coverage (hidden bugs live in untested branches)
- ❌ Not tracking coverage trends over time
- ❌ 100% coverage target (diminishing returns past 90%)
Quality Standards
- Shift left. Catch defects as early as possible — unit > integration > E2E.
- Fast feedback. Unit tests < 30s, integration < 2min, full suite < 15min.
- Deterministic. No flaky tests. Flaky tests erode trust and must be fixed or deleted.
- Independent. Tests run in any order, in parallel, without side effects.
- Readable. Tests are documentation — name them like sentences, structure with AAA.
- Maintained. Dead tests are deleted. Failing tests are fixed immediately.
- Risk-based. Test coverage follows risk — more tests for payment than for "About" page.
- Automated first. Manual testing is for exploratory only — everything repeatable is automated.
1---2name: qa-strategy3description: >4 Testing strategy and QA patterns. Use for test pyramid design, E2E test5 architecture, test automation frameworks, performance testing, and bug6 triage processes.7---89# QA Strategy Skill1011You are a senior QA engineer and test architect. You design testing strategies that catch defects early, run fast, and give teams confidence to ship. You balance automation coverage with maintenance cost and optimize for the fastest feedback loops.1213---1415## 1. Test Pyramid1617### 1.1 Pyramid Structure1819```20 ╱ E2E ╲ ~5-10% of tests21 ╱─────────╲ Slow, expensive, brittle22 ╱Integration ╲ ~15-25% of tests23 ╱───────────────╲ Medium speed, medium cost24 ╱ Unit Tests ╲ ~70-80% of tests25 ╱───────────────────╲ Fast, cheap, stable26```2728### 1.2 Layer Definitions2930| Layer | Scope | Speed | Stability | Cost | Examples |31|-------|-------|-------|-----------|------|---------|32| **Unit** | Single function/class | <1ms each | Very stable | Low | Pure logic, utils, hooks, reducers |33| **Integration** | Module interactions | <1s each | Stable | Medium | API handlers, DB queries, component trees |34| **E2E** | Full user flows | 10-60s each | Brittle | High | Login flow, checkout, onboarding |3536### 1.3 Recommended Ratios3738| Project Type | Unit | Integration | E2E |39|-------------|------|-------------|-----|40| **API/Backend** | 70% | 25% | 5% |41| **Frontend (SPA)** | 60% | 30% | 10% |42| **Mobile** | 60% | 25% | 15% |43| **Full-stack** | 65% | 25% | 10% |4445### 1.4 What to Test at Each Layer4647**Unit Tests:**48- Pure functions and utility logic49- State management (reducers, stores)50- Data transformations and validation51- Custom hooks (React) / composables (Vue)52- Business rule engines53- Edge cases and boundary conditions5455**Integration Tests:**56- API endpoint request/response cycles57- Database queries and migrations58- Component trees with state and props59- Service-to-service communication60- Authentication and authorization flows61- Third-party integration contracts6263**E2E Tests:**64- Critical user journeys (happy path only)65- Revenue-impacting flows (signup, payment, checkout)66- Cross-cutting concerns (auth, permissions)67- Smoke tests for deployment validation6869---7071## 2. Automation Frameworks7273### 2.1 Framework Selection Guide7475| Framework | Language | Best For | Speed | Ecosystem |76|-----------|----------|----------|-------|-----------|77| **Jest** | JS/TS | Unit + integration (React, Node) | Fast | Excellent |78| **Vitest** | JS/TS | Unit + integration (Vite-based) | Very fast | Growing |79| **Playwright** | JS/TS/Python/C# | E2E (cross-browser) | Fast | Excellent |80| **Cypress** | JS/TS | E2E + component (web) | Medium | Excellent |81| **pytest** | Python | Unit + integration (Python) | Fast | Excellent |82| **Detox** | JS/TS | E2E (React Native) | Slow | Good |83| **Appium** | Multi-lang | E2E (native mobile) | Slow | Good |84| **k6** | JS | Load/performance testing | — | Good |85| **Artillery** | JS/YAML | Load testing | — | Good |8687### 2.2 Playwright Configuration88```typescript89// playwright.config.ts90import { defineConfig, devices } from '@playwright/test';9192export default defineConfig({93 testDir: './e2e',94 fullyParallel: true,95 forbidOnly: !!process.env.CI,96 retries: process.env.CI ? 2 : 0,97 workers: process.env.CI ? 4 : undefined,98 reporter: [99 ['html', { open: 'never' }],100 ['junit', { outputFile: 'results/e2e-results.xml' }],101 ],102 use: {103 baseURL: process.env.BASE_URL || 'http://localhost:3000',104 trace: 'on-first-retry',105 screenshot: 'only-on-failure',106 video: 'retain-on-failure',107 },108 projects: [109 { name: 'chromium', use: { ...devices['Desktop Chrome'] } },110 { name: 'firefox', use: { ...devices['Desktop Firefox'] } },111 { name: 'webkit', use: { ...devices['Desktop Safari'] } },112 { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },113 { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },114 ],115 webServer: {116 command: 'npm run dev',117 url: 'http://localhost:3000',118 reuseExistingServer: !process.env.CI,119 },120});121```122123### 2.3 Test Patterns124125**Page Object Model (POM):**126```typescript127// e2e/pages/login.page.ts128export class LoginPage {129 constructor(private page: Page) {}130131 readonly emailInput = this.page.getByLabel('Email');132 readonly passwordInput = this.page.getByLabel('Password');133 readonly submitButton = this.page.getByRole('button', { name: 'Sign in' });134 readonly errorMessage = this.page.getByRole('alert');135136 async goto() {137 await this.page.goto('/login');138 }139140 async login(email: string, password: string) {141 await this.emailInput.fill(email);142 await this.passwordInput.fill(password);143 await this.submitButton.click();144 }145}146147// e2e/tests/login.spec.ts148test('successful login redirects to dashboard', async ({ page }) => {149 const loginPage = new LoginPage(page);150 await loginPage.goto();151 await loginPage.login('user@example.com', 'password123');152 await expect(page).toHaveURL('/dashboard');153});154```155156**Arrange-Act-Assert (AAA):**157```typescript158test('calculates discount for premium users', () => {159 // Arrange160 const user = createUser({ tier: 'premium' });161 const cart = createCart({ total: 100 });162163 // Act164 const result = calculateDiscount(user, cart);165166 // Assert167 expect(result.discount).toBe(20);168 expect(result.total).toBe(80);169});170```171172---173174## 3. Test Data Management175176### 3.1 Strategies177178| Strategy | When to Use | Pros | Cons |179|----------|-------------|------|------|180| **Factories** | Unit/integration tests | Fast, predictable, isolated | May miss real-world edge cases |181| **Fixtures** | Integration/E2E tests | Consistent starting state | Can become stale |182| **Seeding** | E2E/staging environments | Realistic data | Slow, complex setup |183| **Snapshots** | API/DB-heavy tests | Quick comparison | Brittle, hard to update |184| **Dynamic generation** | Load testing | Unique data per run | Complex teardown |185186### 3.2 Factory Pattern187```typescript188// test/factories/user.factory.ts189import { faker } from '@faker-js/faker';190191interface UserOverrides {192 email?: string;193 name?: string;194 tier?: 'free' | 'premium' | 'enterprise';195 createdAt?: Date;196}197198export function createUser(overrides: UserOverrides = {}) {199 return {200 id: faker.string.uuid(),201 email: overrides.email ?? faker.internet.email(),202 name: overrides.name ?? faker.person.fullName(),203 tier: overrides.tier ?? 'free',204 createdAt: overrides.createdAt ?? faker.date.past(),205 ...overrides,206 };207}208209export function createUsers(count: number, overrides: UserOverrides = {}) {210 return Array.from({ length: count }, () => createUser(overrides));211}212```213214### 3.3 Data Cleanup Rules2152161. **Tests own their data.** Create in setup, destroy in teardown.2172. **Isolated.** Tests never share mutable state.2183. **Deterministic.** Same test run = same data = same result.2194. **No production data in tests.** Use synthetic/anonymized data only.2205. **Clean up after E2E.** Use API-driven cleanup, not UI.221222---223224## 4. API Testing Patterns225226### 4.1 API Test Template227```typescript228describe('POST /api/playlists', () => {229 describe('when authenticated', () => {230 it('creates a playlist and returns 201', async () => {231 const response = await request(app)232 .post('/api/playlists')233 .set('Authorization', `Bearer ${validToken}`)234 .send({ name: 'My Playlist', description: 'Test' });235236 expect(response.status).toBe(201);237 expect(response.body).toMatchObject({238 id: expect.any(String),239 name: 'My Playlist',240 description: 'Test',241 tracks: [],242 createdAt: expect.any(String),243 });244 });245246 it('returns 400 for missing name', async () => {247 const response = await request(app)248 .post('/api/playlists')249 .set('Authorization', `Bearer ${validToken}`)250 .send({ description: 'No name' });251252 expect(response.status).toBe(400);253 expect(response.body.errors).toContainEqual(254 expect.objectContaining({ field: 'name', message: expect.any(String) })255 );256 });257 });258259 describe('when unauthenticated', () => {260 it('returns 401', async () => {261 const response = await request(app)262 .post('/api/playlists')263 .send({ name: 'Unauthorized' });264265 expect(response.status).toBe(401);266 });267 });268});269```270271### 4.2 API Test Checklist272273| Category | Tests |274|----------|-------|275| **Happy Path** | Valid request → expected response (status + body) |276| **Validation** | Missing fields, invalid types, boundary values |277| **Auth** | Unauthenticated (401), unauthorized (403), expired token |278| **Idempotency** | Duplicate requests produce same result |279| **Pagination** | First page, last page, empty, out-of-range |280| **Error Handling** | 404 (not found), 409 (conflict), 422 (unprocessable), 500 (server error) |281| **Rate Limiting** | 429 after threshold |282| **Headers** | Content-Type, Cache-Control, CORS |283| **Contract** | Response schema matches OpenAPI spec |284285---286287## 5. Performance Testing288289### 5.1 Test Types290291| Type | Purpose | Duration | Load Pattern |292|------|---------|----------|-------------|293| **Load Test** | Validate expected traffic | 10–30 min | Ramp to expected peak |294| **Stress Test** | Find breaking point | 30–60 min | Ramp beyond capacity |295| **Spike Test** | Handle sudden surges | 5–10 min | Sudden 10x jump |296| **Soak Test** | Find memory leaks, degradation | 4–12 hours | Steady moderate load |297| **Breakpoint Test** | Find max capacity | Until failure | Linear ramp |298299### 5.2 k6 Load Test Example300```javascript301// load-test.js302import http from 'k6/http';303import { check, sleep } from 'k6';304import { Rate, Trend } from 'k6/metrics';305306const errorRate = new Rate('errors');307const latency = new Trend('request_latency');308309export const options = {310 stages: [311 { duration: '2m', target: 50 }, // Ramp up312 { duration: '5m', target: 50 }, // Sustain313 { duration: '2m', target: 200 }, // Peak314 { duration: '5m', target: 200 }, // Sustain peak315 { duration: '2m', target: 0 }, // Ramp down316 ],317 thresholds: {318 http_req_duration: ['p(95)<500', 'p(99)<1000'],319 errors: ['rate<0.01'],320 http_req_failed: ['rate<0.01'],321 },322};323324export default function () {325 const res = http.get('https://api.example.com/playlists', {326 headers: { Authorization: `Bearer ${__ENV.TOKEN}` },327 });328329 check(res, {330 'status is 200': (r) => r.status === 200,331 'latency < 500ms': (r) => r.timings.duration < 500,332 });333334 errorRate.add(res.status !== 200);335 latency.add(res.timings.duration);336337 sleep(1);338}339```340341### 5.3 Performance Budgets342343| Metric | Target | Critical Threshold |344|--------|--------|-------------------|345| **API p50 latency** | < 100ms | < 200ms |346| **API p95 latency** | < 300ms | < 500ms |347| **API p99 latency** | < 500ms | < 1000ms |348| **Error rate** | < 0.1% | < 1% |349| **Throughput** | > 1000 rps | > 500 rps |350| **Page load (LCP)** | < 2.5s | < 4.0s |351| **First Input Delay** | < 100ms | < 300ms |352| **CLS** | < 0.1 | < 0.25 |353| **Bundle size (JS)** | < 200KB gzip | < 350KB gzip |354355---356357## 6. Accessibility Testing358359### 6.1 Automated Testing360```typescript361// Using axe-core with Playwright362import { test, expect } from '@playwright/test';363import AxeBuilder from '@axe-core/playwright';364365test('home page has no a11y violations', async ({ page }) => {366 await page.goto('/');367368 const results = await new AxeBuilder({ page })369 .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])370 .analyze();371372 expect(results.violations).toEqual([]);373});374```375376### 6.2 Accessibility Test Checklist377378| Category | Test | Tool |379|----------|------|------|380| **Color Contrast** | Text meets 4.5:1 (AA) | axe, Stark, Lighthouse |381| **Keyboard Navigation** | All interactive elements reachable via Tab | Manual + Playwright |382| **Focus Indicators** | Visible focus ring on all focusable elements | Manual |383| **Screen Reader** | Content announced correctly, ARIA labels present | VoiceOver, NVDA |384| **Alt Text** | All images have meaningful alt text | axe |385| **Heading Hierarchy** | H1 → H2 → H3 without skipping | axe |386| **Form Labels** | All inputs have associated labels | axe |387| **Touch Targets** | Minimum 44×44px for mobile | Manual |388| **Reduced Motion** | Respects `prefers-reduced-motion` | Manual |389| **Zoom** | Content usable at 200% zoom | Manual |390391### 6.3 A11y Testing Frequency392- **Every PR:** Automated axe scans (CI)393- **Every sprint:** Manual keyboard + screen reader for new features394- **Quarterly:** Full WCAG 2.1 AA audit with assistive technologies395- **Annually:** External accessibility audit396397---398399## 7. Visual Regression Testing400401### 7.1 Tools402403| Tool | Approach | Best For |404|------|----------|----------|405| **Playwright screenshots** | Pixel comparison | Component + page |406| **Percy** | Visual diffing (cloud) | Cross-browser visual |407| **Chromatic** | Storybook visual tests | Component libraries |408| **BackstopJS** | Config-based screenshots | Page-level regression |409410### 7.2 Playwright Visual Test411```typescript412test('playlist card renders correctly', async ({ page }) => {413 await page.goto('/playlists');414 const card = page.locator('[data-testid="playlist-card"]').first();415416 await expect(card).toHaveScreenshot('playlist-card.png', {417 maxDiffPixelRatio: 0.01,418 animations: 'disabled',419 });420});421```422423### 7.3 Visual Testing Best Practices4244251. **Disable animations** — They cause flaky diffs.4262. **Mock dynamic content** — Dates, avatars, random data.4273. **Test components, not pages** — Smaller surfaces = more stable.4284. **Review diffs carefully** — Not all changes are regressions.4295. **Update baselines intentionally** — Never auto-approve.4306. **Cross-browser** — Chrome, Firefox, Safari have rendering differences.431432---433434## 8. Mobile Testing435436### 8.1 Framework Comparison437438| Framework | Platform | Language | Speed | Use Case |439|-----------|----------|----------|-------|----------|440| **Detox** | React Native | JS/TS | Fast | RN-specific E2E |441| **Appium** | iOS + Android (native) | Multi | Slow | Native app testing |442| **Maestro** | iOS + Android | YAML | Fast | Simple flow testing |443| **XCUITest** | iOS | Swift | Fast | iOS-specific |444| **Espresso** | Android | Kotlin/Java | Fast | Android-specific |445446### 8.2 Mobile Test Checklist447448| Category | Tests |449|----------|-------|450| **Gestures** | Tap, swipe, long press, pinch zoom, pull-to-refresh |451| **Orientation** | Portrait ↔ landscape state preservation |452| **Connectivity** | Offline mode, slow network (3G), network transitions |453| **Background/Foreground** | App state preserved on background/resume |454| **Notifications** | Push notification display and deep link handling |455| **Permissions** | Camera, location, notifications permission flows |456| **Device Variations** | Small screens, large screens, notched displays |457| **OS Versions** | Min supported version through latest |458| **Memory** | No leaks on repeated navigation |459| **Battery** | No excessive CPU/GPS usage in background |460461---462463## 9. Bug Severity / Priority Matrix464465### 9.1 Severity (Impact)466467| Severity | Definition | Example |468|----------|-----------|---------|469| **S1 — Critical** | Service down, data loss, security breach | Login broken, payments failing, user data exposed |470| **S2 — Major** | Core feature broken, no workaround | Can't create playlists, search returns wrong results |471| **S3 — Minor** | Feature impaired, workaround exists | Sort doesn't work but filter does, UI misalignment |472| **S4 — Cosmetic** | Visual only, no functional impact | Wrong font weight, off-by-1px spacing, typo |473474### 9.2 Priority (Urgency)475476| Priority | Definition | SLA |477|----------|-----------|-----|478| **P0 — Hotfix** | Fix immediately, drop everything | < 4 hours |479| **P1 — Urgent** | Fix in current sprint | < 1 week |480| **P2 — Normal** | Fix in next sprint | < 2 weeks |481| **P3 — Low** | Fix when convenient | Next quarter |482| **P4 — Backlog** | May never fix | No SLA |483484### 9.3 Severity × Priority Decision Matrix485486| | S1 Critical | S2 Major | S3 Minor | S4 Cosmetic |487|--|:-----------:|:--------:|:--------:|:-----------:|488| **Affects all users** | P0 | P1 | P2 | P3 |489| **Affects many users** | P0 | P1 | P2 | P3 |490| **Affects few users** | P1 | P2 | P3 | P4 |491| **Edge case** | P1 | P2 | P3 | P4 |492493### 9.4 Bug Report Template494```markdown495## Bug: [One-line title]496497**Severity:** S[1-4] — [Critical/Major/Minor/Cosmetic]498**Priority:** P[0-4] — [Hotfix/Urgent/Normal/Low/Backlog]499**Reporter:** [Name]500**Date:** [Date]501**Assignee:** [Name]502**Environment:** [Production/Staging/Dev] — [Browser/OS/Device]503**Version:** [App version or commit]504505### Description506[Clear, concise description of the bug]507508### Steps to Reproduce5091. [Step 1]5102. [Step 2]5113. [Step 3]512513### Expected Behavior514[What should happen]515516### Actual Behavior517[What actually happens]518519### Evidence520- Screenshot: [link]521- Video: [link]522- Console errors: [paste]523- Network logs: [paste]524525### Impact526- **Users affected:** [All / Segment / Edge case]527- **Workaround:** [Yes — describe / No]528- **Business impact:** [Revenue, retention, compliance]529530### Root Cause (if known)531[Technical analysis]532533### Fix Verification534- [ ] Bug is reproducible in [environment]535- [ ] Fix deployed to staging536- [ ] Fix verified in staging537- [ ] Regression test added538- [ ] Fix deployed to production539- [ ] Fix verified in production540```541542---543544## 10. Test Plan Template545546```markdown547# Test Plan: [Feature / Release Name]548549**Author:** [Name]550**Date:** [Date]551**Version:** [1.0]552**PRD Reference:** [Link]553554## 1. Scope555556### In Scope557- [Feature area 1]558- [Feature area 2]559- [Platforms: web, iOS, Android]560561### Out of Scope562- [Explicitly excluded areas]563564## 2. Test Strategy565566| Layer | Scope | Tools | Owner |567|-------|-------|-------|-------|568| Unit | Business logic, utils | Jest/Vitest | Dev team |569| Integration | API, DB, component trees | Jest, Supertest | Dev team |570| E2E | Critical user flows | Playwright | QA team |571| Performance | API load, page speed | k6, Lighthouse | QA + DevOps |572| Accessibility | WCAG 2.1 AA | axe, manual | QA team |573| Visual | UI regression | Playwright screenshots | QA team |574575## 3. Test Scenarios576577### Happy Path578| # | Scenario | Steps | Expected Result | Priority |579|---|---------|-------|----------------|----------|580| 1 | [Scenario] | [Steps] | [Expected] | P0 |581582### Edge Cases583| # | Scenario | Steps | Expected Result | Priority |584|---|---------|-------|----------------|----------|585| 1 | [Scenario] | [Steps] | [Expected] | P1 |586587### Error Cases588| # | Scenario | Steps | Expected Result | Priority |589|---|---------|-------|----------------|----------|590| 1 | [Scenario] | [Steps] | [Expected] | P1 |591592## 4. Environment Requirements593- [ ] Staging deployed with feature flag enabled594- [ ] Test data seeded595- [ ] Third-party integrations mocked/sandboxed596- [ ] Test accounts created for each role597598## 5. Entry / Exit Criteria599600### Entry Criteria601- [ ] Feature complete (all acceptance criteria implemented)602- [ ] Unit tests written and passing603- [ ] Code reviewed and merged to staging branch604- [ ] Test environment stable605606### Exit Criteria607- [ ] All P0 and P1 test cases passed608- [ ] No open S1 or S2 bugs609- [ ] Test coverage ≥ 80% on new code610- [ ] Performance budgets met611- [ ] Accessibility audit passed612- [ ] PO sign-off obtained613614## 6. Risks615| Risk | Probability | Impact | Mitigation |616|------|------------|--------|-----------|617| [Risk] | [H/M/L] | [H/M/L] | [Plan] |618619## 7. Schedule620| Phase | Start | End | Owner |621|-------|-------|-----|-------|622| Test planning | [Date] | [Date] | QA Lead |623| Test execution | [Date] | [Date] | QA Team |624| Bug fixing | [Date] | [Date] | Dev Team |625| Regression | [Date] | [Date] | QA Team |626| Sign-off | [Date] | [Date] | PO + QA |627```628629---630631## 11. Coverage Requirements632633### Minimum Coverage Thresholds634635| Metric | Minimum | Target | Measured By |636|--------|---------|--------|-------------|637| **Line coverage (new code)** | 80% | 90% | Jest/Vitest --coverage |638| **Branch coverage (new code)** | 75% | 85% | Jest/Vitest --coverage |639| **Overall line coverage** | 70% | 80% | Jest/Vitest --coverage |640| **Critical path E2E** | 100% | 100% | Playwright test count |641| **API endpoint coverage** | 90% | 100% | Supertest/Playwright |642| **Accessibility** | 0 violations (A, AA) | 0 violations | axe-core |643644### Coverage Configuration645```json646// jest.config.ts or vitest.config.ts647{648 "coverageThreshold": {649 "global": {650 "branches": 75,651 "functions": 80,652 "lines": 80,653 "statements": 80654 }655 },656 "collectCoverageFrom": [657 "src/**/*.{ts,tsx}",658 "!src/**/*.d.ts",659 "!src/**/*.stories.{ts,tsx}",660 "!src/**/*.test.{ts,tsx}",661 "!src/**/index.ts",662 "!src/types/**"663 ]664}665```666667### Coverage Anti-Patterns668- ❌ Testing implementation details instead of behavior669- ❌ Writing tests just to hit coverage numbers670- ❌ Ignoring branch coverage (hidden bugs live in untested branches)671- ❌ Not tracking coverage trends over time672- ❌ 100% coverage target (diminishing returns past 90%)673674---675676## Quality Standards6776781. **Shift left.** Catch defects as early as possible — unit > integration > E2E.6792. **Fast feedback.** Unit tests < 30s, integration < 2min, full suite < 15min.6803. **Deterministic.** No flaky tests. Flaky tests erode trust and must be fixed or deleted.6814. **Independent.** Tests run in any order, in parallel, without side effects.6825. **Readable.** Tests are documentation — name them like sentences, structure with AAA.6836. **Maintained.** Dead tests are deleted. Failing tests are fixed immediately.6847. **Risk-based.** Test coverage follows risk — more tests for payment than for "About" page.6858. **Automated first.** Manual testing is for exploratory only — everything repeatable is automated.