# R-test

> Generate or run unit, integration, and Playwright e2e tests.

- Skill: `roxabi/r-test` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add roxabi/r-test`
- Raw SKILL.md: https://api.skillmd.com/api/skills/roxabi/r-test/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: Roxabi (https://skillmd.com/u/roxabi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/roxabi/r-test

---


# 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}`

**Stack:** Read `.dev/stack.yml` first — every `{field}` placeholder below resolves from it. ¬∃ → output: "`.dev/stack.yml` not found — run `/R-env-setup` to generate it." and stop.

Generate tests for changed/specified files. Follow existing codebase patterns.

## Usage

```
/R-test                      → Generate tests for files changed vs base branch
/R-test src/auth/login.ts    → Generate tests for a specific file
/R-test --e2e                → Generate Playwright e2e tests for changed files
/R-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 |
| 8 | falsify | ✓ | `broke {file} → {error}` | mechanical preferred; e2e skip |

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

```bash
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:
```typescript
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

∀ τ:
1. Read source → understand exports, signatures, types, behavior
2. Generate: happy path, edge cases (empty/null/boundary), error cases
3. Structure using AAA with explicit comments:

```typescript
it('should return user by id', () => {
  // Arrange
  const userId = 'abc-123'
  // Act
  const result = getUser(userId)
  // Assert
  expect(result).toBeDefined()
})
```

4. Coverage targets: 90% business logic | 80% controllers/modules | 70% overall
5. Follow discovered patterns exactly
6. 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 `/R-test`)

Applies to: unit + fast-integration tests only. Triggered after Step 7 green run. **Ownership:** when invoked by `/R-dev-implement`, the implement orchestrator drives the gate (¬R-tester). When invoked standalone (no implement orchestrator), `/R-test` owns the cycle itself — the R-tester agent still only writes tests; the runner is driven by the `/R-test` flow.

**e2e exemption:** tests generated via `--e2e` → set Status to `⚠ NO FALSIFY — e2e` (do not leave `⏳ not run`). 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`.

**Evidence is mandatory.** A test without a `broke {file} → {error}` line stays `⏳ not run`, never `✓ proven`.

Spec SCs with a priced-quantity block: test `priced` + `oracles`, never `not`.

**Runner — plugin-owned (default, ADR-019):**

```
bash ${CLAUDE_PLUGIN_ROOT}/skills/pr/run-falsify.sh --map <map.json> --out artifacts/reviews/{N}-falsify.json --issue {N}
```

Consumer `{commands.test:falsify}` / `package.json` `test:falsify` / `scripts/test-falsify.sh` allowed **only if** they exec the plugin helper without swallowing non-zero — else stub-refuse. LLM `git stash` is ¬an alternate oracle.

On `oracle_ok=true`, set `✓ proven` from JSON rows. Persist JSON (+ optional md render). Evidence lines come from the helper output. Matrix `Status` = `✓ proven` only from runner rows — append evidence block to output before reporting done.

## E2E Mode (`--e2e`)

Check Playwright:
```bash
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.

```typescript
// e2e/pages/login.page.ts
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()
  }
}
```

```typescript
// e2e/auth/login.spec.ts
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

1. ¬overwrite existing test files without explicit approval
2. ¬install dependencies — inform + stop
3. Always present generated tests for approval before writing
4. Always run tests after writing
5. Always match existing patterns — ¬impose different style

$ARGUMENTS

