---
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%
