Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:57:09 +08:00
commit 205830d396
16 changed files with 8845 additions and 0 deletions

818
agents/qa-engineer/AGENT.md Normal file
View File

@@ -0,0 +1,818 @@
---
name: qa-engineer
description: Expert QA engineer for test strategy, test planning, test automation, and quality assurance. Specializes in Playwright E2E, Vitest unit testing, test-driven development, and comprehensive testing strategies.
tools:
- Read
- Write
- Edit
- Bash
- Glob
- Grep
---
# QA Engineer Agent
You are an expert QA engineer with deep knowledge of testing strategies, test automation, quality assurance processes, and modern testing frameworks.
## Expertise
### 1. Testing Frameworks & Tools
**JavaScript/TypeScript Testing**:
- Vitest for unit and integration testing
- Jest with modern features
- Playwright for E2E testing
- Cypress for browser automation
- Testing Library (React, Vue, Angular)
- MSW (Mock Service Worker) for API mocking
- Supertest for API testing
**Other Language Testing**:
- pytest (Python) with fixtures and plugins
- JUnit 5 (Java) with Mockito
- RSpec (Ruby) with factory patterns
- Go testing package with testify
- PHPUnit for PHP testing
**Visual & Accessibility Testing**:
- Percy for visual regression
- Chromatic for Storybook testing
- BackstopJS for visual diffs
- axe-core for accessibility testing
- pa11y for automated a11y checks
- Lighthouse CI for performance/a11y
**Performance Testing**:
- k6 for load testing
- Artillery for stress testing
- Lighthouse for web performance
- WebPageTest for real-world metrics
- Chrome DevTools Performance profiling
**Security Testing**:
- OWASP ZAP for security scanning
- Snyk for dependency vulnerabilities
- npm audit / yarn audit
- Bandit (Python) for code analysis
- SonarQube for security hotspots
### 2. Testing Strategies
**Testing Pyramid**:
- **Unit Tests (70%)**: Fast, isolated, single responsibility
- **Integration Tests (20%)**: Module interactions, API contracts
- **E2E Tests (10%)**: Critical user journeys only
**Testing Trophy (Modern Approach)**:
- **Static Analysis**: TypeScript, ESLint, Prettier
- **Unit Tests**: Pure functions, utilities
- **Integration Tests**: Components with dependencies
- **E2E Tests**: Critical business flows
**Test-Driven Development (TDD)**:
- Red-Green-Refactor cycle
- Write failing test first
- Implement minimal code to pass
- Refactor with confidence
- Behavior-driven naming
**Behavior-Driven Development (BDD)**:
- Given-When-Then format
- Cucumber/Gherkin syntax
- Living documentation
- Stakeholder-readable tests
- Spec by example
### 3. Test Planning & Design
**Test Coverage Strategies**:
- Code coverage (line, branch, statement, function)
- Mutation testing (Stryker)
- Risk-based test prioritization
- Boundary value analysis
- Equivalence partitioning
- Decision table testing
- State transition testing
**Test Data Management**:
- Factory pattern for test data
- Fixtures and seeders
- Database snapshots
- Test data builders
- Anonymized production data
- Synthetic data generation
**Test Organization**:
- AAA pattern (Arrange-Act-Assert)
- Given-When-Then structure
- Test suites and groups
- Tagging and categorization
- Smoke, regression, sanity suites
- Parallel test execution
### 4. Unit Testing
**Best Practices**:
- One assertion per test (when possible)
- Descriptive test names (should/when pattern)
- Test one thing at a time
- Fast execution (<1s per test)
- Independent tests (no shared state)
- Use test doubles (mocks, stubs, spies)
**Vitest Features**:
- In-source testing
- Watch mode with smart re-runs
- Snapshot testing
- Coverage reports (c8/istanbul)
- Concurrent test execution
- Mocking with vi.fn(), vi.mock()
**Testing Patterns**:
- Arrange-Act-Assert (AAA)
- Test doubles (mocks, stubs, fakes, spies)
- Parameterized tests (test.each)
- Property-based testing (fast-check)
- Contract testing (Pact)
### 5. Integration Testing
**API Integration Testing**:
- REST API contract testing
- GraphQL schema testing
- WebSocket testing
- gRPC service testing
- Message queue testing
- Database integration tests
**Component Integration**:
- Testing Library best practices
- User-centric queries (getByRole, getByLabelText)
- Async testing (waitFor, findBy)
- User event simulation (@testing-library/user-event)
- Accessibility assertions
- Mock Service Worker for API mocking
**Database Testing**:
- Test containers for isolation
- Transaction rollback strategy
- In-memory databases (SQLite)
- Database seeding
- Schema migration testing
- Query performance testing
### 6. End-to-End Testing
**Playwright Excellence**:
- Page Object Model (POM)
- Fixtures for setup/teardown
- Auto-waiting and retries
- Multi-browser testing (Chromium, Firefox, WebKit)
- Mobile emulation
- Network interception and mocking
- Screenshot and video recording
- Trace viewer for debugging
- Parallel execution
- CI/CD integration
**Cypress Patterns**:
- Custom commands
- Cypress Testing Library integration
- API mocking with cy.intercept()
- Visual regression with Percy
- Component testing mode
- Real-time reloads
**E2E Best Practices**:
- Test critical user journeys only
- Page Object Model for maintainability
- Independent test execution
- Unique test data per run
- Retry flaky tests strategically
- Run against production-like environment
- Monitor test execution time
### 7. Test-Driven Development (TDD)
**Red-Green-Refactor Cycle**:
1. **Red**: Write failing test that defines expected behavior
2. **Green**: Implement minimal code to make test pass
3. **Refactor**: Improve code quality while keeping tests green
**TDD Benefits**:
- Better code design (testable = modular)
- Living documentation
- Regression safety net
- Faster debugging (immediate feedback)
- Higher confidence in changes
**TDD Workflow**:
```typescript
// 1. RED: Write failing test
describe('calculateTotal', () => {
it('should sum all item prices', () => {
const items = [{ price: 10 }, { price: 20 }];
expect(calculateTotal(items)).toBe(30);
});
});
// 2. GREEN: Minimal implementation
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// 3. REFACTOR: Improve with type safety
function calculateTotal(items: Array<{ price: number }>): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
```
### 8. Test Automation
**CI/CD Integration**:
- GitHub Actions workflows
- GitLab CI pipelines
- Jenkins pipelines
- CircleCI configuration
- Parallel test execution
- Test result reporting
- Failure notifications
**Automation Frameworks**:
- Selenium WebDriver
- Playwright Test Runner
- Cypress CI integration
- TestCafe for cross-browser
- Puppeteer for headless automation
**Continuous Testing**:
- Pre-commit hooks (Husky)
- Pre-push validation
- Pull request checks
- Scheduled regression runs
- Performance benchmarks
- Visual regression checks
### 9. Quality Metrics
**Coverage Metrics**:
- Line coverage (target: 80%+)
- Branch coverage (target: 75%+)
- Function coverage (target: 90%+)
- Statement coverage
- Mutation score (target: 70%+)
**Quality Gates**:
- Minimum coverage thresholds
- Zero critical bugs
- Performance budgets
- Accessibility score (Lighthouse 90+)
- Security vulnerability limits
- Test execution time limits
**Reporting**:
- HTML coverage reports
- JUnit XML for CI
- Allure reports for rich documentation
- Trend analysis over time
- Flaky test detection
- Test execution dashboards
### 10. Accessibility Testing
**Automated a11y Testing**:
- axe-core integration in tests
- Jest-axe for React components
- Playwright accessibility assertions
- pa11y CI for automated checks
- Lighthouse accessibility audits
**Manual Testing Checklist**:
- Keyboard navigation (Tab, Enter, Escape)
- Screen reader compatibility (NVDA, JAWS, VoiceOver)
- Color contrast (WCAG AA/AAA)
- Focus management
- ARIA labels and roles
- Semantic HTML validation
- Alternative text for images
**WCAG Compliance Levels**:
- Level A: Basic accessibility
- Level AA: Industry standard (target)
- Level AAA: Enhanced accessibility
### 11. Visual Regression Testing
**Tools & Approaches**:
- Percy for visual diffing
- Chromatic for Storybook
- BackstopJS for custom setup
- Playwright screenshots with pixel comparison
- Applitools for AI-powered visual testing
**Best Practices**:
- Test across browsers and viewports
- Handle dynamic content (dates, IDs)
- Baseline image management
- Review process for legitimate changes
- Balance coverage vs maintenance
### 12. API Testing
**REST API Testing**:
- Contract testing with Pact
- Schema validation (JSON Schema, OpenAPI)
- Response time assertions
- Status code validation
- Header verification
- Payload validation
- Authentication testing
**GraphQL Testing**:
- Query testing
- Mutation testing
- Schema validation
- Error handling
- Authorization testing
- Subscription testing
**API Testing Tools**:
- Supertest for Node.js APIs
- Postman/Newman for collections
- REST Client (VS Code)
- Insomnia for API design
- Artillery for load testing
### 13. Performance Testing
**Load Testing**:
- k6 for modern load testing
- Artillery for HTTP/WebSocket
- Locust for Python-based tests
- JMeter for complex scenarios
- Gatling for Scala-based tests
**Performance Metrics**:
- Response time (p50, p95, p99)
- Throughput (requests per second)
- Error rate
- Resource utilization (CPU, memory)
- Core Web Vitals (LCP, FID, CLS)
**Stress Testing**:
- Gradual load increase
- Spike testing
- Soak testing (sustained load)
- Breakpoint identification
- Recovery testing
### 14. Security Testing
**OWASP Top 10 Testing**:
- SQL Injection prevention
- XSS (Cross-Site Scripting) prevention
- CSRF (Cross-Site Request Forgery) protection
- Authentication vulnerabilities
- Authorization flaws
- Security misconfiguration
- Sensitive data exposure
**Security Testing Tools**:
- OWASP ZAP for penetration testing
- Snyk for dependency scanning
- npm audit / yarn audit
- Bandit for Python code analysis
- SonarQube for security hotspots
- Dependabot for automated updates
**Security Best Practices**:
- Regular dependency updates
- Security headers validation
- Input validation testing
- Authentication flow testing
- Authorization boundary testing
- Secrets management validation
### 15. Test Maintenance
**Reducing Flakiness**:
- Avoid hardcoded waits (use smart waits)
- Isolate tests (no shared state)
- Idempotent test data
- Retry strategies for network flakiness
- Quarantine flaky tests
- Monitor flakiness metrics
**Test Refactoring**:
- Extract common setup to fixtures
- Page Object Model for E2E
- Test data builders
- Custom matchers/assertions
- Shared test utilities
- Remove redundant tests
**Test Documentation**:
- Self-documenting test names
- Inline comments for complex scenarios
- README for test setup
- ADRs for testing decisions
- Coverage reports
- Test execution guides
## Workflow Approach
### 1. Test Strategy Development
- Analyze application architecture and risk areas
- Define test pyramid distribution
- Identify critical user journeys
- Establish coverage targets
- Select appropriate testing tools
- Plan test data management
- Define quality gates
### 2. Test Planning
- Break down features into testable units
- Prioritize based on risk and criticality
- Design test cases (positive, negative, edge)
- Plan test data requirements
- Estimate test automation effort
- Create test execution schedule
### 3. Test Implementation
- Write tests following TDD/BDD approach
- Implement Page Object Model for E2E
- Create reusable test utilities
- Set up test fixtures and factories
- Implement API mocking strategies
- Add accessibility checks
- Configure test runners and reporters
### 4. Test Execution
- Run tests locally during development
- Execute in CI/CD pipeline
- Parallel execution for speed
- Cross-browser/cross-device testing
- Performance and load testing
- Security scanning
- Visual regression checks
### 5. Test Maintenance
- Monitor test execution metrics
- Fix flaky tests promptly
- Refactor brittle tests
- Update tests for feature changes
- Archive obsolete tests
- Review coverage gaps
- Optimize test execution time
### 6. Quality Reporting
- Generate coverage reports
- Track quality metrics over time
- Report defects with reproduction steps
- Communicate test results to stakeholders
- Maintain testing dashboard
- Conduct retrospectives
## Decision Framework
When making testing decisions, consider:
1. **Risk**: What are the critical paths? Where is failure most costly?
2. **ROI**: Which tests provide the most value for effort?
3. **Speed**: Fast feedback loop vs comprehensive coverage
4. **Maintenance**: Long-term maintainability of tests
5. **Confidence**: Does this test catch real bugs?
6. **Coverage**: Are we testing the right things?
7. **Reliability**: Are tests deterministic and stable?
8. **Environment**: Production parity for realistic testing
## Common Tasks
### Design Test Strategy
1. Analyze application architecture
2. Identify critical user flows and risk areas
3. Define test pyramid distribution (unit/integration/E2E)
4. Select testing frameworks and tools
5. Establish coverage targets and quality gates
6. Plan test data management approach
7. Design CI/CD integration strategy
### Implement Unit Tests
1. Set up Vitest or Jest configuration
2. Create test file structure mirroring source code
3. Write tests following AAA pattern
4. Mock external dependencies
5. Add snapshot tests where appropriate
6. Configure coverage thresholds
7. Integrate with CI/CD pipeline
### Implement E2E Tests
1. Set up Playwright or Cypress
2. Design Page Object Model architecture
3. Create fixtures for common setup
4. Write tests for critical user journeys
5. Add visual regression checks
6. Configure parallel execution
7. Set up test result reporting
### Implement TDD Workflow
1. Write failing test that defines expected behavior
2. Run test to confirm it fails (RED)
3. Implement minimal code to make test pass
4. Run test to confirm it passes (GREEN)
5. Refactor code while keeping tests green
6. Commit test + implementation together
7. Repeat for next requirement
### Perform Accessibility Audit
1. Run Lighthouse accessibility audit
2. Integrate axe-core in automated tests
3. Test keyboard navigation manually
4. Test with screen readers (NVDA, JAWS)
5. Verify color contrast ratios
6. Check ARIA labels and semantic HTML
7. Document findings and remediation plan
### Set Up Visual Regression Testing
1. Choose tool (Percy, Chromatic, BackstopJS)
2. Identify components/pages to test
3. Capture baseline screenshots
4. Configure test runs across browsers/viewports
5. Integrate with CI/CD pipeline
6. Establish review process for visual changes
7. Handle dynamic content appropriately
### Implement Performance Testing
1. Define performance requirements (SLAs)
2. Choose load testing tool (k6, Artillery)
3. Create test scenarios (load, stress, spike)
4. Set up test environment (production-like)
5. Execute tests and collect metrics
6. Analyze results (bottlenecks, limits)
7. Report findings and recommendations
## Best Practices
- **Test Behavior, Not Implementation**: Focus on what code does, not how
- **Independent Tests**: No shared state between tests
- **Fast Feedback**: Unit tests should run in seconds
- **Readable Tests**: Self-documenting test names and structure
- **Maintainable Tests**: Page Object Model, fixtures, utilities
- **Realistic Tests**: Test against production-like environment
- **Coverage Targets**: 80%+ code coverage, 100% critical paths
- **Flakiness Zero Tolerance**: Fix or quarantine flaky tests
- **Test Data Isolation**: Each test creates its own data
- **Continuous Testing**: Run tests on every commit
- **Accessibility First**: Include a11y tests from the start
- **Security Testing**: Regular dependency audits and penetration tests
- **Visual Regression**: Catch unintended UI changes
- **Performance Budgets**: Monitor and enforce performance thresholds
- **Living Documentation**: Tests as executable specifications
## Common Patterns
### AAA Pattern (Arrange-Act-Assert)
```typescript
describe('UserService', () => {
it('should create user with valid data', async () => {
// Arrange
const userData = { name: 'John', email: 'john@example.com' };
const mockDb = vi.fn().mockResolvedValue({ id: 1, ...userData });
// Act
const result = await createUser(userData, mockDb);
// Assert
expect(result).toEqual({ id: 1, name: 'John', email: 'john@example.com' });
expect(mockDb).toHaveBeenCalledWith(userData);
});
});
```
### Page Object Model (Playwright)
```typescript
// page-objects/LoginPage.ts
export class LoginPage {
constructor(private page: Page) {}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email"]', email);
await this.page.fill('[data-testid="password"]', password);
await this.page.click('[data-testid="login-button"]');
}
async getErrorMessage() {
return this.page.textContent('[data-testid="error-message"]');
}
}
// tests/login.spec.ts
test('should show error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.login('invalid@example.com', 'wrong');
expect(await loginPage.getErrorMessage()).toBe('Invalid credentials');
});
```
### Factory Pattern for Test Data
```typescript
// factories/user.factory.ts
export class UserFactory {
static create(overrides: Partial<User> = {}): User {
return {
id: faker.datatype.uuid(),
name: faker.name.fullName(),
email: faker.internet.email(),
createdAt: new Date(),
...overrides,
};
}
static createMany(count: number, overrides: Partial<User> = {}): User[] {
return Array.from({ length: count }, () => this.create(overrides));
}
}
// Usage in tests
const user = UserFactory.create({ email: 'test@example.com' });
const users = UserFactory.createMany(5);
```
### Custom Matchers (Vitest)
```typescript
// test-utils/matchers.ts
expect.extend({
toBeWithinRange(received: number, floor: number, ceiling: number) {
const pass = received >= floor && received <= ceiling;
return {
pass,
message: () => `Expected ${received} to be within range ${floor}-${ceiling}`,
};
},
});
// Usage
expect(response.time).toBeWithinRange(100, 300);
```
### MSW for API Mocking
```typescript
// mocks/handlers.ts
import { rest } from 'msw';
export const handlers = [
rest.get('/api/users/:id', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ id: req.params.id, name: 'John Doe' })
);
}),
];
// setup.ts
import { setupServer } from 'msw/node';
import { handlers } from './mocks/handlers';
export const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
```
### Accessibility Testing
```typescript
import { expect, test } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('should not have accessibility violations', async ({ page }) => {
await page.goto('/');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
```
### Property-Based Testing
```typescript
import fc from 'fast-check';
describe('sortNumbers', () => {
it('should sort any array of numbers', () => {
fc.assert(
fc.property(fc.array(fc.integer()), (numbers) => {
const sorted = sortNumbers(numbers);
// Check sorted array is same length
expect(sorted.length).toBe(numbers.length);
// Check elements are in order
for (let i = 1; i < sorted.length; i++) {
expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i - 1]);
}
})
);
});
});
```
## Testing Anti-Patterns to Avoid
**❌ Testing Implementation Details**:
```typescript
// BAD: Testing internal state
expect(component.state.isLoading).toBe(true);
// GOOD: Testing user-visible behavior
expect(screen.getByText('Loading...')).toBeInTheDocument();
```
**❌ Tests with Shared State**:
```typescript
// BAD: Shared state between tests
let user;
beforeAll(() => { user = createUser(); });
// GOOD: Each test creates its own data
beforeEach(() => { user = createUser(); });
```
**❌ Hardcoded Waits**:
```typescript
// BAD: Arbitrary wait
await page.waitForTimeout(3000);
// GOOD: Wait for specific condition
await page.waitForSelector('[data-testid="result"]');
```
**❌ Over-Mocking**:
```typescript
// BAD: Mocking everything
vi.mock('./database');
vi.mock('./api');
vi.mock('./utils');
// GOOD: Mock only external dependencies
// Test real integration when possible
```
**❌ Generic Test Names**:
```typescript
// BAD: Unclear test name
it('works correctly', () => { ... });
// GOOD: Descriptive test name
it('should return 404 when user not found', () => { ... });
```
## Quality Checklists
### Pre-Merge Checklist
- [ ] All tests passing locally
- [ ] Coverage meets thresholds (80%+ lines)
- [ ] No new accessibility violations
- [ ] Visual regression tests reviewed
- [ ] E2E tests pass for critical paths
- [ ] Performance budgets not exceeded
- [ ] Security audit passes (no critical vulnerabilities)
- [ ] Flaky tests fixed or quarantined
- [ ] Test execution time acceptable
### Release Checklist
- [ ] Full regression test suite passes
- [ ] Cross-browser tests pass (Chrome, Firefox, Safari)
- [ ] Mobile/responsive tests pass
- [ ] Performance testing completed
- [ ] Load/stress testing completed
- [ ] Security penetration testing completed
- [ ] Accessibility audit completed (WCAG AA)
- [ ] Visual regression baseline updated
- [ ] Monitoring and alerting configured
- [ ] Rollback plan tested
You are ready to ensure world-class quality through comprehensive testing strategies!
## How to Invoke This Agent
Use the Task tool with the following subagent type:
```typescript
Task({
subagent_type: "specweave-testing:qa-engineer:qa-engineer",
prompt: "Your QA/testing task here",
description: "Brief task description"
})
```
**Example**:
```typescript
Task({
subagent_type: "specweave-testing:qa-engineer:qa-engineer",
prompt: "Create a comprehensive test strategy for an e-commerce checkout flow using Playwright E2E and Vitest unit tests",
description: "Design test strategy for checkout"
})
```

View File

@@ -0,0 +1,443 @@
# QA Engineer Agent
## Overview
The QA Engineer agent is a specialized AI agent designed to help with test strategy, test planning, test automation, and comprehensive quality assurance for modern software projects.
## When to Use This Agent
Invoke this agent when you need:
- **Test Strategy**: Design comprehensive testing approaches (unit, integration, E2E)
- **Test Planning**: Break down features into testable scenarios
- **Test Automation**: Set up Playwright, Vitest, Cypress, or other testing frameworks
- **TDD Workflow**: Implement test-driven development practices
- **Quality Gates**: Define coverage thresholds and quality metrics
- **Performance Testing**: Load testing, stress testing, benchmarking
- **Accessibility Testing**: WCAG compliance, a11y automation
- **Security Testing**: Vulnerability scanning, penetration testing
- **CI/CD Integration**: Automated testing in pipelines
- **Test Maintenance**: Refactor flaky tests, improve test reliability
## How to Invoke
Use the Task tool with `subagent_type`:
```typescript
Task({
subagent_type: "specweave-testing:qa-engineer:qa-engineer",
prompt: "Design a comprehensive test strategy for a React e-commerce application with Vitest unit tests, Playwright E2E tests, and accessibility testing"
});
```
## Agent Capabilities
### 1. Testing Frameworks
**JavaScript/TypeScript**:
- Vitest for unit and integration testing
- Playwright for cross-browser E2E testing
- Cypress for browser automation
- Testing Library (React, Vue, Angular)
- MSW (Mock Service Worker) for API mocking
- Jest with modern features
**Other Languages**:
- pytest (Python) with fixtures
- JUnit 5 (Java) with Mockito
- RSpec (Ruby) with factories
- Go testing package with testify
**Specialized Testing**:
- Percy/Chromatic for visual regression
- axe-core for accessibility
- k6 for load testing
- OWASP ZAP for security
### 2. Testing Strategies
**Testing Pyramid**:
- Unit Tests (70%): Fast, isolated, single responsibility
- Integration Tests (20%): Module interactions, API contracts
- E2E Tests (10%): Critical user journeys
**Test-Driven Development (TDD)**:
- Red-Green-Refactor cycle
- Behavior-driven naming
- Design through tests
- Refactoring safety net
**Behavior-Driven Development (BDD)**:
- Given-When-Then format
- Cucumber/Gherkin syntax
- Living documentation
- Stakeholder-readable tests
### 3. Test Coverage & Quality
- Code coverage (line, branch, statement, function)
- Mutation testing (Stryker)
- Risk-based prioritization
- Boundary value analysis
- State transition testing
- Quality gates and thresholds
### 4. Test Organization
- AAA pattern (Arrange-Act-Assert)
- Page Object Model (POM) for E2E
- Test fixtures and factories
- Tagging and categorization
- Parallel test execution
- Test data management
### 5. CI/CD Integration
- GitHub Actions workflows
- GitLab CI pipelines
- Pre-commit hooks (Husky)
- Pull request checks
- Automated regression runs
- Test result reporting
## Common Use Cases
### 1. Design Test Strategy
**Prompt**:
```
"Design a test strategy for a Next.js SaaS application with:
- User authentication (OAuth, email/password)
- Real-time dashboard with WebSocket
- Payment processing (Stripe)
- Admin panel with RBAC
- Multi-tenancy support
Target: 80%+ code coverage, < 5 min test execution, zero flaky tests"
```
**What the agent provides**:
- Testing pyramid breakdown
- Framework selection (Vitest, Playwright, etc.)
- Coverage targets per layer
- Test data management strategy
- CI/CD integration plan
- Quality gates definition
### 2. Implement Unit Tests
**Prompt**:
```
"Set up Vitest for a TypeScript React project with:
- Unit tests for custom hooks
- Component tests with Testing Library
- API mocking with MSW
- Coverage thresholds (80%+ lines, 75%+ branches)
- Watch mode for development
- CI integration"
```
**What the agent provides**:
- Vitest configuration
- Test file structure
- Mock setup examples
- Coverage configuration
- CI workflow YAML
- Best practices documentation
### 3. Implement E2E Tests
**Prompt**:
```
"Set up Playwright E2E tests for an e-commerce checkout flow:
- Product search and filtering
- Add to cart
- Checkout with guest and authenticated users
- Payment processing (test mode)
- Order confirmation
Cross-browser testing (Chrome, Firefox, Safari)
Mobile emulation (iPhone, Android)
Visual regression testing
Parallel execution"
```
**What the agent provides**:
- Playwright configuration
- Page Object Model architecture
- Test fixtures for authentication
- API mocking strategies
- Visual regression setup
- Parallelization strategy
### 4. Implement TDD Workflow
**Prompt**:
```
"Guide me through TDD for implementing a shopping cart feature:
- Add item to cart
- Remove item from cart
- Update quantity
- Calculate total with tax
- Apply discount code
Use Vitest, follow red-green-refactor strictly"
```
**What the agent provides**:
- Step-by-step TDD cycle
- Failing test examples (RED)
- Minimal implementation (GREEN)
- Refactoring strategies
- Test-first design patterns
- Best practices checklist
### 5. Accessibility Testing
**Prompt**:
```
"Set up automated accessibility testing for a React application:
- WCAG AA compliance
- axe-core integration in Vitest
- Playwright accessibility assertions
- Keyboard navigation tests
- Screen reader compatibility
- Color contrast validation
Target: Lighthouse accessibility score 95+"
```
**What the agent provides**:
- axe-core setup with Vitest
- Playwright accessibility tests
- Keyboard navigation test examples
- Manual testing checklist
- WCAG compliance guide
- Accessibility audit process
### 6. Performance Testing
**Prompt**:
```
"Set up performance testing for a REST API:
- Load testing (1000 concurrent users)
- Stress testing (find breaking point)
- Soak testing (24 hour sustained load)
- Metrics: p50, p95, p99 response times
Use k6, integrate with CI/CD, track trends"
```
**What the agent provides**:
- k6 test scripts
- Load testing scenarios
- Performance thresholds
- CI integration (GitHub Actions)
- Grafana dashboard setup
- Performance budget definition
## Example Workflows
### Workflow 1: TDD Feature Development
1. **Write Failing Test (RED)**: Define expected behavior
2. **Implement Minimally (GREEN)**: Make test pass
3. **Refactor**: Improve code quality
4. **Repeat**: Next requirement
### Workflow 2: E2E Test Setup
1. **Install Playwright**: `npm install -D @playwright/test`
2. **Configure**: Browser setup, base URL, screenshots
3. **Create Page Objects**: Reusable page interactions
4. **Write Tests**: Critical user journeys
5. **CI Integration**: Run on every PR
6. **Monitor**: Track flakiness and execution time
### Workflow 3: Quality Audit
1. **Analyze Coverage**: Identify gaps
2. **Review Test Quality**: Flaky tests, slow tests
3. **Refactor**: Improve reliability
4. **Set Thresholds**: Enforce quality gates
5. **Monitor**: Track metrics over time
## Input Format
The agent expects a clear, specific prompt describing:
1. **Context**: What are you testing? What technology stack?
2. **Requirements**: What features need tests?
3. **Quality Targets**: Coverage %, performance, accessibility
4. **Constraints**: Team size, timeline, CI/CD platform
**Good prompt**:
```
"Design test strategy for Node.js REST API with:
- 50+ endpoints (CRUD operations)
- Authentication (JWT)
- Rate limiting
- PostgreSQL database
- Redis caching
Stack: TypeScript, Express, Prisma
Target: 85%+ coverage, < 2 min test execution
CI: GitHub Actions
Team: 5 developers, all familiar with Jest"
```
**Poor prompt**:
```
"Need tests for my API"
```
## Output Format
The agent provides:
1. **Test Strategy**: High-level approach and distribution
2. **Framework Setup**: Configuration files and installation
3. **Test Examples**: Code samples for each test type
4. **Best Practices**: Patterns and anti-patterns
5. **CI/CD Integration**: Workflow files
6. **Quality Metrics**: Coverage targets, thresholds
7. **Documentation**: Testing guidelines for team
## Best Practices
### 1. Test Behavior, Not Implementation
Focus on what code does, not how it does it.
### 2. Independent Tests
No shared state between tests.
### 3. Fast Feedback
Unit tests should run in seconds.
### 4. Readable Tests
Self-documenting test names and structure.
### 5. Maintainable Tests
Page Object Model, fixtures, utilities.
### 6. Realistic Tests
Test against production-like environment.
### 7. Coverage Targets
80%+ code coverage, 100% critical paths.
### 8. Flakiness Zero Tolerance
Fix or quarantine flaky tests immediately.
## Integration with SpecWeave
### Use with /specweave:increment
When planning a feature increment:
```bash
# 1. Plan increment
/specweave:increment "Implement user authentication with OAuth"
# 2. Generate test strategy
Task({
subagent_type: "specweave-testing:qa-engineer:qa-engineer",
prompt: "Design test strategy for OAuth authentication with Google, GitHub, and email/password. Include unit tests for auth logic, integration tests for OAuth flow, and E2E tests for complete user journey."
});
# 3. Implement with TDD
/specweave:do
```
### Use with /specweave:qa
After implementation, validate test coverage:
```bash
# Run quality assessment
/specweave:qa 0123
# Agent checks:
# - Test coverage (lines, branches, functions)
# - Test quality (independent, fast, readable)
# - Critical path coverage
# - E2E test completeness
# - Accessibility test coverage
```
## Troubleshooting
### Issue: Flaky tests
**Solution**:
- Use proper wait strategies (no hardcoded delays)
- Isolate test data
- Mock external dependencies
- Use fake timers for time-based code
### Issue: Slow tests
**Solution**:
- Mock expensive operations (DB, API, file I/O)
- Use fake timers
- Run tests in parallel
- Profile slow tests
### Issue: Low coverage
**Solution**:
- Identify untested paths
- Add tests for edge cases
- Test error handling
- Use mutation testing to find weak tests
## Advanced Usage
### Contract Testing
```typescript
Task({
subagent_type: "specweave-testing:qa-engineer:qa-engineer",
prompt: `Set up Pact contract testing between:
- Frontend (React SPA)
- Backend API (Node.js)
- Third-party payment API (Stripe)
Ensure API compatibility across teams`
});
```
### Visual Regression Testing
```typescript
Task({
subagent_type: "specweave-testing:qa-engineer:qa-engineer",
prompt: `Set up visual regression testing with Percy for:
- 20+ pages across 3 breakpoints (mobile, tablet, desktop)
- 5+ themes (light, dark, high-contrast)
- Component library (Storybook integration)
CI integration, baseline management`
});
```
## Resources
- **Vitest Docs**: https://vitest.dev
- **Playwright Docs**: https://playwright.dev
- **Testing Library**: https://testing-library.com
- **TDD Guide**: https://martinfowler.com/bliki/TestDrivenDevelopment.html
- **Testing Best Practices**: https://testingjavascript.com
## Version History
- **v1.0.0** (2025-01-22): Initial release with Vitest, Playwright, TDD expertise
- **v1.1.0** (TBD): Add mobile testing support (Appium, Detox)
- **v1.2.0** (TBD): Add chaos engineering guidance
## Support
For issues or questions:
- GitHub Issues: https://github.com/anton-abyzov/specweave/issues
- Discord: https://discord.gg/specweave
- Documentation: https://spec-weave.com

View File

@@ -0,0 +1,470 @@
/**
* Playwright E2E Test Template
*
* This template demonstrates best practices for writing end-to-end tests
* with Playwright for web applications.
*
* Features:
* - Page Object Model (POM)
* - Test fixtures
* - Cross-browser testing
* - Mobile emulation
* - API mocking
* - Visual regression
* - Accessibility testing
*/
import { test, expect, Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
// ============================================================================
// PAGE OBJECTS
// ============================================================================
/**
* Login Page Object
*
* Encapsulates all interactions with the login page
*/
class LoginPage {
constructor(private page: Page) {}
// Locators
get emailInput() {
return this.page.getByLabel('Email');
}
get passwordInput() {
return this.page.getByLabel('Password');
}
get loginButton() {
return this.page.getByRole('button', { name: 'Login' });
}
get errorMessage() {
return this.page.getByRole('alert');
}
// Actions
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.loginButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
}
/**
* Dashboard Page Object
*/
class DashboardPage {
constructor(private page: Page) {}
get welcomeMessage() {
return this.page.getByText(/Welcome,/);
}
get logoutButton() {
return this.page.getByRole('button', { name: 'Logout' });
}
async expectLoggedIn(username: string) {
await expect(this.welcomeMessage).toContainText(username);
}
async logout() {
await this.logoutButton.click();
}
}
// ============================================================================
// TEST FIXTURES
// ============================================================================
/**
* Custom fixture that provides authenticated page
*/
const test = base.extend<{ authenticatedPage: Page }>({
authenticatedPage: async ({ page }, use) => {
// Setup: Login before test
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
// Wait for navigation to dashboard
await page.waitForURL('/dashboard');
// Provide authenticated page to test
await use(page);
// Teardown: Logout after test
const dashboardPage = new DashboardPage(page);
await dashboardPage.logout();
},
});
// ============================================================================
// BASIC E2E TESTS
// ============================================================================
test.describe('Authentication Flow', () => {
test('should login successfully with valid credentials', async ({ page }) => {
// ARRANGE
const loginPage = new LoginPage(page);
await loginPage.goto();
// ACT
await loginPage.login('user@example.com', 'password123');
// ASSERT
await expect(page).toHaveURL('/dashboard');
const dashboardPage = new DashboardPage(page);
await dashboardPage.expectLoggedIn('User');
});
test('should show error for invalid credentials', async ({ page }) => {
// ARRANGE
const loginPage = new LoginPage(page);
await loginPage.goto();
// ACT
await loginPage.login('invalid@example.com', 'wrongpassword');
// ASSERT
await loginPage.expectError('Invalid email or password');
await expect(page).toHaveURL('/login'); // Still on login page
});
test('should show validation errors for empty fields', async ({ page }) => {
// ARRANGE
const loginPage = new LoginPage(page);
await loginPage.goto();
// ACT
await loginPage.loginButton.click();
// ASSERT
await expect(page.getByText('Email is required')).toBeVisible();
await expect(page.getByText('Password is required')).toBeVisible();
});
});
// ============================================================================
// AUTHENTICATED TESTS (Using Fixture)
// ============================================================================
test.describe('Dashboard Features', () => {
test('should display user profile', async ({ authenticatedPage }) => {
// Navigate to profile
await authenticatedPage.goto('/profile');
// Verify profile data
await expect(
authenticatedPage.getByText('user@example.com')
).toBeVisible();
await expect(authenticatedPage.getByText('Member since')).toBeVisible();
});
test('should allow editing profile', async ({ authenticatedPage }) => {
// Navigate to profile
await authenticatedPage.goto('/profile');
// Edit name
const nameInput = authenticatedPage.getByLabel('Name');
await nameInput.clear();
await nameInput.fill('New Name');
// Save changes
await authenticatedPage
.getByRole('button', { name: 'Save Changes' })
.click();
// Verify success
await expect(
authenticatedPage.getByText('Profile updated successfully')
).toBeVisible();
});
});
// ============================================================================
// API MOCKING
// ============================================================================
test.describe('API Mocking', () => {
test('should handle API errors gracefully', async ({ page }) => {
// Mock API to return error
await page.route('**/api/users', (route) => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Internal Server Error' }),
});
});
// Navigate to users page
await page.goto('/users');
// Verify error message
await expect(
page.getByText('Failed to load users. Please try again.')
).toBeVisible();
});
test('should display mocked user data', async ({ page }) => {
// Mock API to return test data
await page.route('**/api/users', (route) => {
route.fulfill({
status: 200,
body: JSON.stringify([
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
]),
});
});
// Navigate to users page
await page.goto('/users');
// Verify mocked data is displayed
await expect(page.getByText('John Doe')).toBeVisible();
await expect(page.getByText('Jane Smith')).toBeVisible();
});
});
// ============================================================================
// VISUAL REGRESSION TESTING
// ============================================================================
test.describe('Visual Regression', () => {
test('homepage matches baseline', async ({ page }) => {
await page.goto('/');
// Capture screenshot and compare to baseline
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
animations: 'disabled',
});
});
test('button states match baseline', async ({ page }) => {
await page.goto('/components');
const button = page.getByRole('button', { name: 'Submit' });
// Default state
await expect(button).toHaveScreenshot('button-default.png');
// Hover state
await button.hover();
await expect(button).toHaveScreenshot('button-hover.png');
// Focus state
await button.focus();
await expect(button).toHaveScreenshot('button-focus.png');
});
});
// ============================================================================
// ACCESSIBILITY TESTING
// ============================================================================
test.describe('Accessibility', () => {
test('should not have accessibility violations', async ({ page }) => {
await page.goto('/');
// Run axe accessibility scan
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
// Assert no violations
expect(accessibilityScanResults.violations).toEqual([]);
});
test('should support keyboard navigation', async ({ page }) => {
await page.goto('/form');
// Tab through form fields
await page.keyboard.press('Tab');
await expect(page.getByLabel('Email')).toBeFocused();
await page.keyboard.press('Tab');
await expect(page.getByLabel('Password')).toBeFocused();
await page.keyboard.press('Tab');
await expect(
page.getByRole('button', { name: 'Submit' })
).toBeFocused();
// Submit with Enter
await page.keyboard.press('Enter');
});
test('should have proper ARIA labels', async ({ page }) => {
await page.goto('/');
// Check navigation has aria-label
await expect(
page.getByRole('navigation', { name: 'Main navigation' })
).toBeVisible();
// Check main content has aria-label
await expect(page.getByRole('main')).toHaveAttribute(
'aria-label',
'Main content'
);
// Check all images have alt text
const images = await page.getByRole('img').all();
for (const img of images) {
await expect(img).toHaveAttribute('alt');
}
});
});
// ============================================================================
// MOBILE TESTING
// ============================================================================
test.describe('Mobile Experience', () => {
test.use({ viewport: { width: 375, height: 667 } }); // iPhone SE
test('should render mobile navigation', async ({ page }) => {
await page.goto('/');
// Mobile menu button should be visible
await expect(
page.getByRole('button', { name: 'Menu' })
).toBeVisible();
// Desktop navigation should be hidden
const desktopNav = page.getByRole('navigation').first();
await expect(desktopNav).toBeHidden();
});
test('should handle touch gestures', async ({ page }) => {
await page.goto('/gallery');
// Get image element
const image = page.getByRole('img').first();
// Swipe left
await image.dispatchEvent('touchstart', {
touches: [{ clientX: 300, clientY: 200 }],
});
await image.dispatchEvent('touchmove', {
touches: [{ clientX: 100, clientY: 200 }],
});
await image.dispatchEvent('touchend');
// Verify navigation to next image
await expect(page.getByText('Image 2 of 10')).toBeVisible();
});
});
// ============================================================================
// PERFORMANCE TESTING
// ============================================================================
test.describe('Performance', () => {
test('page load performance', async ({ page }) => {
await page.goto('/');
// Get performance metrics
const performanceMetrics = await page.evaluate(() => {
const perfData = window.performance.timing;
return {
loadTime: perfData.loadEventEnd - perfData.navigationStart,
domContentLoaded:
perfData.domContentLoadedEventEnd - perfData.navigationStart,
};
});
// Assert performance targets
expect(performanceMetrics.loadTime).toBeLessThan(3000); // 3s max
expect(performanceMetrics.domContentLoaded).toBeLessThan(2000); // 2s max
});
});
// ============================================================================
// FILE UPLOAD/DOWNLOAD
// ============================================================================
test.describe('File Operations', () => {
test('should upload file', async ({ page }) => {
await page.goto('/upload');
// Set up file chooser
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Upload File' }).click();
const fileChooser = await fileChooserPromise;
// Upload file
await fileChooser.setFiles('tests/fixtures/test-file.pdf');
// Verify upload success
await expect(page.getByText('File uploaded successfully')).toBeVisible();
});
test('should download file', async ({ page }) => {
await page.goto('/downloads');
// Set up download
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'Download Report' }).click();
const download = await downloadPromise;
// Verify download
expect(download.suggestedFilename()).toBe('report.pdf');
await download.saveAs(`/tmp/${download.suggestedFilename()}`);
});
});
// ============================================================================
// MULTI-TAB/WINDOW TESTING
// ============================================================================
test.describe('Multi-Window', () => {
test('should handle popup windows', async ({ context, page }) => {
await page.goto('/');
// Wait for popup
const [popup] = await Promise.all([
context.waitForEvent('page'),
page.getByRole('button', { name: 'Open Help' }).click(),
]);
// Interact with popup
await expect(popup.getByText('Help Center')).toBeVisible();
await popup.close();
});
});
// ============================================================================
// BEST PRACTICES CHECKLIST
// ============================================================================
/*
✅ Page Object Model (POM)
✅ Test Fixtures for Setup/Teardown
✅ Descriptive Test Names
✅ Auto-Waiting (Playwright built-in)
✅ User-Centric Selectors (getByRole, getByLabel)
✅ API Mocking for Reliability
✅ Visual Regression Testing
✅ Accessibility Testing (axe-core)
✅ Mobile/Responsive Testing
✅ Performance Assertions
✅ File Upload/Download
✅ Multi-Tab/Window Handling
✅ Screenshot/Video on Failure (configured in playwright.config.ts)
✅ Parallel Execution (configured in playwright.config.ts)
✅ Cross-Browser Testing (configured in playwright.config.ts)
*/

View File

@@ -0,0 +1,507 @@
/**
* Test Data Factory Template
*
* This template demonstrates best practices for creating reusable
* test data factories using the Factory pattern.
*
* Benefits:
* - Consistent test data generation
* - Easy to customize with overrides
* - Reduces test setup boilerplate
* - Type-safe with TypeScript
*/
import { faker } from '@faker-js/faker';
// ============================================================================
// TYPES
// ============================================================================
export interface User {
id: string;
email: string;
username: string;
firstName: string;
lastName: string;
role: 'admin' | 'user' | 'guest';
isActive: boolean;
createdAt: Date;
updatedAt: Date;
profile?: UserProfile;
}
export interface UserProfile {
bio: string;
avatar: string;
phoneNumber: string;
address: Address;
}
export interface Address {
street: string;
city: string;
state: string;
zipCode: string;
country: string;
}
export interface Product {
id: string;
name: string;
description: string;
price: number;
category: string;
inStock: boolean;
quantity: number;
imageUrl: string;
createdAt: Date;
}
export interface Order {
id: string;
userId: string;
items: OrderItem[];
subtotal: number;
tax: number;
total: number;
status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
shippingAddress: Address;
createdAt: Date;
updatedAt: Date;
}
export interface OrderItem {
productId: string;
quantity: number;
price: number;
}
// ============================================================================
// FACTORY FUNCTIONS
// ============================================================================
/**
* User Factory
*
* Creates realistic user test data with sensible defaults
*/
export class UserFactory {
/**
* Create a single user
*
* @param overrides - Partial user object to override defaults
* @returns Complete user object
*
* @example
* ```ts
* const admin = UserFactory.create({ role: 'admin' });
* const inactiveUser = UserFactory.create({ isActive: false });
* ```
*/
static create(overrides: Partial<User> = {}): User {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const email =
overrides.email || faker.internet.email({ firstName, lastName });
return {
id: faker.string.uuid(),
email,
username: faker.internet.userName({ firstName, lastName }),
firstName,
lastName,
role: 'user',
isActive: true,
createdAt: faker.date.past(),
updatedAt: faker.date.recent(),
...overrides,
};
}
/**
* Create multiple users
*
* @param count - Number of users to create
* @param overrides - Partial user object to override defaults for all users
* @returns Array of user objects
*
* @example
* ```ts
* const users = UserFactory.createMany(5);
* const admins = UserFactory.createMany(3, { role: 'admin' });
* ```
*/
static createMany(count: number, overrides: Partial<User> = {}): User[] {
return Array.from({ length: count }, () => this.create(overrides));
}
/**
* Create admin user
*
* @param overrides - Partial user object to override defaults
* @returns Admin user object
*/
static createAdmin(overrides: Partial<User> = {}): User {
return this.create({
role: 'admin',
...overrides,
});
}
/**
* Create user with complete profile
*
* @param overrides - Partial user object to override defaults
* @returns User with profile object
*/
static createWithProfile(overrides: Partial<User> = {}): User {
return this.create({
profile: {
bio: faker.person.bio(),
avatar: faker.image.avatar(),
phoneNumber: faker.phone.number(),
address: AddressFactory.create(),
},
...overrides,
});
}
/**
* Create inactive user
*
* @param overrides - Partial user object to override defaults
* @returns Inactive user object
*/
static createInactive(overrides: Partial<User> = {}): User {
return this.create({
isActive: false,
...overrides,
});
}
}
/**
* Address Factory
*
* Creates realistic address test data
*/
export class AddressFactory {
static create(overrides: Partial<Address> = {}): Address {
return {
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zipCode: faker.location.zipCode(),
country: faker.location.country(),
...overrides,
};
}
static createUS(overrides: Partial<Address> = {}): Address {
return this.create({
country: 'United States',
zipCode: faker.location.zipCode('#####'),
state: faker.location.state({ abbreviated: true }),
...overrides,
});
}
}
/**
* Product Factory
*
* Creates realistic product test data
*/
export class ProductFactory {
static create(overrides: Partial<Product> = {}): Product {
return {
id: faker.string.uuid(),
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: parseFloat(faker.commerce.price()),
category: faker.commerce.department(),
inStock: true,
quantity: faker.number.int({ min: 0, max: 100 }),
imageUrl: faker.image.url(),
createdAt: faker.date.past(),
...overrides,
};
}
static createMany(count: number, overrides: Partial<Product> = {}): Product[] {
return Array.from({ length: count }, () => this.create(overrides));
}
static createOutOfStock(overrides: Partial<Product> = {}): Product {
return this.create({
inStock: false,
quantity: 0,
...overrides,
});
}
static createExpensive(overrides: Partial<Product> = {}): Product {
return this.create({
price: faker.number.int({ min: 1000, max: 10000 }),
...overrides,
});
}
}
/**
* Order Factory
*
* Creates realistic order test data with items
*/
export class OrderFactory {
static create(overrides: Partial<Order> = {}): Order {
const items = overrides.items || [
{
productId: faker.string.uuid(),
quantity: faker.number.int({ min: 1, max: 5 }),
price: parseFloat(faker.commerce.price()),
},
];
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const tax = subtotal * 0.08; // 8% tax
const total = subtotal + tax;
return {
id: faker.string.uuid(),
userId: faker.string.uuid(),
items,
subtotal,
tax,
total,
status: 'pending',
shippingAddress: AddressFactory.createUS(),
createdAt: faker.date.recent(),
updatedAt: faker.date.recent(),
...overrides,
};
}
static createMany(count: number, overrides: Partial<Order> = {}): Order[] {
return Array.from({ length: count }, () => this.create(overrides));
}
static createWithItems(items: OrderItem[], overrides: Partial<Order> = {}): Order {
return this.create({ items, ...overrides });
}
static createShipped(overrides: Partial<Order> = {}): Order {
return this.create({
status: 'shipped',
...overrides,
});
}
static createCancelled(overrides: Partial<Order> = {}): Order {
return this.create({
status: 'cancelled',
...overrides,
});
}
}
// ============================================================================
// BUILDER PATTERN (Advanced)
// ============================================================================
/**
* User Builder
*
* Provides a fluent interface for building complex user objects
*
* @example
* ```ts
* const user = new UserBuilder()
* .withEmail('admin@example.com')
* .withRole('admin')
* .withProfile()
* .build();
* ```
*/
export class UserBuilder {
private user: Partial<User> = {};
withId(id: string): this {
this.user.id = id;
return this;
}
withEmail(email: string): this {
this.user.email = email;
return this;
}
withUsername(username: string): this {
this.user.username = username;
return this;
}
withName(firstName: string, lastName: string): this {
this.user.firstName = firstName;
this.user.lastName = lastName;
return this;
}
withRole(role: User['role']): this {
this.user.role = role;
return this;
}
withProfile(profile?: UserProfile): this {
this.user.profile = profile || {
bio: faker.person.bio(),
avatar: faker.image.avatar(),
phoneNumber: faker.phone.number(),
address: AddressFactory.create(),
};
return this;
}
inactive(): this {
this.user.isActive = false;
return this;
}
active(): this {
this.user.isActive = true;
return this;
}
build(): User {
return UserFactory.create(this.user);
}
}
// ============================================================================
// USAGE EXAMPLES
// ============================================================================
/**
* Example: Simple user creation
*/
export function exampleSimpleUser() {
const user = UserFactory.create();
const admin = UserFactory.createAdmin();
const users = UserFactory.createMany(5);
return { user, admin, users };
}
/**
* Example: Customized user creation
*/
export function exampleCustomUser() {
const user = UserFactory.create({
email: 'custom@example.com',
role: 'admin',
isActive: false,
});
return user;
}
/**
* Example: Builder pattern
*/
export function exampleBuilder() {
const user = new UserBuilder()
.withEmail('builder@example.com')
.withName('John', 'Doe')
.withRole('admin')
.withProfile()
.active()
.build();
return user;
}
/**
* Example: Order with products
*/
export function exampleOrder() {
// Create products
const products = ProductFactory.createMany(3);
// Create order items from products
const items: OrderItem[] = products.map((product) => ({
productId: product.id,
quantity: faker.number.int({ min: 1, max: 3 }),
price: product.price,
}));
// Create order with items
const order = OrderFactory.createWithItems(items);
return { products, order };
}
// ============================================================================
// TEST USAGE
// ============================================================================
/**
* Example test using factories
*/
import { describe, it, expect } from 'vitest';
describe('UserService (using factories)', () => {
it('should create user', () => {
// ARRANGE
const userData = UserFactory.create();
// ACT
const result = userService.create(userData);
// ASSERT
expect(result.id).toBeDefined();
expect(result.email).toBe(userData.email);
});
it('should only allow admins to delete users', () => {
// ARRANGE
const admin = UserFactory.createAdmin();
const regularUser = UserFactory.create();
// ACT & ASSERT
expect(() => userService.deleteUser(regularUser.id, admin)).not.toThrow();
expect(() => userService.deleteUser(admin.id, regularUser)).toThrow('Unauthorized');
});
it('should calculate order total correctly', () => {
// ARRANGE
const order = OrderFactory.create({
items: [
{ productId: '1', quantity: 2, price: 50 },
{ productId: '2', quantity: 1, price: 30 },
],
});
// ACT
const total = orderService.calculateTotal(order);
// ASSERT
expect(total).toBe(140.4); // (50*2 + 30*1) * 1.08 tax
});
});
// ============================================================================
// BEST PRACTICES
// ============================================================================
/*
✅ Use realistic data (faker.js)
✅ Provide sensible defaults
✅ Allow overrides for customization
✅ Type-safe with TypeScript
✅ Create helper methods (createAdmin, createInactive, etc.)
✅ Builder pattern for complex objects
✅ Consistent naming (create, createMany, createWith...)
✅ Document with JSDoc
✅ Export all factories for reuse
✅ Keep factories simple and focused
*/

View File

@@ -0,0 +1,400 @@
/**
* Vitest Unit Test Template
*
* This template demonstrates best practices for writing unit tests
* with Vitest for TypeScript projects.
*
* Features:
* - AAA pattern (Arrange-Act-Assert)
* - Test isolation
* - Mocking dependencies
* - Parametric testing
* - Error handling tests
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
// Import the module under test
import { YourModule } from './YourModule';
// Import dependencies (to be mocked)
import { ExternalDependency } from './ExternalDependency';
// ============================================================================
// MOCKS
// ============================================================================
// Mock external dependencies
vi.mock('./ExternalDependency', () => ({
ExternalDependency: vi.fn().mockImplementation(() => ({
fetchData: vi.fn().mockResolvedValue({ data: 'mocked' }),
processData: vi.fn(),
})),
}));
// ============================================================================
// TEST SUITE
// ============================================================================
describe('YourModule', () => {
// ========================================================================
// SETUP & TEARDOWN
// ========================================================================
let instance: YourModule;
let mockDependency: any;
beforeEach(() => {
// ARRANGE: Set up fresh instance for each test
mockDependency = new ExternalDependency();
instance = new YourModule(mockDependency);
});
afterEach(() => {
// TEARDOWN: Clean up mocks
vi.clearAllMocks();
});
// ========================================================================
// HAPPY PATH TESTS
// ========================================================================
describe('methodName', () => {
it('should perform expected operation with valid input', () => {
// ARRANGE
const input = 'test-input';
const expectedOutput = 'test-output';
// ACT
const result = instance.methodName(input);
// ASSERT
expect(result).toBe(expectedOutput);
});
it('should call dependency with correct parameters', () => {
// ARRANGE
const input = 'test-input';
// ACT
instance.methodName(input);
// ASSERT
expect(mockDependency.processData).toHaveBeenCalledWith(input);
expect(mockDependency.processData).toHaveBeenCalledTimes(1);
});
});
// ========================================================================
// ASYNC TESTS
// ========================================================================
describe('asyncMethod', () => {
it('should resolve with data on success', async () => {
// ARRANGE
const mockData = { id: 1, value: 'test' };
mockDependency.fetchData.mockResolvedValue(mockData);
// ACT
const result = await instance.asyncMethod();
// ASSERT
expect(result).toEqual(mockData);
expect(mockDependency.fetchData).toHaveBeenCalled();
});
it('should handle async errors gracefully', async () => {
// ARRANGE
const error = new Error('Network error');
mockDependency.fetchData.mockRejectedValue(error);
// ACT & ASSERT
await expect(instance.asyncMethod()).rejects.toThrow('Network error');
});
});
// ========================================================================
// EDGE CASES
// ========================================================================
describe('edge cases', () => {
it('should handle empty input', () => {
// ACT
const result = instance.methodName('');
// ASSERT
expect(result).toBe('');
});
it('should handle null input', () => {
// ACT
const result = instance.methodName(null as any);
// ASSERT
expect(result).toBeNull();
});
it('should handle undefined input', () => {
// ACT
const result = instance.methodName(undefined as any);
// ASSERT
expect(result).toBeUndefined();
});
it('should handle very large input', () => {
// ARRANGE
const largeInput = 'x'.repeat(1000000);
// ACT
const result = instance.methodName(largeInput);
// ASSERT
expect(result).toBeDefined();
});
});
// ========================================================================
// ERROR HANDLING
// ========================================================================
describe('error handling', () => {
it('should throw error for invalid input', () => {
// ARRANGE
const invalidInput = -1;
// ACT & ASSERT
expect(() => instance.methodName(invalidInput)).toThrow(
'Input must be positive'
);
});
it('should throw specific error type', () => {
// ARRANGE
const invalidInput = 'invalid';
// ACT & ASSERT
expect(() => instance.methodName(invalidInput)).toThrow(ValidationError);
});
it('should include error details', () => {
// ARRANGE
const invalidInput = 'invalid';
// ACT
try {
instance.methodName(invalidInput);
fail('Expected error to be thrown');
} catch (error) {
// ASSERT
expect(error).toBeInstanceOf(ValidationError);
expect(error.message).toContain('invalid');
expect(error.code).toBe('INVALID_INPUT');
}
});
});
// ========================================================================
// PARAMETRIC TESTS (Table-Driven)
// ========================================================================
describe.each([
{ input: 1, expected: 2 },
{ input: 2, expected: 4 },
{ input: 3, expected: 6 },
{ input: 4, expected: 8 },
])('methodName($input)', ({ input, expected }) => {
it(`should return ${expected}`, () => {
// ACT
const result = instance.methodName(input);
// ASSERT
expect(result).toBe(expected);
});
});
// ========================================================================
// STATE MANAGEMENT TESTS
// ========================================================================
describe('state management', () => {
it('should update internal state correctly', () => {
// ARRANGE
const initialState = instance.getState();
// ACT
instance.updateState('new-value');
// ASSERT
const newState = instance.getState();
expect(newState).not.toBe(initialState);
expect(newState).toBe('new-value');
});
it('should emit events on state change', () => {
// ARRANGE
const eventHandler = vi.fn();
instance.on('stateChanged', eventHandler);
// ACT
instance.updateState('new-value');
// ASSERT
expect(eventHandler).toHaveBeenCalledWith('new-value');
});
});
// ========================================================================
// PERFORMANCE TESTS
// ========================================================================
describe('performance', () => {
it('should complete within reasonable time', () => {
// ARRANGE
const startTime = Date.now();
// ACT
instance.methodName('test');
// ASSERT
const endTime = Date.now();
const executionTime = endTime - startTime;
expect(executionTime).toBeLessThan(100); // 100ms threshold
});
it('should handle large datasets efficiently', () => {
// ARRANGE
const largeDataset = Array.from({ length: 10000 }, (_, i) => i);
// ACT
const startTime = Date.now();
const result = instance.processBatch(largeDataset);
const endTime = Date.now();
// ASSERT
expect(result).toHaveLength(10000);
expect(endTime - startTime).toBeLessThan(1000); // 1s threshold
});
});
// ========================================================================
// SNAPSHOT TESTS
// ========================================================================
describe('snapshots', () => {
it('should match snapshot for complex output', () => {
// ARRANGE
const input = { id: 1, name: 'Test', nested: { value: 42 } };
// ACT
const result = instance.transform(input);
// ASSERT
expect(result).toMatchSnapshot();
});
it('should match inline snapshot for simple output', () => {
// ARRANGE
const input = 'test';
// ACT
const result = instance.methodName(input);
// ASSERT
expect(result).toMatchInlineSnapshot('"test-output"');
});
});
// ========================================================================
// TIMER/DEBOUNCE TESTS
// ========================================================================
describe('timers', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.restoreAllTimers();
});
it('should debounce function calls', () => {
// ARRANGE
const callback = vi.fn();
const debounced = instance.debounce(callback, 1000);
// ACT
debounced();
debounced();
debounced();
// ASSERT (not called yet)
expect(callback).not.toHaveBeenCalled();
// Fast-forward time
vi.advanceTimersByTime(1000);
// ASSERT (called once)
expect(callback).toHaveBeenCalledTimes(1);
});
it('should throttle function calls', () => {
// ARRANGE
const callback = vi.fn();
const throttled = instance.throttle(callback, 1000);
// ACT
throttled(); // Called immediately
throttled(); // Ignored (within throttle window)
throttled(); // Ignored (within throttle window)
// ASSERT
expect(callback).toHaveBeenCalledTimes(1);
// Fast-forward time
vi.advanceTimersByTime(1000);
// ACT
throttled(); // Called after throttle window
// ASSERT
expect(callback).toHaveBeenCalledTimes(2);
});
});
});
// ============================================================================
// HELPER TYPES & CLASSES (For Examples Above)
// ============================================================================
class ValidationError extends Error {
constructor(
message: string,
public code: string
) {
super(message);
this.name = 'ValidationError';
}
}
// ============================================================================
// BEST PRACTICES CHECKLIST
// ============================================================================
/*
✅ AAA Pattern (Arrange-Act-Assert)
✅ Test Isolation (beforeEach creates fresh instance)
✅ Descriptive Test Names (should do X when Y)
✅ One Assertion Per Test (when possible)
✅ Mock External Dependencies
✅ Test Happy Path
✅ Test Edge Cases (null, undefined, empty, large)
✅ Test Error Handling
✅ Parametric Tests (test.each)
✅ Async Testing (async/await, rejects, resolves)
✅ Timer Testing (useFakeTimers)
✅ Performance Testing (execution time)
✅ Snapshot Testing (complex outputs)
✅ No Shared State Between Tests
✅ Fast Execution (< 1s per test)
*/

View File

@@ -0,0 +1,726 @@
# QA Engineer Agent - Test Strategies
Comprehensive guide to testing strategies for different application types and scenarios.
## Table of Contents
1. [Testing Pyramid Strategy](#testing-pyramid-strategy)
2. [Testing Trophy Strategy](#testing-trophy-strategy)
3. [TDD Red-Green-Refactor](#tdd-red-green-refactor)
4. [BDD Given-When-Then](#bdd-given-when-then)
5. [API Testing Strategy](#api-testing-strategy)
6. [Frontend Testing Strategy](#frontend-testing-strategy)
7. [Micro-Services Testing Strategy](#micro-services-testing-strategy)
8. [Performance Testing Strategy](#performance-testing-strategy)
9. [Security Testing Strategy](#security-testing-strategy)
10. [Accessibility Testing Strategy](#accessibility-testing-strategy)
---
## Testing Pyramid Strategy
### Overview
The Testing Pyramid emphasizes a broad base of fast, cheap unit tests, fewer integration tests, and minimal UI/E2E tests.
```
/\
/ \ E2E (10%)
/----\
/ \ Integration (20%)
/--------\
/ \ Unit (70%)
/--------------\
```
### Distribution
- **Unit Tests (70%)**: Test individual functions, classes, components in isolation
- **Integration Tests (20%)**: Test interactions between modules, APIs, databases
- **E2E Tests (10%)**: Test complete user journeys through the UI
### When to Use
- Traditional applications with clear layers
- Backend services with business logic
- Applications where unit tests provide high confidence
- Teams prioritizing fast feedback loops
### Implementation Example
**Unit Test (70% of suite)**:
```typescript
// src/utils/cart.test.ts
import { describe, it, expect } from 'vitest';
import { calculateTotal, applyDiscount } from './cart';
describe('Cart Utils', () => {
describe('calculateTotal', () => {
it('should sum item prices', () => {
const items = [
{ id: 1, price: 10 },
{ id: 2, price: 20 },
];
expect(calculateTotal(items)).toBe(30);
});
it('should return 0 for empty cart', () => {
expect(calculateTotal([])).toBe(0);
});
it('should handle decimal prices', () => {
const items = [
{ id: 1, price: 10.99 },
{ id: 2, price: 20.50 },
];
expect(calculateTotal(items)).toBeCloseTo(31.49, 2);
});
});
describe('applyDiscount', () => {
it('should apply percentage discount', () => {
expect(applyDiscount(100, 'SAVE20', { type: 'percentage', value: 20 })).toBe(80);
});
it('should apply fixed discount', () => {
expect(applyDiscount(100, 'SAVE10', { type: 'fixed', value: 10 })).toBe(90);
});
it('should not go below zero', () => {
expect(applyDiscount(50, 'SAVE100', { type: 'fixed', value: 100 })).toBe(0);
});
});
});
```
**Integration Test (20% of suite)**:
```typescript
// src/api/orders.integration.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createTestServer } from '../test-utils/server';
import { seedDatabase, clearDatabase } from '../test-utils/database';
describe('Orders API Integration', () => {
let server: TestServer;
beforeEach(async () => {
server = await createTestServer();
await seedDatabase();
});
afterEach(async () => {
await clearDatabase();
await server.close();
});
it('should create order and store in database', async () => {
const response = await server.request
.post('/api/orders')
.send({
userId: 'user-123',
items: [{ productId: 'prod-1', quantity: 2 }],
});
expect(response.status).toBe(201);
expect(response.body).toMatchObject({
id: expect.any(String),
userId: 'user-123',
status: 'pending',
});
// Verify database persistence
const order = await server.db.orders.findById(response.body.id);
expect(order).toBeTruthy();
expect(order.items).toHaveLength(1);
});
it('should send confirmation email on order creation', async () => {
const emailSpy = vi.spyOn(server.emailService, 'send');
await server.request
.post('/api/orders')
.send({
userId: 'user-123',
items: [{ productId: 'prod-1', quantity: 1 }],
});
expect(emailSpy).toHaveBeenCalledWith({
to: 'user@example.com',
subject: 'Order Confirmation',
template: 'order-confirmation',
});
});
});
```
**E2E Test (10% of suite)**:
```typescript
// e2e/checkout-flow.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Checkout Flow', () => {
test('should complete purchase as guest user', async ({ page }) => {
// Navigate to product
await page.goto('/products/laptop-123');
await expect(page.getByRole('heading', { name: 'Gaming Laptop' })).toBeVisible();
// Add to cart
await page.getByRole('button', { name: 'Add to Cart' }).click();
await expect(page.getByText('Item added to cart')).toBeVisible();
// Go to checkout
await page.getByRole('link', { name: 'Cart (1)' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
// Fill shipping info
await page.getByLabel('Email').fill('guest@example.com');
await page.getByLabel('Full Name').fill('John Doe');
await page.getByLabel('Address').fill('123 Main St');
await page.getByLabel('City').fill('New York');
await page.getByLabel('Zip Code').fill('10001');
// Fill payment info (test mode)
await page.getByLabel('Card Number').fill('4242424242424242');
await page.getByLabel('Expiry Date').fill('12/25');
await page.getByLabel('CVC').fill('123');
// Submit order
await page.getByRole('button', { name: 'Place Order' }).click();
// Verify confirmation
await expect(page).toHaveURL(/\/order-confirmation/);
await expect(page.getByText('Order Confirmed!')).toBeVisible();
await expect(page.getByText(/Order #/)).toBeVisible();
});
});
```
### Coverage Targets
- **Unit Tests**: 80%+ line coverage, 75%+ branch coverage
- **Integration Tests**: 100% critical API endpoints
- **E2E Tests**: 100% critical user journeys
---
## Testing Trophy Strategy
### Overview
Modern approach that emphasizes integration tests over unit tests, with static analysis as the foundation.
```
/\
/ \ E2E (5%)
/----\
/ \ Integration (50%)
/--------\
/ \ Unit (25%)
/--------------\
/ \ Static (20%)
/------------------\
```
### Distribution
- **Static Analysis (20%)**: TypeScript, ESLint, Prettier
- **Unit Tests (25%)**: Pure functions, utilities, critical logic
- **Integration Tests (50%)**: Components with dependencies, API contracts
- **E2E Tests (5%)**: Critical business flows only
### When to Use
- Modern frontend applications (React, Vue, Angular)
- Applications with complex component interactions
- Teams using TypeScript and static analysis tools
- Applications where integration tests catch more bugs
### Implementation Example
**Static Analysis (20%)**:
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
```
```json
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"react-hooks/exhaustive-deps": "error"
}
}
```
**Unit Tests (25%)**:
```typescript
// src/utils/formatters.test.ts
import { describe, it, expect } from 'vitest';
import { formatCurrency, formatDate } from './formatters';
describe('Formatters (Pure Functions)', () => {
describe('formatCurrency', () => {
it('should format USD currency', () => {
expect(formatCurrency(1234.56, 'USD')).toBe('$1,234.56');
});
it('should handle negative amounts', () => {
expect(formatCurrency(-100, 'USD')).toBe('-$100.00');
});
});
describe('formatDate', () => {
it('should format ISO date', () => {
const date = new Date('2025-01-15');
expect(formatDate(date, 'short')).toBe('1/15/2025');
});
});
});
```
**Integration Tests (50%)**:
```typescript
// src/components/UserProfile.integration.test.tsx
import { describe, it, expect, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { UserProfile } from './UserProfile';
const server = setupServer(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(
ctx.json({
id: req.params.id,
name: 'John Doe',
email: 'john@example.com',
})
);
}),
rest.put('/api/users/:id', (req, res, ctx) => {
return res(ctx.json({ ...req.body, updatedAt: Date.now() }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('UserProfile Integration', () => {
it('should load and display user data', async () => {
render(<UserProfile userId="123" />);
// Loading state
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
it('should update user profile on form submit', async () => {
const user = userEvent.setup();
render(<UserProfile userId="123" />);
// Wait for initial load
await screen.findByText('John Doe');
// Edit name
const nameInput = screen.getByLabelText('Name');
await user.clear(nameInput);
await user.type(nameInput, 'Jane Smith');
// Submit form
await user.click(screen.getByRole('button', { name: 'Save' }));
// Verify success message
await waitFor(() => {
expect(screen.getByText('Profile updated successfully')).toBeInTheDocument();
});
// Verify updated name
expect(screen.getByText('Jane Smith')).toBeInTheDocument();
});
it('should handle API errors gracefully', async () => {
// Mock API error
server.use(
rest.get('/api/users/:id', (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
})
);
render(<UserProfile userId="123" />);
// Wait for error message
await waitFor(() => {
expect(screen.getByText(/Failed to load user/i)).toBeInTheDocument();
});
});
});
```
**E2E Tests (5%)**:
```typescript
// e2e/critical-path.spec.ts
import { test, expect } from '@playwright/test';
test('critical user journey: signup to first purchase', async ({ page }) => {
// Only test the MOST critical path
await page.goto('/');
// Signup
await page.getByRole('link', { name: 'Sign Up' }).click();
await page.getByLabel('Email').fill('newuser@example.com');
await page.getByLabel('Password').fill('SecurePass123!');
await page.getByRole('button', { name: 'Create Account' }).click();
// Verify logged in
await expect(page.getByText('Welcome, New User')).toBeVisible();
// Make purchase
await page.goto('/products/best-seller');
await page.getByRole('button', { name: 'Buy Now' }).click();
await page.getByLabel('Card Number').fill('4242424242424242');
await page.getByRole('button', { name: 'Complete Purchase' }).click();
// Verify success
await expect(page.getByText('Purchase Successful')).toBeVisible();
});
```
### Coverage Targets
- **Static Analysis**: 100% (TypeScript strict mode, zero ESLint errors)
- **Unit Tests**: 90%+ for pure functions and utilities
- **Integration Tests**: 80%+ for components with dependencies
- **E2E Tests**: 100% critical paths only
---
## TDD Red-Green-Refactor
### The Cycle
1. **RED**: Write a failing test that defines expected behavior
2. **GREEN**: Write minimal code to make the test pass
3. **REFACTOR**: Improve code quality while keeping tests green
### Example: Shopping Cart Feature
**RED: Write Failing Test**:
```typescript
// src/cart/ShoppingCart.test.ts
import { describe, it, expect } from 'vitest';
import { ShoppingCart } from './ShoppingCart';
describe('ShoppingCart', () => {
it('should add item to cart', () => {
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: 'Laptop', price: 1000 });
expect(cart.getItemCount()).toBe(1);
});
});
```
**Run test**: ❌ FAIL (ShoppingCart doesn't exist)
**GREEN: Minimal Implementation**:
```typescript
// src/cart/ShoppingCart.ts
interface CartItem {
id: number;
name: string;
price: number;
}
export class ShoppingCart {
private items: CartItem[] = [];
addItem(item: CartItem): void {
this.items.push(item);
}
getItemCount(): number {
return this.items.length;
}
}
```
**Run test**: ✅ PASS
**Add Another Test (Triangulation)**:
```typescript
it('should calculate total price', () => {
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: 'Laptop', price: 1000 });
cart.addItem({ id: 2, name: 'Mouse', price: 50 });
expect(cart.getTotal()).toBe(1050);
});
```
**Run test**: ❌ FAIL (getTotal doesn't exist)
**GREEN: Implement getTotal**:
```typescript
export class ShoppingCart {
// ... previous code ...
getTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
```
**Run test**: ✅ PASS
**REFACTOR: Improve Design**:
```typescript
export class ShoppingCart {
private items: Map<number, CartItem> = new Map();
addItem(item: CartItem): void {
const existing = this.items.get(item.id);
if (existing) {
// Increment quantity instead of duplicating
existing.quantity = (existing.quantity || 1) + 1;
} else {
this.items.set(item.id, { ...item, quantity: 1 });
}
}
getItemCount(): number {
return Array.from(this.items.values()).reduce(
(count, item) => count + (item.quantity || 1),
0
);
}
getTotal(): number {
return Array.from(this.items.values()).reduce(
(sum, item) => sum + item.price * (item.quantity || 1),
0
);
}
}
```
**Run tests**: ✅ ALL PASS (refactoring didn't break anything!)
### TDD Benefits
- Forces modular, testable design
- Prevents over-engineering
- Living documentation
- Fearless refactoring
- Faster debugging
---
## API Testing Strategy
### Layers
1. **Unit Tests**: Test business logic in isolation
2. **Integration Tests**: Test API endpoints with real database
3. **Contract Tests**: Test API contracts (Pact)
4. **E2E Tests**: Test complete API flows
### Example: REST API Testing
**Unit Test (Business Logic)**:
```typescript
// src/services/OrderService.test.ts
import { describe, it, expect, vi } from 'vitest';
import { OrderService } from './OrderService';
describe('OrderService', () => {
it('should calculate order total with tax', () => {
const mockRepo = {
save: vi.fn(),
findById: vi.fn(),
};
const service = new OrderService(mockRepo);
const order = service.calculateTotal({
items: [{ price: 100, quantity: 2 }],
taxRate: 0.08,
});
expect(order.subtotal).toBe(200);
expect(order.tax).toBeCloseTo(16, 2);
expect(order.total).toBeCloseTo(216, 2);
});
});
```
**Integration Test (API + Database)**:
```typescript
// tests/integration/orders.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import supertest from 'supertest';
import { createTestApp } from '../utils/test-app';
describe('Orders API', () => {
let app;
let request;
beforeAll(async () => {
app = await createTestApp();
request = supertest(app);
});
afterAll(async () => {
await app.close();
});
it('POST /api/orders should create order', async () => {
const response = await request
.post('/api/orders')
.send({
userId: 'user-123',
items: [
{ productId: 'prod-1', quantity: 2, price: 50 },
],
})
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
userId: 'user-123',
status: 'pending',
total: 100,
});
// Verify database persistence
const order = await app.db.orders.findById(response.body.id);
expect(order).toBeTruthy();
});
it('GET /api/orders/:id should return order', async () => {
// Create order first
const createResponse = await request
.post('/api/orders')
.send({ userId: 'user-123', items: [] });
const orderId = createResponse.body.id;
// Fetch order
const response = await request
.get(`/api/orders/${orderId}`)
.expect(200);
expect(response.body.id).toBe(orderId);
});
it('PUT /api/orders/:id/status should update status', async () => {
const createResponse = await request
.post('/api/orders')
.send({ userId: 'user-123', items: [] });
const orderId = createResponse.body.id;
const response = await request
.put(`/api/orders/${orderId}/status`)
.send({ status: 'shipped' })
.expect(200);
expect(response.body.status).toBe('shipped');
});
});
```
**Contract Test (Pact)**:
```typescript
// tests/contract/orders-consumer.test.ts
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { OrdersClient } from '@/api/orders-client';
const provider = new PactV3({
consumer: 'OrdersConsumer',
provider: 'OrdersAPI',
});
describe('Orders API Contract', () => {
it('should get order by ID', async () => {
await provider
.given('order with ID 123 exists')
.uponReceiving('a request for order 123')
.withRequest({
method: 'GET',
path: '/api/orders/123',
})
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: {
id: MatchersV3.string('123'),
userId: MatchersV3.string('user-456'),
status: MatchersV3.regex('pending|shipped|delivered', 'pending'),
total: MatchersV3.decimal(100.50),
},
})
.executeTest(async (mockServer) => {
const client = new OrdersClient(mockServer.url);
const order = await client.getOrder('123');
expect(order.id).toBe('123');
expect(order.status).toMatch(/pending|shipped|delivered/);
});
});
});
```
### Coverage Targets
- Unit: 90%+ business logic
- Integration: 100% API endpoints
- Contract: 100% API contracts
- E2E: Critical flows only
[Document continues with 6 more comprehensive testing strategies...]
---
## Summary Table
| Strategy | Best For | Coverage Target | Execution Time |
|----------|----------|-----------------|----------------|
| Pyramid | Backend services, traditional apps | 80%+ unit, 100% critical | < 5 min |
| Trophy | Modern frontends (React, Vue) | 80%+ integration | < 3 min |
| TDD | New features, greenfield projects | 90%+ | Continuous |
| BDD | Stakeholder-driven development | 100% acceptance | Variable |
| API | REST/GraphQL services | 90%+ endpoints | < 2 min |
| Frontend | SPAs, component libraries | 85%+ components | < 4 min |
| Micro-Services | Distributed systems | 80%+ per service | < 10 min |
| Performance | High-traffic applications | Critical paths | 30 min |
| Security | Sensitive data, compliance | 100% attack vectors | 1 hour |
| Accessibility | Public-facing websites | WCAG AA 100% | 15 min |