description: 'Generate UAT checklist and runnable E2E test stubs from a spec' when_to_use: "After /generate-tests, when UAT checklist and E2E stubs are needed from a spec." name: generate-uat version: 1.0.0
Dispatch
Phase A — Generate + preview (spawn → collect → present):
Spawn Agent tool: model: "sonnet", subagent_type: "general-purpose".
Prompt: Steps 1–7 from the skill below + spec path from $ARGUMENTS. Working dir: <cp-root>.
Collect the subagent's output (test count, categories, file paths, preview content). Present to user.
Ask: "Does this look correct? Should I save all 3 test files? (y/n)"
Phase B — Save files (only after user approves):
Spawn a second Agent tool: model: "sonnet", subagent_type: "general-purpose".
Prompt: Step 8 (save) from the skill below + the exact file content from Phase A output. Write all 3 files.
Report subagent output verbatim.
/generate-tests
Generate User Acceptance Test checklist and runnable E2E test files from a feature specification.
Announce at start: "I'm using the /generate-tests skill to extract testable criteria from the spec and create test files."
Usage
/generate-tests <path-to-spec> [--format full|minimal] [--output <path>]
Examples:
/generate-tests features/p61_events_complete_tech_spec.md/generate-tests features/p142_export_csv.md --format minimal
What This Skill Does
- Read the spec — Parse the entire specification file
- Extract success criteria — Find testable statements from these sections (in priority order):
- "Acceptance Criteria"
- "Success Criteria"
- "User Stories"
- "Requirements"
- Bullet points under "Goals" or "Objectives"
- Categorize tests — Group by feature area and test type
- Generate 3 types of test files:
- UAT checklist (
features/uat/p{N}.md) — Manual validation checklist - E2E test stubs (
e2e/p{N}-{slug}.spec.ts) — Runnable Playwright test file with TODO stubs - Smoke tests (
e2e/p{N}-smoke.spec.ts) — Fast regression tests (page loads, no errors)
- UAT checklist (
What It Generates
1. UAT Checklist (Manual Validation)
File: features/uat/p{N}.md
Purpose: Manual checklist for user to validate:
- Business outcomes achieved
- User experience feels right
- Edge cases handled
- Acceptance criteria met
Format: Given/When/Then scenarios with verification methods
Use case: User validates quality (not just functionality)
2. E2E Test File Stubs (Automated Testing)
File: e2e/p{N}-{feature-slug}.spec.ts
Purpose: Runnable Playwright test file with TODO stubs for implementation
Contains:
- One test per acceptance criterion
- Setup/teardown scaffolding
- Page navigation stubs
- Assertion stubs (to be filled by /dev agent)
- Test helpers for auth, data creation, cleanup
Example stub:
import { test, expect } from '@playwright/test';
import { createTestUser, deleteTestUser, setTestSession, TestUser } from './helpers/test-user';
test.describe('P142: Export CSV', () => {
let testUser: TestUser;
test.beforeEach(async () => {
testUser = await createTestUser({ name: 'CSV Test User' });
});
test.afterEach(async () => {
if (testUser?.user?.id) {
await deleteTestUser(testUser.user.id);
}
});
test('exports sifter responses as CSV', async ({ page }) => {
// TODO: Setup - create sifter with responses
// TODO: Navigate to results page
// TODO: Click export button
// TODO: Verify CSV file downloaded
// TODO: Verify CSV contains expected data
});
test('disables export when no responses', async ({ page }) => {
// TODO: Setup - create sifter with 0 responses
// TODO: Navigate to results page
// TODO: Verify export button disabled
// TODO: Verify tooltip shows "No responses to export"
});
});
3. Smoke Test File (Fast Regression Detection)
File: e2e/p{N}-smoke.spec.ts
Purpose: Fast smoke tests to catch basic breakage
Contains:
- Page loads test (no 404/500)
- No console errors test
- Critical elements present test
Example:
import { test, expect } from '@playwright/test';
import { createTestUser, deleteTestUser, setTestSession, TestUser } from './helpers/test-user';
test.describe('P142 Smoke Tests', () => {
let testUser: TestUser;
test.beforeEach(async () => {
testUser = await createTestUser({ name: 'Smoke Test User' });
});
test.afterEach(async () => {
if (testUser?.user?.id) {
await deleteTestUser(testUser.user.id);
}
});
test('results page loads without errors', async ({ page }) => {
// TODO: Setup - create sifter
// TODO: Navigate to results page
// TODO: Verify page loads (no 404/500)
// TODO: Verify no console errors
// TODO: Verify main heading present
});
});
Process
Step 1: Read the Spec
Read the entire spec file provided as argument.
If no file path provided, ask:
Which spec file should I generate a UAT for? (e.g.,
features/p70_new_feature.md)
Step 2: Find Success Criteria
Search for sections containing testable criteria. Look for these headers (case-insensitive):
Explicit sections (highest priority):
## Success Criteria## Acceptance Criteria## Requirements### Tests
User story format:
As a [user], I want [action] so that [outcome]
→ Convert to: "User can [action]"
Requirements tables:
| Requirement | Priority |
|-------------|----------|
| Events have title | Must |
→ Convert to: "Events have title"
Implicit in description (lowest priority):
The events page shows upcoming events with title, date, and location.
→ Extract: "Events show title", "Events show date", "Events show location"
Step 3: Categorize Tests
Assign each criterion to a category based on keywords:
| Keywords in criterion | Category | |----------------------|----------| | table, column, migration, SQL, RLS, database | Database | | page, render, display, show, UI, component, screen | UI | | login, auth, session, permission, role, authenticated | Auth | | API, endpoint, request, response, fetch | API | | form, input, validation, submit, field | Forms | | navigation, route, link, redirect, URL | Navigation | | error, fail, invalid, edge case | Error Handling | | test, unit, e2e, playwright | Testing |
If no keywords match, use "General".
Group tests by category, then number sequentially: UAT-{category}.{sequence}
Step 4: Convert to Given/When/Then
For each criterion, create a structured test:
### UAT-{N}.{M}: {Short description}
**Given:** {precondition/context}
**When:** {action (if applicable)}
**Then:** {expected outcome}
**Verify:** {how to verify - Playwright MCP, DB query, visual inspection, etc.}
Rules:
- If criterion is a state (e.g., "Events table exists"), omit "When"
- For UI tests, verification is typically "Playwright MCP screenshot"
- For database tests, verification is "Run query in Supabase dashboard" or "npm run build"
- For API tests, verification is "Playwright MCP network tab" or "curl command"
Step 5: Generate UAT Checklist Output
Use this template for features/uat/p{N}.md:
# {Feature Name} — Acceptance Tests
**Purpose:** Testable acceptance criteria for {feature} implementation.
**Usage:** Ralph Loop iterates until ALL tests pass (score 100%).
**Source:** {spec_path}
**Generated:** {date}
**Generated by:** /generate-uat
---
## Test Scoring
\`\`\`
Score = passed_tests / {total} (shown as X/{total} or N%)
Total tests: {total}
Pass threshold: {total}/{total} (100% — all tests must pass)
\`\`\`
---
## Pre-Checks (must pass before UAT)
\`\`\`bash
npm run lint # No errors
npm run build # Compiles successfully
npm test # All unit tests pass
\`\`\`
---
{For each category}
## Category {N}: {Category Name} ({count} tests)
{For each test in category}
### UAT-{N}.{M}: {Short description}
**Given:** {precondition}
**When:** {action}
**Then:** {expected outcome}
**Verify:** {verification method}
{End for each test}
---
{End for each category}
## Test Execution Log
| Test | Status | Notes |
|------|--------|-------|
| UAT-1.1 | ⬜ | |
| UAT-1.2 | ⬜ | |
| ... | ... | ... |
**Legend:** ⬜ Not tested | ✅ Pass | ❌ Fail | ⏭️ Skipped (blocked — add note)
---
## Success Criteria
Ralph Loop completes when:
1. All {total} UAT tests show ✅
2. `./scripts/pre-commit-checks.sh` passes
3. No console errors during Playwright verification
Output `<promise>{Feature ID} UAT COMPLETE</promise>` when done.
---
## Notes for Agent
- **Use Playwright MCP** for visual verification
- **Use Chrome DevTools MCP** if network issues
- **Commit after each category passes** — Progress is preserved
- **Update scorecard** after each test — This file is your state
Step 6: Generate E2E Test Stubs
File: e2e/p{N}-{feature-slug}.spec.ts
Process:
-
Extract feature slug from spec filename (e.g.,
p142_export_csv.md→export-csv) -
Extract acceptance criteria from spec
-
Create test structure:
import { test, expect } from '@playwright/test'; import { createTestUser, deleteTestUser, setTestSession, TestUser } from './helpers/test-user'; test.describe('P{N}: {Feature Name}', () => { let testUser: TestUser; test.beforeEach(async () => { testUser = await createTestUser({ name: '{Feature} Test User' }); }); test.afterEach(async () => { if (testUser?.user?.id) { await deleteTestUser(testUser.user.id); } }); {For each acceptance criterion} test('{test name from acceptance criterion}', async ({ page }) => { // TODO: Setup - {describe setup needed} // TODO: Navigate to {page} // TODO: {action to perform} // TODO: Verify {expected outcome} }); }); -
Generate one test per acceptance criterion
-
Use descriptive TODO comments for /dev agent to implement
-
Include setup/teardown scaffolding (auth, data creation, cleanup)
Rules:
- Test names should be concise, lowercase, descriptive
- Use test helpers from
./helpers/test-userfor auth - Leave implementation as TODOs (filled by /dev)
- Include common imports (test, expect, helpers)
Step 7: Generate Smoke Tests
File: e2e/p{N}-smoke.spec.ts
Process:
-
Create basic smoke test structure:
import { test, expect } from '@playwright/test'; import { createTestUser, deleteTestUser, setTestSession, TestUser } from './helpers/test-user'; test.describe('P{N} Smoke Tests', () => { let testUser: TestUser; test.beforeEach(async () => { testUser = await createTestUser({ name: 'Smoke Test User' }); }); test.afterEach(async () => { if (testUser?.user?.id) { await deleteTestUser(testUser.user.id); } }); test('{main page} loads without errors', async ({ page }) => { // TODO: Setup - {minimal setup needed} // TODO: Navigate to {main page of feature} // TODO: Verify page loads (no 404/500) // TODO: Verify no console errors // TODO: Verify main heading/content present }); }); -
Identify main page/route from spec (UX section, technical section, or acceptance criteria)
-
Create single smoke test for page load
-
Use TODOs for implementation details
Purpose: Fast test to catch basic breakage (page doesn't load, console errors, critical elements missing)
Step 8: Confirm and Save
Before saving, show the user:
- Number of UAT tests extracted: {N}
- Number of E2E test stubs: {M}
- Categories: {list}
- Output paths:
- UAT:
features/uat/p{N}.md - E2E:
e2e/p{N}-{slug}.spec.ts - Smoke:
e2e/p{N}-smoke.spec.ts
- UAT:
Ask: "Does this look correct? Should I save all 3 test files?"
If approved, save all 3 files (or custom paths if --output specified).
Options
| Flag | Default | Description |
|------|---------|-------------|
| --output <path> | features/uat/p{N}.md | Custom output path |
| --format full | full | Include Given/When/Then for each test |
| --format minimal | - | Scorecard table only (no detailed tests) |
Edge Cases
| Scenario | Behavior | |----------|----------| | Spec has no acceptance/success criteria section | Warning: "No explicit acceptance criteria found. Extracting from description. Review generated tests carefully." | | Spec is too vague to extract tests | Error: "Couldn't extract testable criteria. Add an 'Acceptance Criteria' section to the spec." | | UAT file already exists | Ask: "UAT file exists at {path}. Overwrite / Merge / Cancel?" | | E2E test file already exists | Ask: "E2E test file exists at {path}. Overwrite / Merge / Cancel?" | | Spec has 50+ criteria | Warning: "Large spec ({N} criteria). Consider breaking into smaller features." | | Category has no tests | Skip the category in UAT, don't create empty sections | | Can't determine main page for smoke test | Use generic page (e.g., homepage or main feature page), add TODO for /dev to specify | | Feature is backend-only (no UI) | Skip smoke test generation, only create E2E test stubs for API/database tests |
Example Input → Output
Input spec excerpt:
# P142: Export CSV
## Acceptance Criteria
- [ ] User can export sifter responses as CSV file
- [ ] Export button is disabled when sifter has 0 responses
- [ ] CSV includes all response data (timestamp, answers, respondent)
- [ ] File downloads with name format: `{sifter-title}-responses-{date}.csv`
Output 1: UAT Checklist (features/uat/p142.md):
# Export CSV — Acceptance Tests
## Category 1: UI (2 tests)
### UAT-1.1: Export button works
**Given:** Sifter has 5 responses
**When:** User navigates to results page and clicks "Export CSV"
**Then:** CSV file downloads with correct data
**Verify:** Playwright MCP click + download verification
### UAT-1.2: Export disabled when empty
**Given:** Sifter has 0 responses
**When:** User navigates to results page
**Then:** Export button is disabled
**Verify:** Playwright MCP screenshot
## Category 2: Data (2 tests)
### UAT-2.1: CSV includes all data
**Given:** Sifter has responses with answers
**When:** CSV is exported
**Then:** File contains timestamp, answers, respondent columns
**Verify:** Open CSV file, check headers
### UAT-2.2: Filename format correct
**Given:** Sifter titled "Remote Trust"
**When:** CSV exported on 2026-02-13
**Then:** Filename is `remote-trust-responses-2026-02-13.csv`
**Verify:** Check downloaded file name
Output 2: E2E Test Stubs (e2e/p142-export-csv.spec.ts):
import { test, expect } from '@playwright/test';
import { createTestUser, deleteTestUser, setTestSession, TestUser } from './helpers/test-user';
test.describe('P142: Export CSV', () => {
let testUser: TestUser;
test.beforeEach(async () => {
testUser = await createTestUser({ name: 'CSV Test User' });
});
test.afterEach(async () => {
if (testUser?.user?.id) {
await deleteTestUser(testUser.user.id);
}
});
test('exports sifter responses as CSV', async ({ page }) => {
// TODO: Setup - create sifter with responses
// TODO: Navigate to results page
// TODO: Click export button
// TODO: Verify CSV file downloaded
// TODO: Verify CSV contains expected data
});
test('disables export when no responses', async ({ page }) => {
// TODO: Setup - create sifter with 0 responses
// TODO: Navigate to results page
// TODO: Verify export button disabled
// TODO: Verify tooltip shows "No responses to export"
});
test('CSV includes all response data', async ({ page }) => {
// TODO: Setup - create sifter with multiple responses
// TODO: Export CSV
// TODO: Verify CSV has timestamp, answers, respondent columns
});
test('filename format is correct', async ({ page }) => {
// TODO: Setup - create sifter titled "Remote Trust"
// TODO: Export CSV
// TODO: Verify filename matches pattern: {slug}-responses-{date}.csv
});
});
Output 3: Smoke Tests (e2e/p142-smoke.spec.ts):
import { test, expect } from '@playwright/test';
import { createTestUser, deleteTestUser, setTestSession, TestUser } from './helpers/test-user';
test.describe('P142 Smoke Tests', () => {
let testUser: TestUser;
test.beforeEach(async () => {
testUser = await createTestUser({ name: 'Smoke Test User' });
});
test.afterEach(async () => {
if (testUser?.user?.id) {
await deleteTestUser(testUser.user.id);
}
});
test('results page loads without errors', async ({ page }) => {
// TODO: Setup - create sifter
// TODO: Navigate to results page
// TODO: Verify page loads (no 404/500)
// TODO: Verify no console errors
// TODO: Verify main heading present
});
});
Related Skills
/create-spec→/ux→/architect→/generate-tests→/dev— New sequential flow (P143)/prep-spec— Legacy skill (deprecated, but still calls/generate-tests)/dev— Reads UAT + E2E stubs, implements tests, runs them, iterates until pass
Backward Compatibility
Skill name change: /generate-uat → /generate-tests
/generate-uatstill works (alias to/generate-tests)- Old behavior preserved: still generates
features/uat/p{N}.md - New behavior added: also generates E2E stubs + smoke tests
What's new in this version:
- Generates 3 files instead of 1
- E2E test stubs are runnable Playwright files (not manual checklists)
- Smoke tests for fast regression detection
- Integrates with
/devskill (reads stubs, implements, runs, iterates)
Migration path:
- Existing UAT files (features/uat/*.md) still valid
- New features after P143 get all 3 files
- Old features can be retro-fitted with E2E stubs if needed
Validation
This skill was validated against:
- P61 acceptance tests (UAT generation)
- P135, P131, P140 E2E tests (test stub format)
- P143 sequential flow requirements (test automation)
TDD Red-Green-Refactor
Testing
Skill that guides Claude through the complete TDD cycle.
Web Accessibility Audit
Testing
Performs a comprehensive web accessibility audit following WCAG standards.
UAT Test Case Generator
Testing
Generates structured and comprehensive user acceptance test cases.