Write, structure, and maintain tests across unit, integration, E2E, accessibility, and performance layers. The goal is tests that catch regressions, document behavior, and run fast in CI - not tests that exist to inflate coverage numbers.
Target versions (September 2026):
Vitest 5.0.0, Jest 30.5.1
Playwright 1.62.1, Cypress 16.0.0 (both Vitest and Cypress are major upgrades; review migration notes)
pytest 9.1.1, pytest-cov 7.1.0
Go 1.27.1 (testing stdlib, testing/synctest GA)
Rust 1.98.1 (cargo test, cargo-nextest 0.9.143)
Testing Library 16.3.3 (@testing-library/react)
axe-core 4.13.0 (@axe-core/playwright)
Grafana k6 2.2.0
When to use
Writing new tests (unit, integration, E2E, accessibility, performance)
Debugging flaky or failing tests
Designing test architecture for a project (fixture strategies, factory patterns, test data)
Setting up test infrastructure in CI (parallelization, sharding, coverage gates)
Choosing testing tools or migrating between test frameworks
Implementing TDD workflow
Adding accessibility or visual regression tests to an existing suite
When NOT to use
Reviewing existing test quality or correctness as part of a code review - use code-review
Security-specific testing (penetration testing, OWASP checks) - use security-audit
Cleaning up verbose/sloppy test code - use anti-slop
Ad-hoc web browsing, scraping, or page interaction outside of tests - use browse
CI/CD pipeline architecture (test jobs run inside pipelines, but pipeline design is ci-cd's domain) - use ci-cd
Database testing patterns at the engine level - use databases
Writing or refining LLM prompts (use prompt-generator)
Infrastructure or configuration validation outside tests (use terraform, ansible, or kubernetes)
AI/ML model evaluation or LLM output scoring - use ai-ml
Infrastructure-level load or chaos testing beyond application tests (use kubernetes for cluster-level chaos, or ci-cd for pipeline-integrated load test orchestration)
AI Self-Check
AI tools consistently produce the same testing mistakes. Before returning any generated test code, verify against this list:
Tests assert behavior, not implementation - no testing private methods or internal state
Each test has exactly one reason to fail (single assertion concept, not single assert call)
Test names describe the scenario and expected outcome, not the method name
Mocks/stubs are scoped to the test - no shared mutable mock state across tests
No hardcoded ports, paths, or timestamps that break on other machines or in CI
Async tests properly await all promises/futures - no fire-and-forget assertions
Test data is isolated - each test creates its own state, no dependency on test execution order
Cleanup happens even when assertions fail (use afterEach/teardown/t.Cleanup/Drop)
No sleep() or fixed delays for async waits - use polling, retries, or event-based waits
Coverage threshold is realistic (80% line coverage is a good default; 100% is a lie)
Snapshot tests have been reviewed manually before committing (blind --update is a bug factory)
E2E selectors use data-testid, role, or accessible names - not CSS classes or DOM structure
Runner APIs current: pytest, Vitest, Jest, Playwright, and Testing Library examples match current runner behavior
Flake source identified: retries are not used to hide nondeterminism without diagnosis
Cross-cutting agent hygiene applied - see references/agent-hygiene.md
Performance
Split fast unit tests from integration, browser, and performance suites.
Use fixtures and test data builders to avoid repeated expensive setup.
Shard or parallelize only after isolating shared state, ports, databases, and clocks.
Best Practices
Test behavior through stable public interfaces, not implementation details.
Use stable roles/test IDs for UI tests; do not select generated CSS classes.
Every regression fix gets a failing test that would have caught the bug.
Workflow
Step 1: Determine scope
Based on context:
New feature -> write tests alongside or before the code (TDD when appropriate)
Bug fix -> write a failing test first that reproduces the bug, then fix
Existing untested code -> prioritize critical paths, not 100% coverage
Test infrastructure -> set up runners, CI config, coverage gates
Identify the project's existing test framework from config files (vitest.config.ts, jest.config.*, pyproject.toml, Cargo.toml, *_test.go, playwright.config.ts). Match it. Don't introduce a second test runner without a reason.
Step 2: Choose the test layer
Layer
Tests what
Speed
When to use
Unit
Single function/module in isolation
ms
Pure logic, utilities, data transforms, state machines
Integration
Multiple modules, real dependencies
seconds
API handlers, database queries, service boundaries
E2E
Full user flows through the UI
seconds-minutes
Critical paths, checkout flows, auth, onboarding
Accessibility
WCAG compliance, screen reader compat
seconds
Every user-facing component/page
Visual
Screenshot comparison
seconds
UI components after style changes
Performance
Load, latency, throughput
minutes
Before releases, after arch changes
The testing pyramid still holds: many unit tests, fewer integration tests, fewest E2E tests. Invert it and your CI takes 45 minutes and everyone ignores test failures.
Step 3: Write the test
Follow the language-specific patterns below. Universal principles:
Arrange-Act-Assert (or Given-When-Then):
// Arrange: set up test data and dependencies
// Act: call the thing being tested
// Assert: verify the outcome
Test naming: describe the scenario, not the function.
# Bad: test_calculate_total
# Good: test_calculate_total_applies_discount_when_cart_exceeds_100
# Good: it("returns 401 when token is expired")
Step 4: Validate
Run checks appropriate to the changed behavior and every required repository gate. Run the
full suite when required or when the affected surface warrants it; do not add tests that
mirror reversible, low-impact edits. Repeat or broaden passing checks only for new changes,
failures, or unresolved concerns.
Check coverage delta: new code should be covered, but don't chase vanity numbers
Run in CI if possible - tests that pass locally but fail in CI are the worst kind
TDD Workflow
Use TDD when the behavior is well-defined upfront. Skip it when exploring or prototyping.
Red: write a test that fails (confirm it fails for the right reason)
Green: write the minimum code to make the test pass (ugly is fine)
Refactor: clean up without changing behavior (tests still pass)
TDD works best for: pure functions, data transformations, state machines, API contracts, bug reproduction.
TDD works poorly for: UI layout, exploratory prototyping, integration with undocumented APIs.
Mocking Strategy
Mock at boundaries, not everywhere. Over-mocking produces tests that pass while the real code is broken.
What to mock
What NOT to mock
External APIs (HTTP, gRPC)
Your own pure functions
Database (when unit testing)
Data transformations
Time/dates, random values
Simple utility code
File system (when impractical)
The module under test
Third-party SDKs
Standard library functions
Prefer fakes over mocks when possible. An in-memory database implementation tests more real behavior than a mock that returns canned responses.
Injectable clock for TTL/time-dependent tests - pass a clock dependency rather than calling Date.now() or time.Now() directly:
Isolate per test. Shared mutable fixtures cause order-dependent failures.
Use builders/factories over raw object literals - defaults prevent test brittleness.
Database fixtures: use transactions that roll back after each test (pytest db fixture, Jest beforeEach with rollback). Seeded test databases beat shared staging data.
File fixtures: use temp directories (tmp_path in pytest, os.MkdirTemp in Go, tempfile in Rust). Clean up in teardown.
Accessibility Testing
Catch WCAG violations automatically. Not a replacement for manual testing, but catches the mechanical stuff (missing alt text, broken ARIA, contrast ratios, keyboard traps).
Use @axe-core/playwright - run new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa"]).analyze() and assert zero violations. Run axe scans on every page/component. Exclude known issues with .exclude() and track them as tech debt, not permanent exceptions.
Read references/e2e-accessibility.md for Playwright E2E patterns, visual regression setup, and CI accessibility gates.
Performance Testing
Two categories: micro-benchmarks (is this function fast enough?) and load tests (does the system handle traffic?).
Micro-benchmarks
Go: func BenchmarkX(b *testing.B) - built into the stdlib
Rust: cargo bench with criterion (criterion = "0.6")
Don't run load tests against production without explicit approval. Don't run them in CI unless you have dedicated infrastructure for it.
CI Integration
Test parallelization
Vitest/Jest: built-in worker parallelism. Vitest uses Vite's module graph for smart test file distribution.
Playwright: --shard=1/4 for splitting across CI runners. --workers=4 for parallel within a runner.
pytest: pytest-xdist with -n auto for CPU-based parallelism.
Go: go test -parallel N per package, -p N for package-level parallelism.
Rust: cargo nextest run for per-test process isolation and parallelism.
Flaky test management
Flaky tests erode trust. Fix or quarantine immediately.
Identify: track test stability over time (most CI systems have flaky test dashboards)
Quarantine: move to a separate job that doesn't block merges. Tag with @flaky or skip.
Fix root causes - common culprits by framework:
Playwright/Cypress: race conditions on navigation or animation. Use waitForLoadState,
waitForSelector, or Playwright's auto-waiting. Avoid page.waitForTimeout. Stub network
requests to eliminate backend variability. Create a fresh browser context per test so cookies,
storage, and service workers cannot leak between cases. Headless mode (CI) has different rendering
timing than headed - animations may be skipped or font metrics differ; use
--headed locally to reproduce CI-only failures. Check CPU, memory, and worker contention on
the CI runner before changing timeouts.
Vitest/Jest: shared module state between test files. Use --pool forks (Vitest) or
--runInBand to isolate. Check for leaked timers (vi.useFakeTimers not restored).
pytest: database state leaking between tests. Use @pytest.mark.usefixtures("db")
with transactional rollback. Check for global state mutation in fixtures.
Go: t.Parallel() tests sharing package-level state. Use t.Cleanup for teardown.
Check for goroutine leaks with goleak.
Retry with caution: --retries 2 (Playwright) or --reruns 2 (pytest-rerunfailures) is a bandaid, not a fix
Coverage thresholds
Set coverage gates in CI. Reasonable defaults:
Metric
Threshold
Why
Line coverage
80%
Catches obvious gaps
Branch coverage
70%
Catches untested conditions
New code coverage
90%
Prevents coverage erosion
Enforce via vitest --coverage --coverage.thresholds.lines=80, pytest --cov --cov-fail-under=80, or go test -coverprofile + threshold script.
references/language-patterns.md - language-specific test patterns for JS/TS (Vitest, Jest), Python (pytest), Go (testing stdlib), and Rust (cargo test). Covers mocking, table-driven tests, async testing, snapshot testing, and framework-specific idioms.
references/e2e-accessibility.md - E2E testing with Playwright, visual regression (screenshot comparison, component snapshots), accessibility testing patterns, and CI integration for browser tests.
Output Contract
See references/output-contract.md for the full contract.
Skill name: TESTING
Deliverable bucket:audits
Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, emit the full contract - monospace inline header, severity-grouped inline summary, linked Markdown deliverable, and concise monospace conclusion - and write the deliverable to docs/local/audits/testing/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract.
Severity scale:P0 | P1 | P2 | P3 | info (see shared contract; only used in audit/review mode).
Related Skills
code-review - reviews test quality and correctness as part of code reviews. This skill writes the tests; code-review evaluates whether they actually test the right things.
anti-slop - cleans up verbose, over-abstracted, or AI-generated test code. If the test works but reads like a novel, route to anti-slop.
ci-cd - designs the pipeline that runs tests. This skill writes the tests and configures test runners; ci-cd handles the pipeline structure around them.
databases - covers database engine testing and configuration. This skill handles application-level database test patterns (transactions, fixtures, test data).
ai-ml - AI/ML model evaluation, LLM output scoring, and benchmark harnesses. This skill handles functional application testing; ai-ml handles model-level evaluation.
kubernetes - cluster-level chaos, resilience, and infrastructure-layer load testing. This skill handles application test code; kubernetes handles cluster-level fault injection.
Rules
Test behavior, not implementation. Tests coupled to internal structure break on every refactor and catch zero bugs. If a test mocks 8 things and asserts a method was called with specific args, it's testing the mock, not the code.
No sleep() in tests. Use waitFor, Eventually, poll, retry loops, or event-based synchronization. Fixed delays are flaky by definition.
Isolate test state. Each test creates its own data, runs independently, and cleans up after itself. Shared mutable state between tests is the #1 cause of order-dependent failures.
Fix or quarantine flaky tests immediately. A test suite people ignore is worse than no test suite. Track flaky tests, fix root causes, don't just retry.
Don't test the framework. Testing that React renders a div, or that Express routes to a handler, is testing someone else's code. Test YOUR logic.
Run the AI self-check. Every generated test gets verified against the checklist before returning. AI-generated tests love to test implementation details, use sleep(), and share state.
Match the existing framework. Don't introduce Vitest into a Jest project or pytest into a unittest project without the user explicitly asking for a migration.
Snapshot tests require manual review. Never auto-update snapshots (-u / --update) without reviewing the diff. Blind snapshot updates are equivalent to deleting the test.
1---2name: testing3description: · Write/debug tests: unit, integration, E2E, TDD, mocks, fixtures, a11y, perf. Triggers: 'test', 'test spec', 'TDD', 'playwright', 'vitest', 'jest', 'pytest', 'coverage', 'flaky'. Not for security tests (use security-audit).4license: MIT5---67# Testing: Write Tests That Catch Real Bugs89Write, structure, and maintain tests across unit, integration, E2E, accessibility, and performance layers. The goal is tests that catch regressions, document behavior, and run fast in CI - not tests that exist to inflate coverage numbers.1011**Target versions** (September 2026):12- Vitest **5.0.0**, Jest **30.5.1**13- Playwright **1.62.1**, Cypress **16.0.0** (both Vitest and Cypress are major upgrades; review migration notes)14- pytest **9.1.1**, pytest-cov **7.1.0**15- Go **1.27.1** (testing stdlib, `testing/synctest` GA)16- Rust **1.98.1** (`cargo test`, cargo-nextest **0.9.143**)17- Testing Library **16.3.3** (`@testing-library/react`)18- axe-core **4.13.0** (`@axe-core/playwright`)19- Grafana k6 **2.2.0**2021## When to use2223- Writing new tests (unit, integration, E2E, accessibility, performance)24- Debugging flaky or failing tests25- Designing test architecture for a project (fixture strategies, factory patterns, test data)26- Setting up test infrastructure in CI (parallelization, sharding, coverage gates)27- Choosing testing tools or migrating between test frameworks28- Implementing TDD workflow29- Adding accessibility or visual regression tests to an existing suite3031## When NOT to use3233- Reviewing existing test quality or correctness as part of a code review - use **code-review**34- Security-specific testing (penetration testing, OWASP checks) - use **security-audit**35- Cleaning up verbose/sloppy test code - use **anti-slop**36- Ad-hoc web browsing, scraping, or page interaction outside of tests - use **browse**37- CI/CD pipeline architecture (test jobs run inside pipelines, but pipeline design is ci-cd's domain) - use **ci-cd**38- Database testing patterns at the engine level - use **databases**39- Writing or refining LLM prompts (use **prompt-generator**)40- Infrastructure or configuration validation outside tests (use **terraform**, **ansible**, or **kubernetes**)41- AI/ML model evaluation or LLM output scoring - use **ai-ml**42- Infrastructure-level load or chaos testing beyond application tests (use **kubernetes** for cluster-level chaos, or **ci-cd** for pipeline-integrated load test orchestration)4344---4546## AI Self-Check4748AI tools consistently produce the same testing mistakes. **Before returning any generated test code, verify against this list:**4950- [ ] Tests assert behavior, not implementation - no testing private methods or internal state51- [ ] Each test has exactly one reason to fail (single assertion concept, not single `assert` call)52- [ ] Test names describe the scenario and expected outcome, not the method name53- [ ] Mocks/stubs are scoped to the test - no shared mutable mock state across tests54- [ ] No hardcoded ports, paths, or timestamps that break on other machines or in CI55- [ ] Async tests properly await all promises/futures - no fire-and-forget assertions56- [ ] Test data is isolated - each test creates its own state, no dependency on test execution order57- [ ] Cleanup happens even when assertions fail (use `afterEach`/`teardown`/`t.Cleanup`/`Drop`)58- [ ] No `sleep()` or fixed delays for async waits - use polling, retries, or event-based waits59- [ ] Coverage threshold is realistic (80% line coverage is a good default; 100% is a lie)60- [ ] Snapshot tests have been reviewed manually before committing (blind `--update` is a bug factory)61- [ ] E2E selectors use `data-testid`, `role`, or accessible names - not CSS classes or DOM structure62- [ ] **Runner APIs current**: pytest, Vitest, Jest, Playwright, and Testing Library examples match current runner behavior63- [ ] **Flake source identified**: retries are not used to hide nondeterminism without diagnosis64- [ ] Cross-cutting agent hygiene applied - see `references/agent-hygiene.md`6566---6768## Performance6970- Split fast unit tests from integration, browser, and performance suites.71- Use fixtures and test data builders to avoid repeated expensive setup.72- Shard or parallelize only after isolating shared state, ports, databases, and clocks.737475---7677## Best Practices7879- Test behavior through stable public interfaces, not implementation details.80- Use stable roles/test IDs for UI tests; do not select generated CSS classes.81- Every regression fix gets a failing test that would have caught the bug.828384## Workflow8586### Step 1: Determine scope8788Based on context:89- **New feature** -> write tests alongside or before the code (TDD when appropriate)90- **Bug fix** -> write a failing test first that reproduces the bug, then fix91- **Existing untested code** -> prioritize critical paths, not 100% coverage92- **Test infrastructure** -> set up runners, CI config, coverage gates9394Identify the project's existing test framework from config files (`vitest.config.ts`, `jest.config.*`, `pyproject.toml`, `Cargo.toml`, `*_test.go`, `playwright.config.ts`). Match it. Don't introduce a second test runner without a reason.9596### Step 2: Choose the test layer9798| Layer | Tests what | Speed | When to use |99|-------|-----------|-------|-------------|100| **Unit** | Single function/module in isolation | ms | Pure logic, utilities, data transforms, state machines |101| **Integration** | Multiple modules, real dependencies | seconds | API handlers, database queries, service boundaries |102| **E2E** | Full user flows through the UI | seconds-minutes | Critical paths, checkout flows, auth, onboarding |103| **Accessibility** | WCAG compliance, screen reader compat | seconds | Every user-facing component/page |104| **Visual** | Screenshot comparison | seconds | UI components after style changes |105| **Performance** | Load, latency, throughput | minutes | Before releases, after arch changes |106107**The testing pyramid still holds**: many unit tests, fewer integration tests, fewest E2E tests. Invert it and your CI takes 45 minutes and everyone ignores test failures.108109### Step 3: Write the test110111Follow the language-specific patterns below. Universal principles:112113**Arrange-Act-Assert** (or Given-When-Then):114```115// Arrange: set up test data and dependencies116// Act: call the thing being tested117// Assert: verify the outcome118```119120**Test naming**: describe the scenario, not the function.121```122# Bad: test_calculate_total123# Good: test_calculate_total_applies_discount_when_cart_exceeds_100124# Good: it("returns 401 when token is expired")125```126127### Step 4: Validate128129- Run checks appropriate to the changed behavior and every required repository gate. Run the130 full suite when required or when the affected surface warrants it; do not add tests that131 mirror reversible, low-impact edits. Repeat or broaden passing checks only for new changes,132 failures, or unresolved concerns.133- Check coverage delta: new code should be covered, but don't chase vanity numbers134- Run in CI if possible - tests that pass locally but fail in CI are the worst kind135136---137138## TDD Workflow139140Use TDD when the behavior is well-defined upfront. Skip it when exploring or prototyping.1411421. **Red**: write a test that fails (confirm it fails for the right reason)1432. **Green**: write the minimum code to make the test pass (ugly is fine)1443. **Refactor**: clean up without changing behavior (tests still pass)145146TDD works best for: pure functions, data transformations, state machines, API contracts, bug reproduction.147148TDD works poorly for: UI layout, exploratory prototyping, integration with undocumented APIs.149150---151152## Mocking Strategy153154Mock at boundaries, not everywhere. Over-mocking produces tests that pass while the real code is broken.155156| What to mock | What NOT to mock |157|-------------|-----------------|158| External APIs (HTTP, gRPC) | Your own pure functions |159| Database (when unit testing) | Data transformations |160| Time/dates, random values | Simple utility code |161| File system (when impractical) | The module under test |162| Third-party SDKs | Standard library functions |163164**Prefer fakes over mocks when possible.** An in-memory database implementation tests more real behavior than a mock that returns canned responses.165166**Injectable clock for TTL/time-dependent tests** - pass a clock dependency rather than calling `Date.now()` or `time.Now()` directly:167168```typescript169// Production: clock = () => Date.now()170// Test: clock = () => FIXED_TS + offset171function isExpired(createdAt: number, ttlMs: number, clock = Date.now): boolean {172 return clock() - createdAt > ttlMs;173}174// In test: advance virtual time without sleeping175const fakeNow = vi.fn().mockReturnValue(START);176expect(isExpired(START, 1000, fakeNow)).toBe(false);177fakeNow.mockReturnValue(START + 1001);178expect(isExpired(START, 1000, fakeNow)).toBe(true);179```180181For cached fetches, cover the two observable paths separately:182- Cache miss: the HTTP boundary is called once and the returned value is cached.183- Cache hit: the cached value is returned and the HTTP boundary is not called.184- TTL expiry: advance an injected or fake clock, then assert one refresh instead of sleeping.185186Read `references/language-patterns.md` for language-specific mocking idioms (Vitest `vi.mock`, Jest `jest.mock`, pytest `monkeypatch`, Go interfaces, Rust trait objects).187188---189190## Test Data and Fixtures191192### Factory pattern (preferred)193194Build test data with sensible defaults and per-test overrides:195196```typescript197// TypeScript - factory function198function buildUser(overrides: Partial<User> = {}): User {199 return { id: randomUUID(), name: "Test User", email: "test@example.com", ...overrides };200}201202// Python - factory function203def build_user(**overrides) -> User:204 defaults = {"id": uuid4(), "name": "Test User", "email": "test@example.com"}205 return User(**(defaults | overrides))206```207208### Fixture rules209210- **Isolate per test.** Shared mutable fixtures cause order-dependent failures.211- **Use builders/factories** over raw object literals - defaults prevent test brittleness.212- **Database fixtures**: use transactions that roll back after each test (pytest `db` fixture, Jest `beforeEach` with rollback). Seeded test databases beat shared staging data.213- **File fixtures**: use temp directories (`tmp_path` in pytest, `os.MkdirTemp` in Go, `tempfile` in Rust). Clean up in teardown.214215---216217## Accessibility Testing218219Catch WCAG violations automatically. Not a replacement for manual testing, but catches the mechanical stuff (missing alt text, broken ARIA, contrast ratios, keyboard traps).220221Use `@axe-core/playwright` - run `new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa"]).analyze()` and assert zero violations. Run axe scans on every page/component. Exclude known issues with `.exclude()` and track them as tech debt, not permanent exceptions.222223Read `references/e2e-accessibility.md` for Playwright E2E patterns, visual regression setup, and CI accessibility gates.224225---226227## Performance Testing228229Two categories: **micro-benchmarks** (is this function fast enough?) and **load tests** (does the system handle traffic?).230231### Micro-benchmarks232233- **Go**: `func BenchmarkX(b *testing.B)` - built into the stdlib234- **Rust**: `cargo bench` with criterion (`criterion = "0.6"`)235- **JS/TS**: `vitest bench` or `tinybench`236- **Python**: `pytest-benchmark` or `timeit`237238### Load testing (k6)239240```javascript241// k6 load test242import http from "k6/http";243import { check, sleep } from "k6";244245export const options = {246 stages: [247 { duration: "30s", target: 50 }, // ramp up248 { duration: "1m", target: 50 }, // sustain249 { duration: "10s", target: 0 }, // ramp down250 ],251 thresholds: {252 http_req_duration: ["p(95)<500"], // 95th percentile under 500ms253 },254};255256export default function () {257 const res = http.get("http://localhost:3000/api/health");258 check(res, { "status 200": (r) => r.status === 200 });259 sleep(1);260}261```262263Don't run load tests against production without explicit approval. Don't run them in CI unless you have dedicated infrastructure for it.264265---266267## CI Integration268269### Test parallelization270271- **Vitest/Jest**: built-in worker parallelism. Vitest uses Vite's module graph for smart test file distribution.272- **Playwright**: `--shard=1/4` for splitting across CI runners. `--workers=4` for parallel within a runner.273- **pytest**: `pytest-xdist` with `-n auto` for CPU-based parallelism.274- **Go**: `go test -parallel N` per package, `-p N` for package-level parallelism.275- **Rust**: `cargo nextest run` for per-test process isolation and parallelism.276277### Flaky test management278279Flaky tests erode trust. Fix or quarantine immediately.2802811. **Identify**: track test stability over time (most CI systems have flaky test dashboards)2822. **Quarantine**: move to a separate job that doesn't block merges. Tag with `@flaky` or `skip`.2833. **Fix root causes** - common culprits by framework:284 - **Playwright/Cypress**: race conditions on navigation or animation. Use `waitForLoadState`,285 `waitForSelector`, or Playwright's auto-waiting. Avoid `page.waitForTimeout`. Stub network286 requests to eliminate backend variability. Create a fresh browser context per test so cookies,287 storage, and service workers cannot leak between cases. Headless mode (CI) has different rendering288 timing than headed - animations may be skipped or font metrics differ; use289 `--headed` locally to reproduce CI-only failures. Check CPU, memory, and worker contention on290 the CI runner before changing timeouts.291 - **Vitest/Jest**: shared module state between test files. Use `--pool forks` (Vitest) or292 `--runInBand` to isolate. Check for leaked timers (`vi.useFakeTimers` not restored).293 - **pytest**: database state leaking between tests. Use `@pytest.mark.usefixtures("db")`294 with transactional rollback. Check for global state mutation in fixtures.295 - **Go**: `t.Parallel()` tests sharing package-level state. Use `t.Cleanup` for teardown.296 Check for goroutine leaks with `goleak`.2974. **Retry with caution**: `--retries 2` (Playwright) or `--reruns 2` (pytest-rerunfailures) is a bandaid, not a fix298299### Coverage thresholds300301Set coverage gates in CI. Reasonable defaults:302303| Metric | Threshold | Why |304|--------|-----------|-----|305| Line coverage | 80% | Catches obvious gaps |306| Branch coverage | 70% | Catches untested conditions |307| New code coverage | 90% | Prevents coverage erosion |308309Enforce via `vitest --coverage --coverage.thresholds.lines=80`, `pytest --cov --cov-fail-under=80`, or `go test -coverprofile` + threshold script.310311**Minimal CI example (pytest + GitHub Actions)**:312```yaml313- run: pip install pytest pytest-xdist pytest-cov314- run: pytest -n auto --cov=src --cov-fail-under=80 --tb=short315```316317---318319## Reference Files320321- `references/language-patterns.md` - language-specific test patterns for JS/TS (Vitest, Jest), Python (pytest), Go (testing stdlib), and Rust (cargo test). Covers mocking, table-driven tests, async testing, snapshot testing, and framework-specific idioms.322- `references/e2e-accessibility.md` - E2E testing with Playwright, visual regression (screenshot comparison, component snapshots), accessibility testing patterns, and CI integration for browser tests.323324---325326## Output Contract327328See `references/output-contract.md` for the full contract.329330- **Skill name:** TESTING331- **Deliverable bucket:** `audits`332- **Mode:** conditional. When invoked to **analyze, review, audit, or improve** existing repo content, emit the full contract - monospace inline header, severity-grouped inline summary, linked Markdown deliverable, and concise monospace conclusion - and write the deliverable to `docs/local/audits/testing/<YYYY-MM-DD>-<slug>.md`. When invoked to **answer a question, teach a concept, build a new artifact, or generate content**, respond freely without the contract.333- **Severity scale:** `P0 | P1 | P2 | P3 | info` (see shared contract; only used in audit/review mode).334335## Related Skills336337- **code-review** - reviews test quality and correctness as part of code reviews. This skill writes the tests; code-review evaluates whether they actually test the right things.338- **security-audit** - handles security-specific testing (OWASP, penetration testing, credential scanning). This skill handles functional testing.339- **anti-slop** - cleans up verbose, over-abstracted, or AI-generated test code. If the test works but reads like a novel, route to anti-slop.340- **ci-cd** - designs the pipeline that runs tests. This skill writes the tests and configures test runners; ci-cd handles the pipeline structure around them.341- **databases** - covers database engine testing and configuration. This skill handles application-level database test patterns (transactions, fixtures, test data).342- **ai-ml** - AI/ML model evaluation, LLM output scoring, and benchmark harnesses. This skill handles functional application testing; ai-ml handles model-level evaluation.343- **kubernetes** - cluster-level chaos, resilience, and infrastructure-layer load testing. This skill handles application test code; kubernetes handles cluster-level fault injection.344345---346347## Rules3483491. **Test behavior, not implementation.** Tests coupled to internal structure break on every refactor and catch zero bugs. If a test mocks 8 things and asserts a method was called with specific args, it's testing the mock, not the code.3502. **No `sleep()` in tests.** Use `waitFor`, `Eventually`, `poll`, retry loops, or event-based synchronization. Fixed delays are flaky by definition.3513. **Isolate test state.** Each test creates its own data, runs independently, and cleans up after itself. Shared mutable state between tests is the #1 cause of order-dependent failures.3524. **Fix or quarantine flaky tests immediately.** A test suite people ignore is worse than no test suite. Track flaky tests, fix root causes, don't just retry.3535. **Don't test the framework.** Testing that React renders a div, or that Express routes to a handler, is testing someone else's code. Test YOUR logic.3546. **Run the AI self-check.** Every generated test gets verified against the checklist before returning. AI-generated tests love to test implementation details, use `sleep()`, and share state.3557. **Match the existing framework.** Don't introduce Vitest into a Jest project or pytest into a unittest project without the user explicitly asking for a migration.3568. **Snapshot tests require manual review.** Never auto-update snapshots (`-u` / `--update`) without reviewing the diff. Blind snapshot updates are equivalent to deleting the test.
Run npx skillmds@latest add iuliandita/testing in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
· Write/debug tests: unit, integration, E2E, TDD, mocks, fixtures, a11y, perf. Triggers: 'test', 'test spec', 'TDD', 'playwright', 'vitest', 'jest', 'pytest', 'coverage', 'flaky'. Not for security tests (use security-audit). It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: makes network calls, reads secrets. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
iuliandita (@iuliandita) published this skill. Their other Agent Skills are listed on their SkillMD profile.