--- name: testing-master description: | Master testing skill combining TDD, E2E testing, Playwright, and webapp testing. Use when writing tests, setting up testing infrastructure, or fixing failing tests. --- # Testing Master Comprehensive testing skill combining: TDD, E2E testing, Playwright, webapp testing, and testing patterns. --- ## Quick Reference | Task | Use Section | |------|-------------| | Write tests first | **TDD** | | E2E browser tests | **E2E Testing** | | Playwright automation | **Playwright** | | Fix failing tests | **Test Fixing** | | Generate unit tests | **Unit Testing** | | Web app testing | **Webapp Testing** | --- ## Test-Driven Development (TDD) ### Core Principle > "Write the test first. Watch it fail. Write minimal code to pass." ### The Iron Law ``` NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST ``` ### TDD Cycle (Red-Green-Refactor) 1. **RED** - Write a failing test 2. **GREEN** - Write minimal code to pass 3. **REFACTOR** - Improve code while keeping tests passing ### When to Use TDD - ✅ New features - ✅ Bug fixes - ✅ Refactoring - ✅ Behavior changes - ❌ Throwaway prototypes - ❌ Generated code - ❌ Configuration files ### Example: TDD Cycle ```javascript // 1. RED - Write failing test describe('Calculator', () => { it('should add two numbers', () => { const calc = new Calculator(); expect(calc.add(2, 3)).toBe(5); }); }); // 2. GREEN - Minimal implementation class Calculator { add(a, b) { return a + b; } } // 3. REFACTOR - Improve (if needed) ``` ### Test Naming Conventions ```javascript describe('UserService', () => { it('should return null for non-existent user', () => {}); it('should hash password before storing', () => {}); it('should throw ValidationError for invalid email', () => {}); }); ``` --- ## E2E Testing ### Playwright Setup ```bash npm init playwright@latest npx playwright install chromium ``` ### Basic Test Structure ```javascript import { test, expect } from '@playwright/test'; test.describe('Login Flow', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); }); test('should login with valid credentials', async ({ page }) => { await page.fill('[name="email"]', 'user@example.com'); await page.fill('[name="password"]', 'password123'); await page.click('[type="submit"]'); await expect(page).toHaveURL('/dashboard'); }); test('should show error for invalid credentials', async ({ page }) => { await page.fill('[name="email"]', 'invalid@example.com'); await page.fill('[name="password"]', 'wrong'); await page.click('[type="submit"]'); await expect(page.locator('.error')).toContainText('Invalid credentials'); }); }); ``` ### Page Object Model ```javascript // pages/LoginPage.js class LoginPage { constructor(page) { this.page = page; this.emailInput = page.locator('[name="email"]'); this.passwordInput = page.locator('[name="password"]'); this.submitButton = page.locator('[type="submit"]'); } async login(email, password) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } } // test.js test('login', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login('user@example.com', 'password123'); }); ``` ### Visual Regression Testing ```javascript import { test, expect } from '@playwright/test'; test('homepage visual regression', async ({ page }) => { await page.goto('/'); const screenshot = await page.screenshot(); expect(screenshot).toMatchSnapshot('homepage.png'); }); ``` ### Cross-Browser Testing ```javascript test.describe('Browser Compatibility', () => { test('works on Chrome', async ({ browserName }) => { test.skip(browserName !== 'chromium', 'Skip on non-Chrome'); // Chrome-specific test }); }); ``` --- ## Playwright Testing ### Locators (Priority Order) 1. **role** - `getByRole('button', { name: 'Submit' })` 2. **label** - `getByLabel('Email')` 3. **placeholder** - `getByPlaceholder('Enter email')` 4. **text** - `getByText('Sign in')` 5. **testid** - `getByTestId('submit-btn')` 6. **CSS** - `locator('.submit')` 7. **XPath** - `locator('//button[@type="submit"]')` ### Common Actions ```javascript // Click await page.click('button'); // Fill input await page.fill('input[name="email"]', 'test@example.com'); // Select await page.selectOption('select', 'Option 1'); // Checkbox await page.check('input[type="checkbox"]'); // Hover await page.hover('.dropdown'); // Drag and drop await page.dragAndDrop('.source', '.target'); ``` ### Assertions ```javascript expect(locator).toBeVisible() expect(locator).toBeHidden() expect(locator).toBeEnabled() expect(locator).toBeDisabled() expect(locator).toHaveText('expected') expect(locator).toContainText('partial') expect(locator).toHaveValue('value') expect(locator).toHaveCount(5) expect(page).toHaveURL('**/dashboard') expect(page).toHaveTitle('Dashboard') ``` ### Network Interception ```javascript await page.route('**/api/**', route => { route.fulfill({ status: 200, body: JSON.stringify({ data: 'mocked' }), }); }); ``` ### File Upload ```javascript await page.setInputFiles('input[type="file"]', 'path/to/file.pdf'); ``` --- ## Webapp Testing ### Test Coverage Checklist - [ ] Homepage loads - [ ] Navigation works - [ ] Forms submit correctly - [ ] Validation messages appear - [ ] Error states handled - [ ] Loading states work - [ ] Authentication flows - [ ] Responsive design - [ ] Accessibility (a11y) - [ ] Performance ### Accessibility Testing ```javascript test('accessibility check', async ({ page }) => { await page.goto('/'); // Check for accessibility violations const violations = await new AxeBuilder({ page }).analyze(); expect(violations.length).toBe(0); }); ``` ### Mobile Testing ```javascript test('mobile responsive', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/'); // Mobile-specific assertions }); ``` ### API Testing ```javascript test('API integration', async ({ request }) => { const response = await request.get('https://api.example.com/users'); expect(response.status()).toBe(200); expect(response.ok()).toBeTruthy(); }); ``` --- ## Test Fixing ### Debugging Failing Tests 1. **Read the error** - What is it saying? 2. **Check the test** - Is it testing the right thing? 3. **Check the code** - Is the implementation correct? 4. **Check the mocks** - Are they set up correctly? 5. **Check the data** - Is the test data valid? ### Common Issues | Issue | Solution | |-------|----------| | Flaky tests | Add retries, stabilize timing | | Race conditions | Add waits, use explicit conditions | | Environment differences | Use Docker, consistent setup | | Data dependencies | Use fixtures, clean state | ### Test Isolation ```javascript // Bad - shared state let user; test('creates user', () => { user = createUser(); }); test('modifies user', () => { modifyUser(user.id); }); // Good - isolated test('creates and modifies user', () => { const user = createUser(); modifyUser(user.id); // assertions }); ``` --- ## Best Practices 1. **AAA Pattern** - Arrange, Act, Assert 2. **One Assertion** - Per test when possible 3. **Descriptive Names** - `it('should return 404 for missing resource')` 4. **Fast Tests** - Mock external dependencies 5. **Independent Tests** - No order dependency 6. **Real Data** - Don't over-mock 7. **Coverage** - Aim for meaningful coverage, not 100%