name: test
argument-hint: [file | --e2e | --run]
description: Generate/run unit, integration & Playwright e2e tests. Triggers: "test this file" | "write tests" | "add coverage" | "run tests" | "e2e tests" | "add tests" | "test coverage" | "generate tests" | "test this" | "write unit tests" | "add integration tests".
version: 0.4.1
allowed-tools: Bash, Read, Write, Glob, Grep, ToolSearch
Test
Success
I := π written ∧ test passes
V := {commands.test} {test_file} → exit 0
Let:
τ := target file(s) under test
π := test file adjacent to source ({name}.test.ts | {name}.spec.ts | __tests__/{name}.test.ts)
Σ := {standards.testing}
Generate tests for changed/specified files. Follow existing codebase patterns.
Usage
/test → Generate tests for files changed vs base branch
/test src/auth/login.ts → Generate tests for a specific file
/test --e2e → Generate Playwright e2e tests for changed files
/test --run → Run existing tests ({commands.test})
Pipeline
| Step | ID | Required | Verifies via | Notes |
|---|
| 1 | run-shortcut | — | {commands.test} exit 0 | --run flag only |
| 2 | identify-targets | ✓ | Δ files listed | — |
| 3 | read-standards | ✓ | Σ read | — |
| 4 | check-coverage | ✓ | π ∃? | — |
| 5 | generate-tests | ✓ | tests generated | — |
| 6 | approval | ✓ | user confirms | — |
| 7 | write-and-verify | ✓ | test exit 0 | retry 1 |
Pre-flight
Success: π written ∧ test passes
Evidence: {commands.test} {test_file} exit 0
Steps: identify-targets → read-standards → check-coverage → generate-tests → approval → write-and-verify
¬clear → STOP + ask: "Which file(s) need tests?"
Step 1 — --run Shortcut
--run ⇒ {commands.test} → report results → stop.
Step 2 — Identify Target Files
BASE=$(. "${CLAUDE_SKILL_DIR}/../shared/lib.sh" && detect_base_branch)
git diff origin/${BASE}...HEAD --name-only
Include: .ts, .tsx. Exclude: *.config.ts, *.d.ts, *.test.*, *.spec.*, files with no exports.
Specific file arg ⇒ use directly. ¬testable τ ⇒ inform + stop.
Step 3 — Read Standards + Find Patterns
Read Σ before generating — contains framework config, AAA requirements, mocking strategies, coverage targets.
Glob *.test.ts / *.spec.ts near τ → read 1–2 examples → extract: describe/it nesting, mock approach, assertion style, naming.
Framework: Vitest on Bun. Always import explicitly:
import { describe, it, expect, vi } from 'vitest'
Bun compat constraints:
| Avoid | Use instead |
|---|
vi.mocked(fn) | fn as ReturnType<typeof vi.fn> |
vi.stubGlobal('fetch', mock) | globalThis.fetch = mock as typeof fetch |
vi.stubGlobal('Bun', {...}) | vi.spyOn(Bun, 'spawn').mockImplementation(...) |
vi.restoreAllMocks() in beforeEach | vi.clearAllMocks() |
Mock factory hoisting: Bun validates vi.mock factories against real module at hoist time. Side-effectful imports run before process.env assignments. Fix: vi.mock('../../shared/config', factory) to intercept directly.
Step 4 — Check Existing Coverage
∀ τ → check for π. ∃ π ⇒ read, compare with source exports, offer to add missing coverage (¬overwrite). ¬π ⇒ generate full test file.
Step 5 — Generate Tests
∀ τ:
- Read source → understand exports, signatures, types, behavior
- Generate: happy path, edge cases (empty/null/boundary), error cases
- Structure using AAA with explicit comments:
it('should return user by id', () => {
const userId = 'abc-123'
const result = getUser(userId)
expect(result).toBeDefined()
})
- Coverage targets: 90% business logic | 80% controllers/modules | 70% overall
- Follow discovered patterns exactly
- Place adjacent to source:
login.ts → login.test.ts
Step 6 — Approval
→ present choice Approve and write all | Approve with modifications | Skip specific files
¬write without approval.
Step 7 — Write + Verify
∀ approved τ: write via Write tool → {commands.test} {test_file_path} → report pass/fail.
∃ failures ⇒ → present choice show failing test + error → propose fix → re-run.
Step 8 — Falsification Gate (standalone /test)
Applies to: unit + fast-integration tests only. Triggered after Step 7 green run. Ownership: when invoked by /implement, the implement orchestrator drives the stash (¬tester). When invoked standalone (no implement orchestrator), /test owns the stash cycle itself — the tester agent still only writes tests; the stash is driven by the /test flow.
e2e exemption: tests generated via --e2e → annotate each as ⚠ NO FALSIFY — e2e. Stop. ¬run stash cycle.
Precondition: all newly created source files must be git add-ed before the gate runs — the Write tool does NOT auto-stage new files, and unstaged new files are invisible to git diff HEAD.
∀ new/modified test written in this session:
- Stash source (¬test files):
SRC=$( { git diff HEAD --name-only; git ls-files --others --exclude-standard; } \
| grep -v '\.test\.' | grep -v '\.spec\.' )
git stash -- $SRC
This enumerates both tracked-dirty AND untracked source files, then excludes test/spec files.
- Run the test:
{commands.test} {test_file_path}.
- Assert FAIL: exit 0 → tautological → tautological = merge blocker. Do NOT pop stash. Restore worktree:
git stash pop. user choice Rewrite test | Flag and block (¬silently pass). ¬assign a matrix Status — no Status update until the test is rewritten and re-run.
- Pop stash (success path only):
git stash pop.
- Assert GREEN: re-run → exit 0.
- Record evidence (one line per test):
broke {source file} → test failed with {error/assertion message}
Evidence lines feed the #279 matrix Status column: ✓ proven. Append evidence block to output before reporting done.
E2E Mode (--e2e)
Check Playwright:
bunx playwright --version 2>/dev/null
¬installed ⇒ inform install command for {package_manager}:
- bun:
bun add -d @playwright/test && bunx playwright install
- pnpm:
pnpm add -D @playwright/test && pnpm exec playwright install
- npm:
npm install --save-dev @playwright/test && npx playwright install
- yarn:
yarn add --dev @playwright/test && yarn playwright install
Stop (¬install deps).
E2E dir: {frontend.path}/e2e/ (fall back to e2e/ if {frontend.path} not set).
Check existing patterns first. Name: {feature}.spec.ts.
Follow approval + verification flow (Steps 6–7).
Playwright Patterns
∃ page objects in {frontend.path}/e2e/ → follow them.
import { type Page, type Locator } from '@playwright/test'
export class LoginPage {
readonly emailInput: Locator
readonly passwordInput: Locator
readonly submitButton: Locator
constructor(readonly page: Page) {
this.emailInput = page.getByLabel('Email')
this.passwordInput = page.getByLabel('Password')
this.submitButton = page.getByRole('button', { name: 'Sign in' })
}
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.submitButton.click()
}
}
import { test, expect } from '@playwright/test'
import { LoginPage } from '../pages/login.page'
test.describe('Login flow', () => {
test('should login with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.login('user@example.com', 'password123')
await expect(page).toHaveURL('/dashboard')
})
})
Selectors: page.getByRole(), page.getByLabel(), page.getByText() (¬page.locator('css') unless no semantic alternative).
Edge Cases
| Scenario | Behavior |
|---|
| File has no exports | Skip, inform user |
| Tests already exist | Offer to add missing coverage, ¬overwrite |
| Test framework not detected | → ask user which framework to use |
--run flag | Run {commands.test} and report only |
| React component | Generate component tests with appropriate render approach |
| File in monorepo package | Place tests relative to package, ¬root |
Safety Rules
- ¬overwrite existing test files without explicit approval
- ¬install dependencies — inform + stop
- Always present generated tests for approval before writing
- Always run tests after writing
- Always match existing patterns — ¬impose different style
$ARGUMENTS