Some_claude_skills test-automation-expert
Comprehensive test automation specialist covering unit, integration, and E2E testing strategies. Expert in Jest, Vitest, Playwright, Cypress, pytest, and modern testing frameworks. Guides test
git clone https://github.com/curiositech/some_claude_skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/curiositech/some_claude_skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/test-automation-expert" ~/.claude/skills/erichowens-some-claude-skills-test-automation-expert && rm -rf "$T"
.claude/skills/test-automation-expert/SKILL.mdTest Automation Expert
Comprehensive testing guidance from unit to E2E. Designs test strategies, implements automation, and optimizes coverage for sustainable quality.
When to Use
Use for:
- Designing test strategy for new projects
- Setting up testing frameworks (Jest, Vitest, Playwright, Cypress, pytest)
- Writing effective unit, integration, and E2E tests
- Optimizing test coverage and eliminating gaps
- Debugging flaky tests
- CI/CD test pipeline configuration
- Test-Driven Development (TDD) guidance
- Mocking strategies and test fixtures
Do NOT use for:
- Manual QA test case writing - this is automation-focused
- Load/performance testing - use performance-engineer skill
- Security testing - use security-auditor skill
- API contract testing only - use backend-architect for API design
Test Pyramid Philosophy
/\ / \ E2E Tests (10%) /----\ - Critical user journeys / \ - Cross-browser validation /--------\ / \ Integration Tests (20%) / \ - API contracts /--------------\- Component interactions / \ /------------------\ Unit Tests (70%) - Fast, isolated, deterministic - Business logic validation
Distribution Guidelines
| Test Type | Percentage | Execution Time | Purpose |
|---|---|---|---|
| Unit | 70% | < 100ms each | Logic validation |
| Integration | 20% | < 1s each | Component contracts |
| E2E | 10% | < 30s each | Critical paths |
Framework Selection
JavaScript/TypeScript
| Framework | Best For | Speed | Config Complexity |
|---|---|---|---|
| Vitest | Vite projects, modern ESM | Fastest | Low |
| Jest | React, established projects | Fast | Medium |
| Playwright | E2E, cross-browser | N/A | Low |
| Cypress | E2E, component testing | N/A | Medium |
Python
| Framework | Best For | Speed | Features |
|---|---|---|---|
| pytest | Everything | Fast | Fixtures, plugins |
| unittest | Standard library | Medium | Built-in |
| hypothesis | Property-based | Varies | Generative |
Decision Tree: Framework Selection
New project? ├── Yes → Using Vite? │ ├── Yes → Vitest │ └── No → Jest or Vitest (both work) └── No → What exists? ├── Jest → Keep Jest (migration cost rarely worth it) ├── Mocha → Consider migration to Vitest └── Nothing → Vitest (modern default) Need E2E? ├── Cross-browser critical → Playwright ├── Developer experience priority → Cypress └── Both → Playwright (more flexible)
Unit Testing Patterns
Good Unit Test Anatomy
describe('UserService', () => { describe('validateEmail', () => { // Arrange-Act-Assert pattern it('should accept valid email formats', () => { // Arrange const validEmails = ['user@example.com', 'name+tag@domain.co']; // Act & Assert validEmails.forEach(email => { expect(validateEmail(email)).toBe(true); }); }); it('should reject invalid email formats', () => { // Arrange const invalidEmails = ['invalid', '@missing.com', 'no@tld']; // Act & Assert invalidEmails.forEach(email => { expect(validateEmail(email)).toBe(false); }); }); // Edge cases explicitly tested it('should handle empty string', () => { expect(validateEmail('')).toBe(false); }); it('should handle null/undefined', () => { expect(validateEmail(null)).toBe(false); expect(validateEmail(undefined)).toBe(false); }); }); });
Mocking Strategies
// ✅ Good: Mock at boundaries jest.mock('../services/api', () => ({ fetchUser: jest.fn() })); // ✅ Good: Explicit mock setup per test beforeEach(() => { fetchUser.mockReset(); }); it('handles user not found', async () => { fetchUser.mockRejectedValue(new NotFoundError()); await expect(getUser(123)).rejects.toThrow('User not found'); }); // ❌ Bad: Mocking implementation details jest.mock('../utils/internal-helper'); // Don't mock internals
Test Isolation Checklist
- Each test can run independently
- No shared mutable state between tests
- Database/API state reset between tests
- No test order dependencies
- Parallel execution safe
Integration Testing Patterns
API Integration Test
describe('POST /api/users', () => { let app; let db; beforeAll(async () => { db = await createTestDatabase(); app = createApp({ db }); }); afterAll(async () => { await db.close(); }); beforeEach(async () => { await db.clear(); }); it('creates user with valid data', async () => { const response = await request(app) .post('/api/users') .send({ name: 'Test', email: 'test@example.com' }) .expect(201); expect(response.body).toMatchObject({ id: expect.any(String), name: 'Test', email: 'test@example.com' }); // Verify side effects const dbUser = await db.users.findById(response.body.id); expect(dbUser).toBeDefined(); }); it('rejects duplicate email', async () => { await db.users.create({ name: 'Existing', email: 'test@example.com' }); await request(app) .post('/api/users') .send({ name: 'New', email: 'test@example.com' }) .expect(409); }); });
Component Integration (React)
import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserProfile } from './UserProfile'; import { UserProvider } from '../context/UserContext'; describe('UserProfile integration', () => { it('loads and displays user data', async () => { render( <UserProvider> <UserProfile userId="123" /> </UserProvider> ); // Verify loading state expect(screen.getByRole('progressbar')).toBeInTheDocument(); // Wait for data await waitFor(() => { expect(screen.getByText('John Doe')).toBeInTheDocument(); }); // Verify loaded state expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); }); });
E2E Testing Patterns
Playwright Best Practices
import { test, expect } from '@playwright/test'; test.describe('Checkout Flow', () => { test.beforeEach(async ({ page }) => { // Seed test data via API await page.request.post('/api/test/seed', { data: { scenario: 'checkout-ready' } }); }); test('complete purchase with credit card', async ({ page }) => { await page.goto('/cart'); // Use accessible selectors await page.getByRole('button', { name: 'Proceed to checkout' }).click(); // Fill payment form await page.getByLabel('Card number').fill('4242424242424242'); await page.getByLabel('Expiry').fill('12/25'); await page.getByLabel('CVC').fill('123'); // Complete purchase await page.getByRole('button', { name: 'Pay now' }).click(); // Verify success await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible(); await expect(page.getByText(/Order #\d+/)).toBeVisible(); }); test('shows error for declined card', async ({ page }) => { await page.goto('/checkout'); // Use test card that triggers decline await page.getByLabel('Card number').fill('4000000000000002'); await page.getByLabel('Expiry').fill('12/25'); await page.getByLabel('CVC').fill('123'); await page.getByRole('button', { name: 'Pay now' }).click(); await expect(page.getByRole('alert')).toContainText('Card declined'); }); });
Flaky Test Detection & Prevention
Common Causes:
- Race conditions in async operations
- Time-dependent tests
- Shared state between tests
- Network variability
- Animation/transition timing
Fixes:
// ❌ Bad: Fixed timeout await page.waitForTimeout(2000); // ✅ Good: Wait for specific condition await expect(page.getByText('Loaded')).toBeVisible(); // ❌ Bad: Checking exact time expect(new Date()).toEqual(specificDate); // ✅ Good: Mock time jest.useFakeTimers(); jest.setSystemTime(new Date('2024-01-15')); // ❌ Bad: Depending on animation completion await page.click('.button'); expect(await page.isVisible('.modal')).toBe(true); // ✅ Good: Wait for animation await page.click('.button'); await expect(page.locator('.modal')).toBeVisible();
Coverage Optimization
What to Measure
| Metric | Target | Priority |
|---|---|---|
| Line coverage | 80%+ | Medium |
| Branch coverage | 75%+ | High |
| Function coverage | 90%+ | Medium |
| Critical path coverage | 100% | Critical |
Coverage Configuration
// vitest.config.js export default defineConfig({ test: { coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'test/', '**/*.d.ts', '**/*.config.*', '**/index.ts', // barrel files ], thresholds: { branches: 75, functions: 80, lines: 80, statements: 80 } } } });
Finding Coverage Gaps
# Generate detailed coverage report npx vitest run --coverage # Find untested files npx vitest run --coverage --reporter=json | jq '.coverageMap | to_entries | map(select(.value.s | values | any(. == 0))) | .[].key'
CI/CD Integration
GitHub Actions
name: Tests on: [push, pull_request] jobs: unit-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - run: npm test -- --coverage - uses: codecov/codecov-action@v4 e2e-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - run: npx playwright install --with-deps - run: npm run test:e2e - uses: actions/upload-artifact@v4 if: failure() with: name: playwright-report path: playwright-report/
Test Parallelization
// vitest.config.js - parallel by default export default defineConfig({ test: { pool: 'threads', poolOptions: { threads: { singleThread: false } } } }); // playwright.config.js export default defineConfig({ workers: process.env.CI ? 2 : undefined, fullyParallel: true });
Anti-Patterns
Anti-Pattern: Testing Implementation Details
What it looks like:
// ❌ Testing internal state expect(component.state.isLoading).toBe(true); // ❌ Testing private methods expect(service._calculateHash()).toBe('abc123');
Why wrong: Couples tests to implementation, breaks on refactors
Instead:
// ✅ Test observable behavior expect(screen.getByRole('progressbar')).toBeInTheDocument(); // ✅ Test public interface expect(service.getHash()).toBe('abc123');
Anti-Pattern: Over-Mocking
What it looks like:
// ❌ Mocking everything jest.mock('../utils/format'); jest.mock('../utils/validate'); jest.mock('../utils/transform');
Why wrong: Tests pass even when real code is broken
Instead: Mock only at system boundaries (APIs, databases, external services)
Anti-Pattern: Flaky Acceptance
What it looks like: "That test is just flaky, skip it"
Why wrong: Flaky tests indicate real problems (race conditions, timing issues)
Instead: Fix the flakiness or quarantine while fixing
Anti-Pattern: Coverage Theater
What it looks like:
// ❌ Testing for coverage, not behavior it('covers the function', () => { myFunction(); // No assertions! });
Why wrong: 100% coverage with 0% confidence
Instead: Every test should assert meaningful behavior
Quick Commands
# Run all tests npm test # Run with coverage npm test -- --coverage # Run specific file npm test -- src/utils/format.test.ts # Run in watch mode npm test -- --watch # Run E2E tests npx playwright test # Run E2E with UI npx playwright test --ui # Debug E2E test npx playwright test --debug # Update snapshots npm test -- -u
Reference Files
- Comprehensive test strategy frameworkreferences/test-strategy.md
- Detailed framework comparisonreferences/framework-comparison.md
- Coverage optimization techniquesreferences/coverage-patterns.md
- CI/CD pipeline configurationsreferences/ci-integration.md
Covers: Test strategy | Unit testing | Integration testing | E2E testing | Coverage | CI/CD | Flaky test debugging
Use with: security-auditor (security tests) | performance-engineer (load tests) | code-reviewer (test quality)