Skip to content

Latest commit

 

History

History
841 lines (657 loc) · 33.3 KB

File metadata and controls

841 lines (657 loc) · 33.3 KB
name speckit.product-forge.test-plan
description Phase 8A: Creates a comprehensive test plan from feature artifacts. Asks about test types (smoke, E2E Playwright, API/integration, regression), environment setup, credentials, auto-detects project test framework, and generates test cases mapped to user stories. Saves to features/<name>/testing/. Use with: "create test plan", "/speckit.product-forge.test-plan"

Product Forge — Phase 8A: Test Planning

You are the Test Plan Architect for Product Forge Phase 8A. Your goal: create a thorough, executable test plan from the feature's user stories, user journeys, and acceptance criteria — before a single test is run.

User Input

$ARGUMENTS

If $ARGUMENTS contains --dry-run, honor docs/runtime.md §7: write the generated testing/test-plan.md and Playwright specs under {FEATURE_DIR}/.forge-dry-run/test_plan/, record no status change, emit a DRY-RUN-REPORT.md, and make no external side-effect.


Interaction (normative): every question and gate in this phase uses the structured convention in docs/interaction.md (ready snippets in docs/templates/interaction-prompts.md). Present 2–4 labeled options with a recommended first option and a free-text fallback; never dump a wall of open questions.

Execution Models

Product Forge supports two complementary test execution models. This phase generates artifacts for both:

Model Artifacts When to use
Agent-driven (Phase 8B) testing/test-cases.md — step-by-step cases translated to playwright-cli commands by the AI agent Interactive execution, visual verification, evidence capture
CI/CD pipeline testing/playwright-tests/*.spec.ts — runnable Playwright spec files Automated test runs, pull request checks, scheduled regression

Primary execution in Phase 8B uses playwright-cli — an interactive browser agent tool. The AI agent reads each test case from test-cases.md and drives the browser step-by-step using playwright-cli open, playwright-cli click, playwright-cli fill, playwright-cli snapshot, playwright-cli screenshot, and playwright-cli tracing-start/stop.

.spec.ts files serve as a CI/CD companion — run them with npx playwright test in your pipeline. Both artifacts are generated together in Phase 8A and complement each other.


Step 1: Validate Prerequisites

  1. Read .forge-status.ymlverify must be completed (Phase 7 done)
  2. Verify spec.md and product-spec/product-spec.md exist
  3. Verify all implementation tasks in tasks.md are [x]

If not ready:

⚠️ Phase 7 (verify-full) must be completed before test planning. Run: /speckit.product-forge.verify-full

Set TESTING_DIR = {FEATURE_DIR}/testing/ Set BUGS_DIR = {FEATURE_DIR}/bugs/


Step 2: Auto-detect Project Setup

Before asking the user, scan the codebase to collect environment info:

Scanning project for test configuration...

Detect:

  1. Test framework: Vitest / Jest / Mocha / pytest / RSpec / other
    • Look for: vitest.config.*, jest.config.*, package.json scripts.test, .mocharc.*
  2. E2E framework: Playwright / Cypress / Selenium
    • Look for: playwright.config.*, cypress.json, cypress.config.*
  3. Frontend entry point: Guess from config files
    • Look for: vite.config.*, next.config.*, nuxt.config.*
    • Extract server.port or dev script port
  4. Backend entry point:
    • Look for: nest-cli.json, package.json main script, .env PORT variable
  5. Existing test files:
    • Count *.spec.*, *.test.*, e2e/**, tests/**
  6. Docker / CI config:
    • Look for: docker-compose.yml, .github/workflows/, Dockerfile
  7. Environment files:
    • Look for: .env.example, .env.test, .env.local
    • Extract public variable names (NEVER log values)

Report findings:

🔍 Auto-detected:
  Test framework:    Vitest (vitest.config.ts found)
  E2E framework:     Playwright (playwright.config.ts found)
  Frontend:          http://localhost:5173 (vite.config.ts port: 5173)
  Backend:           http://localhost:3000 (.env PORT=3000)
  Existing tests:    47 spec files, 3 e2e files
  Docker:            docker-compose.yml found (services: app, db)
  Env template:      .env.example (12 variables)

Step 3: Environment Configuration Interview

Ask these as discrete structured prompts (one decision each, related toggles grouped — e.g. URLs together, the multi-select test-type/scope/browser toggles via the host's AskUserQuestion), pre-filling any auto-detected values. Do not dump all seven as one open wall:

Test environment setup — please confirm or correct auto-detected values:

1. **Frontend URL:** {auto-detected or "?"} — where should Playwright navigate?
   (e.g., http://localhost:5173 or https://staging.myapp.com)

2. **Backend/API URL:** {auto-detected or "?"} — base URL for API tests
   (e.g., http://localhost:3000/api)

3. **Test user credentials:** Do tests require login?
   If yes — provide test account: email + password (will be stored only in testing/env.md, never in code)

4. **Additional env vars needed:** Any other credentials, API keys, or config for tests?
   (List names only, I'll ask for values if needed. e.g., "STRIPE_TEST_KEY, GOOGLE_CLIENT_ID")

5. **Test types to generate:**
   Select all that apply:
   - [ ] Smoke tests (critical path, runs first — ~5 min)
   - [ ] E2E Playwright tests (full user journeys — ~15-30 min)
   - [ ] API/Integration tests (endpoint contracts — ~5-10 min)
   - [ ] Regression tests (existing features shouldn't break — ~10-20 min)
   - [ ] Custom: ________________

6. **Test scope:** How thorough?
   - Minimal — happy path only + 2-3 critical edge cases
   - Standard — happy paths + main edge cases + error states
   - Full — all user stories + all acceptance criteria + all edge cases

7. **Browser/Platform targets for Playwright:**
   - [ ] Chrome/Chromium (default)
   - [ ] Firefox
   - [ ] Safari/WebKit
   - [ ] Mobile viewport (375px — iPhone)
   - [ ] Tablet viewport (768px)

Store: FRONTEND_URL, API_URL, TEST_TYPES, TEST_SCOPE, BROWSERS, HAS_AUTH.


Step 4: Create Environment Config

Create {TESTING_DIR}/env.md — stores test environment config (NOT a .env file — never commit credentials):

# Test Environment Config: {Feature Name}

> ⚠️ This file contains test credentials. Do NOT commit to version control.
> Add `testing/env.md` to .gitignore

## Environment

| Variable | Value | Source |
|----------|-------|--------|
| FRONTEND_URL | {url} | User input |
| API_URL | {url} | User input / auto-detected |
| TEST_SCOPE | {scope} | User selection |

## Auth Credentials (test account)
{If auth required — stored here for reference during test execution}
Test email: {email}
Test password: {password}

## Additional Variables
{list of additional env vars with values if provided}

## Browser Targets
{list of selected browsers}

## Notes
{Any special setup steps: seed data, feature flags to enable, etc.}

Also update .gitignore to add testing/env.md if not already present.


Step 5: Extract Test Cases from Feature Artifacts

Read and synthesize:

  1. product-spec/journeys/journeys.ymlauthoritative E2E source — every JRN/STEP/EDGE (see docs/journeys.md).
  2. product-spec/product-spec.md → Must Have user stories + acceptance criteria
  3. product-spec/mockups/component-map.yml → stable selectors per CMP-* region
  4. spec.md → Acceptance criteria (may be more detailed than product-spec)
  5. research/ux-patterns.md → additional edge cases and state inventory

Journey-driven generation (Theme H): E2E cases are generated directly from journeys.yml — one Playwright spec per JRN, each STEP.action → a Playwright action, each STEP.expect/EDGE.then → an assertion, with selectors resolved from component-map.yml. playwright-cli is the committed default runner (config: e2e_runner: playwright-cli).

Build a structured test case matrix:

5A: Smoke Test Cases (always first)

Derive 4–8 critical-path scenarios that answer: "does the feature basically work?"

## Smoke Tests (TC-SMK-NNN)

| ID | Title | Steps | Expected | Priority |
|----|-------|-------|----------|----------|
| TC-SMK-001 | Feature loads without error | 1. Navigate to {URL} 2. Feature renders | No JS errors, content visible | P0 |
| TC-SMK-002 | Primary action works | 1. Perform {main action} | {expected outcome} | P0 |
[...]

5B: E2E Test Cases (generated from journeys.yml)

For each JRN in journeys.yml create test cases:

  • One happy-path case covering all STEPs in order.
  • One case per EDGE (alternate / error / boundary), keyed by its priority.

Map every case back to its journey IDs so coverage is checkable in traceability.yml and verify-full (a Must-Have JRN or P0/P1 EDGE without a TC is a failure).

## E2E Tests: {JRN-NNN journey title} (TC-E2E-NNN)

| ID | Journey | Covers | Scenario | Steps (JRN/STEP) | Expected | Story |
|----|---------|--------|----------|------------------|----------|-------|
| TC-E2E-001 | JRN-001 | STEP-001..002 | Happy path | STEP-001 → STEP-002 | {success state} | US-001 |
| TC-E2E-002 | JRN-001 | EDGE-001 | Save fails (500) | STEP-002 + EDGE-001 | error toast, revert | US-001 |
[...]

5C: API Test Cases (if API tests selected)

For each API endpoint identified in the plan:

  • Happy path (200/201)
  • Invalid input (400)
  • Unauthorized (401)
  • Not found (404)
  • Edge cases
## API Tests (TC-API-NNN)

| ID | Endpoint | Method | Input | Expected Status | Expected Body |
|----|----------|--------|-------|----------------|---------------|
| TC-API-001 | /api/feature | GET | valid token | 200 | {schema} |
| TC-API-002 | /api/feature | GET | no token | 401 | error message |
[...]

5D: Regression Test Cases (if selected)

Identify existing features that could be affected by this change:

  • Look at integration points from research/codebase-analysis.md
  • Look at shared components identified in plan.md
## Regression Tests (TC-REG-NNN)

| ID | Existing Feature | Risk | Test Scenario | Expected |
|----|----------------|------|--------------|----------|
| TC-REG-001 | {feature name} | {how new feature could break it} | {test} | {expected} |
[...]

5E: Unit Test Cases (TC-UNIT-NNN)

Derive from plan.md module boundaries and spec.md behavioural acceptance criteria. Targets per testing-strategy.md §2 Unit layer:

  • Pure functions, validators, formatters, parsers.
  • Service methods with dependency injection (external deps faked).
  • Domain primitives (money math, date arithmetic, identity).
  • UI components whose behaviour depends only on props (render output, event emissions).

Do NOT write TC-UNIT for: pure wiring (controllers that only delegate), framework code, or simple getters/setters. Push those either down (into the unit that contains logic) or up to integration.

## Unit Tests (TC-UNIT-NNN)

| ID | Unit | Workspace | Scenario | Input | Expected | Covers |
|----|------|-----------|----------|-------|----------|--------|
| TC-UNIT-001 | validateEmail | shared | Rejects empty string | "" | { ok: false, reason: "empty" } | FR-003 |
| TC-UNIT-002 | validateEmail | shared | Rejects missing @ | "foo" | { ok: false, reason: "format" } | FR-003 |
| TC-UNIT-003 | validateEmail | shared | Accepts valid RFC 5322 | "a@b.com" | { ok: true } | FR-003 |
| TC-UNIT-004 | UserService.register | backend | First registration wins on race | two concurrent calls, same email | exactly one `created`, one `duplicate` | FR-007 |
| TC-UNIT-005 | PriceFormatter | frontend | Formats amount under 1 unit | 0.42 | "0.42 USD" | UI-012 |
[...]

Fields:

  • Unit — fully-qualified name (class / method / function / component).
  • Workspace — monorepo-only; in single-root omit column.
  • Scenario — the behaviour, not the implementation. Names should survive a rename of the unit under test.
  • Covers — story ID, functional requirement ID, or ADR ID.

For each distinct unit, produce a target coverage note in the summary:

### Unit Coverage Targets

| Unit | Workspace | Target branches | Priority |
|------|-----------|-----------------|----------|
| validateEmail | shared | 4 of 4 | P0 |
| UserService.register | backend | 8 of 9 (skip: legacy NULL path) | P0 |
| PriceFormatter | frontend | 6 of 6 | P1 |

In monorepo mode, unit test IDs remain globally unique (TC-UNIT-NNN) but group by workspace in the summary so workspace teams can filter their slice.

5F: Integration Test Cases (TC-INT-NNN)

Integration tests beyond endpoint contracts. Target the boundaries where behaviour crosses a real collaborator (DB, cache, queue, event bus, filesystem, external HTTP). Per testing-strategy.md §2 Integration layer.

Derive from plan.md data model, research/codebase-analysis.md event patterns, and spec.md NFRs (e.g. "quota enforced across concurrent requests").

## Integration Tests (TC-INT-NNN)

| ID | Collaboration | Workspace | Scenario | Fixture / backing | Expected | Covers |
|----|---------------|-----------|----------|-------------------|----------|--------|
| TC-INT-001 | UserService ↔ Postgres | backend | Register persists row | Testcontainers Postgres 15 | row exists, unique constraint honoured | FR-007 |
| TC-INT-002 | UserService ↔ Redis | backend | Session cached with TTL | miniredis | key present at 299s, absent at 301s | NFR-session |
| TC-INT-003 | RegisterCommand → UserCreated event | backend | Saga compensates on listener failure | in-memory event bus | rollback applied, metric emitted | FR-007 |
| TC-INT-004 | UserRepository migrations | backend | v2 → v3 upgrade preserves rows | test DB loaded with v2 data | all rows present, columns added | MIG-003 |
| TC-INT-005 | Frontend apiClient ↔ backend /users | cross-workspace | Handles 401 with refresh token | MSW mock + real token endpoint | retries once, succeeds on second attempt | FR-009 |
[...]

Fields:

  • Collaboration — which two (or more) components interact and over what boundary.
  • Fixture / backing — testcontainers, in-memory stand-in, shared test DB with rollback, MSW mocks, etc. Name the specific tool.
  • Covers — requirement / NFR / migration ID.

Anti-patterns to reject in this section (see testing-strategy.md §4):

  • Every collaborator stubbed (it's a unit test in disguise).
  • Shared mutable state across tests (use transaction rollback or per-test fresh containers).
  • "Retry on flake" — diagnose, do not paper over.
### Integration Coverage Targets

| Boundary | Workspace | Minimum % | Notes |
|----------|-----------|-----------|-------|
| Service ↔ DB (every repository method) | backend | 90% | Real Postgres via testcontainers. |
| Event emitter ↔ listener (every pair) | backend | 100% | Include idempotency retest. |
| Frontend apiClient ↔ each endpoint | cross-workspace | 80% | MSW mocks acceptable; one real contract test per endpoint. |

In monorepo mode, a TC-INT may span two or more workspaces (listed as cross-workspace) — see TC-INT-005 example. These tests run under whichever workspace's runner is configured as the "primary" for the feature (scope.primary).


Step 6: Generate Playwright Test Files (from journeys)

Generate one .spec.ts per JRN in journeys.yml. Translate mechanically: STEP.action → a Playwright action; STEP.expect / EDGE.then → an assertion. Resolve selectors from mockups/component-map.yml / design-system/manifest.yml (CMP-* → its stable selector, e.g. [data-cmp='button']); fall back to data-testid only when no component selector exists. Tag each test with its JRN/STEP/EDGE ids in a comment so Phase 8B can map failures back.

Into each per-journey spec also emit (executable, not prose — substitute the literals at generation time):

  • (v1.6, W5-B2) one WCAG-AA accessibility test per JRNgated on the a11y_gate config key (default axe; see config-template.yml). Read a11y_gate from the merged config: when it is axe, generate the check below; when it is none, skip generation entirely (emit nothing for the a11y leg) and note a11y_gate: none — automated floor disabled by config in the test-plan's Entry Criteria so the omission is auditable rather than silent. When generated, the check uses @axe-core/playwrightnew AxeBuilder({ page }).withTags(['wcag2a','wcag2aa', 'wcag21a','wcag21aa']).analyze() then expect(a11y.violations, 'JRN-NNN …').toEqual([]), scanned at the journey's end state. This is an automated AA floor — manual review is still required and this is not a hard zero-violations claim beyond AA. Fulfils the bridge AC "Accessibility requirements pass automated + manual testing". Add @axe-core/playwright to the project's dev deps (entry in the test-plan "Entry Criteria") if absent. (A no-op for non-browser journeys regardless of the key.)
  • (v1.6, W5-B5) one component + variant + design-token conformance test per JRN. For every mockups/component-map.yml region whose journeys: includes this JRN, read its component (CMP-*), variant, look up the component's selector in design-system/manifest.yml, and emit three executable assertions: (1) the component is visible at its manifest selector; (2) toHaveAttribute('data-variant', '<region.variant>') for the mapped variant; (3) a design-token check — resolve the relevant manifest.tokens.* CSS-var on :root via getComputedStyle and assert the component's computed style equals it (catches token drift / hardcoded hex). These ride this Phase 8A generator only — never verify-full (Phase 7 is read-only, no running app).

Create {TESTING_DIR}/playwright-tests/ folder.

For each journey:

// {TESTING_DIR}/playwright-tests/{feature-slug}-{journey-name}.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright'; // (v1.6, W5-B2) WCAG-AA automated floor

// Feature: {Feature Name}
// Journey: {Journey Name}
// Generated by Product Forge Phase 8A
// Stories covered: {US-NNN list}

test.describe('{Feature Name} — {Journey Name}', () => {

  test.beforeEach(async ({ page }) => {
    // Setup: navigate and authenticate if needed
    await page.goto('{FRONTEND_URL}');
    {if HAS_AUTH:}
    // Login with test credentials
    await page.fill('[data-testid="email"]', process.env.TEST_EMAIL || '{test-email}');
    await page.fill('[data-testid="password"]', process.env.TEST_PASSWORD || '{test-password}');
    await page.click('[data-testid="login-submit"]');
    await page.waitForURL('**/dashboard');
  });

  // TC-E2E-001: Happy Path — {description}
  // Journey: JRN-001 STEP-001..002 (happy path — no EDGE)
  // Covers: US-001 acceptance criteria: {AC text}
  test('should {primary user outcome}', async ({ page }) => {
    // Arrange
    await page.goto('{feature URL}');

    // Act — Step 1: {action}
    await page.click('{selector}');

    // Act — Step 2: {action}
    await page.fill('{selector}', '{value}');

    // Act — Step 3: {action}
    await page.click('[data-testid="submit"]');

    // Assert
    await expect(page.locator('{result selector}')).toBeVisible();
    await expect(page.locator('{result selector}')).toContainText('{expected text}');
  });

  // (v1.6, W5-B2) WCAG-AA accessibility floor for this journey.
  // Emit ONE such test per JRN (manual review still required — AA-automated only,
  // not a hard zero-violations claim beyond AA). Fulfils bridge.md AC "Accessibility
  // requirements pass automated + manual testing" (the automated half).
  // Journey: JRN-001 (whole-page scan at the journey's end state)
  test('JRN-001 meets WCAG 2.1 A/AA automated floor', async ({ page }) => {
    await page.goto('{feature URL}');
    // …drive to the journey end state (reuse the happy-path STEP actions above)…
    const a11y = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
      .analyze();
    expect(a11y.violations, 'JRN-001 WCAG-AA violations').toEqual([]);
  });

  // (v1.6, W5-B5) Runtime component + variant + design-token conformance.
  // Emit ONE assertion block per region in mockups/component-map.yml whose
  // `journeys:` includes this JRN. CMP / VARIANT / token values are read at
  // generation time from component-map.yml + design-system/manifest.yml — the
  // agent substitutes literals; these are executable Playwright assertions.
  // Journey: JRN-001 — region "save bar" → CMP-Button variant "primary"
  test('JRN-001 uses mapped components, variants, and design tokens', async ({ page }) => {
    await page.goto('{feature URL}');

    // 1. Component present (manifest selector for {CMP-Button} = [data-cmp='button'])
    const cmp = page.locator("[data-cmp='button']");
    await expect(cmp).toBeVisible();

    // 2. Variant matches component-map (region.variant = "primary").
    //    ASSUMPTION: bind the variant assertion to the project's design-system
    //    convention. The repo documents `data-cmp` but NOT how variants surface;
    //    this example uses `data-variant` — switch to a class check
    //    (e.g. toHaveClass(/btn--primary/)) if the design system surfaces variants as classes.
    await expect(cmp).toHaveAttribute('data-variant', 'primary');

    // 3. Design-token conformance: the computed value equals the manifest token.
    //    ASSUMPTION: which CSS property a token binds to is project-specific. This
    //    example maps manifest.tokens.color.primary (var(--color-primary)) → background-color;
    //    rebind to color/border-color per the component if that is where the token lands.
    //    Resolve the var on :root and assert the component's computed value equals it
    //    (catches token drift / hardcoded hex).
    const tokenValue = await page.evaluate(() =>
      getComputedStyle(document.documentElement).getPropertyValue('--color-primary').trim()
    );
    const actual = await cmp.evaluate((el) => getComputedStyle(el).backgroundColor);
    const expected = await page.evaluate((raw) => {
      const probe = document.createElement('span');
      probe.style.backgroundColor = raw;           // normalise token → rgb()
      document.body.appendChild(probe);
      const v = getComputedStyle(probe).backgroundColor;
      probe.remove();
      return v;
    }, tokenValue);
    expect(actual, "JRN-001 CMP-Button background uses --color-primary token").toBe(expected);
  });

  // TC-E2E-002: Empty State
  // Journey: JRN-001 STEP-001 / EDGE-002
  test('should show empty state when no data exists', async ({ page }) => {
    // Navigate to feature with no data
    await page.goto('{feature URL}?empty=true');
    await expect(page.locator('[data-testid="empty-state"]')).toBeVisible();
  });

  // TC-E2E-003: Error State
  // Journey: JRN-001 STEP-002 / EDGE-001
  test('should handle error gracefully', async ({ page }) => {
    // Simulate error condition
    await page.route('**/api/**', route => route.fulfill({ status: 500 }));
    await page.goto('{feature URL}');
    await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
  });

});

Important notes in generated tests:

  • Use data-testid selectors by default (most stable)
  • Tag each test(...) with a per-case // Journey: JRN-NNN STEP-NNN..NNN / EDGE-NNN comment (same JRN/STEP/EDGE shape as the 5B matrix columns) so a failing test maps straight back to the journey step it broke — not just the file header
  • Add comments mapping each test to US-NNN and AC text
  • Use process.env for credentials (never hardcode)
  • Include setup/teardown for test isolation
  • (v1.6, W5-B2) include the per-JRN @axe-core/playwright WCAG-AA test
  • (v1.6, W5-B5) include the per-JRN component/variant/token conformance test, one assertion block per mapped component-map.yml region

Create {TESTING_DIR}/playwright-tests/playwright.config.ts if not exists:

// Product Forge generated Playwright config for: {feature-slug}
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './playwright-tests',
  timeout: 30000,
  retries: 1,
  reporter: [['html', { outputFolder: '../testing/playwright-report' }]],
  use: {
    baseURL: process.env.FRONTEND_URL || '{FRONTEND_URL}',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    {FIREFOX_if_selected: name: 'firefox', use: { ...devices['Desktop Firefox'] }},
    {WEBKIT_if_selected: name: 'webkit', use: { ...devices['Desktop Safari'] }},
    {MOBILE_if_selected: name: 'mobile-chrome', use: { ...devices['Pixel 5'] }},
  ],
});

Step 7: Generate Test Plan Master Document

Create {TESTING_DIR}/test-plan.md:

# Test Plan: {Feature Name}

> Created: {date} | Phase: 8A
> Feature: `{feature-slug}`
> Environment: {FRONTEND_URL}

## Scope

### In Scope
{Features and flows being tested}

### Out of Scope
{What is explicitly NOT being tested — and why}

## Test Types & Estimated Duration

| Type | Count | Est. Duration | Files |
|------|-------|--------------|-------|
| Smoke | {N} | ~5 min | playwright-tests/{slug}-smoke.spec.ts |
| E2E Playwright | {N} | ~{N*2} min | playwright-tests/{slug}-*.spec.ts |
| API/Integration | {N} | ~{N} min ||
| Regression | {N} | ~{N*3} min | playwright-tests/{slug}-regression.spec.ts |

**Total estimated:** ~{N} minutes

## Environment
- Frontend: {FRONTEND_URL}
- API: {API_URL}
- Browsers: {list}
- Auth required: {yes/no}

## Test Case Summary

### Coverage Matrix

| User Story | Smoke | E2E | API | Regression | Coverage |
|------------|-------|-----|-----|-----------|---------|
| US-001: {title} | TC-SMK-001 | TC-E2E-001,002 | TC-API-001 || ✅ Full |
| US-002: {title} || TC-E2E-005 | TC-API-003,004 | TC-REG-001 | ✅ Full |

### Complete Test Case Index
{Link to test-cases.md}

## Entry Criteria (before testing starts)
- [ ] All Phase 7 verify-full CRITICAL issues resolved
- [ ] Feature deployed to test environment
- [ ] Test data seeded / reset
- [ ] Playwright installed: `npx playwright install`
- [ ] a11y floor dep installed (v1.6, W5-B2): `npm i -D @axe-core/playwright`
- [ ] Credentials configured in `testing/env.md`

## Exit Criteria (testing complete when)
- [ ] All P0 smoke tests PASS
- [ ] All E2E happy paths PASS
- [ ] ≥80% of all test cases PASS
- [ ] Zero P0/P1 open bugs
- [ ] All P2 bugs documented with workarounds

## Bug Severity Definition
| Severity | Definition | Examples |
|----------|-----------|---------|
| P0 Blocker | Cannot proceed with testing | App crashes, auth broken |
| P1 Critical | Core user journey broken | Primary action fails |
| P2 High | Important feature broken | Edge case fails, UX degraded |
| P3 Medium | Minor issue | Wrong text, small layout issue |
| P4 Low | Cosmetic | Typo, pixel misalignment |

## How to Run Tests

### Agent-driven execution (Phase 8B — recommended)
Run `/speckit.product-forge.test-run` — the AI agent reads `test-cases.md` and executes
each step interactively using `playwright-cli`.

> Requires: [`playwright-cli`](https://github.com/microsoft/playwright-cli) installed globally.
> See project README → Requirements.

### CI/CD pipeline execution
\`\`\`bash
# Smoke tests (run first)
npx playwright test --grep @smoke

# All E2E tests for this feature
npx playwright test testing/playwright-tests/{slug}-*.spec.ts

# Regression tests
npx playwright test --grep @regression

# Single test by ID
npx playwright test --grep "TC-E2E-001"

# Run with UI mode (debug)
npx playwright test --ui
\`\`\`

Step 8: Create Test Cases Document

Create {TESTING_DIR}/test-cases.md — all test cases in one searchable document. This is the primary input for Phase 8B playwright-cli execution.

Include all TC-SMK, TC-E2E, TC-API, TC-REG cases in full detail with:

  • Preconditions
  • Step-by-step instructions written as discrete UI actions (navigate, click, fill, wait, assert) so Phase 8B can translate each step directly to a playwright-cli command
  • Expected result (what to verify via snapshot / screenshot / DOM assertion)
  • A per-case **Journey:** JRN-NNN | **Steps:** STEP-NNN to NNN | **Edge:** EDGE-NNN header line (same shape as the 5B matrix columns) so Phase 8B can map a failure back to the journey step
  • Linked story (US-NNN)
  • Linked AC

Write each test case step at the playwright-cli action granularity:

## TC-E2E-001: Happy path — {title}

**Preconditions:** User is logged in. No data exists for {feature}.
**Journey:** JRN-001 | **Steps:** STEP-001 to 002 | **Edge:** — (none — happy path)
**Story:** US-001 | **AC:** 1.1, 1.2

| # | Action | playwright-cli equivalent |
|---|--------|--------------------------|
| 1 | Navigate to {url} | `playwright-cli goto {url}` |
| 2 | Click "{button label}" | `playwright-cli click "text={button label}"` |
| 3 | Fill "{field label}" with "{value}" | `playwright-cli fill "[name={field}]" "{value}"` |
| 4 | Click "Submit" | `playwright-cli click "[data-testid=submit]"` |
| 5 | Assert: success toast appears | `playwright-cli snapshot` → verify "Success" visible |

**Expected result:** {outcome}
**Screenshot point:** After step 4

This format lets Phase 8B translate steps mechanically — no ambiguity, no interpretation needed.

Step 8.1: Write the tests: column of the traceability matrix (Theme C)

test-plan is the registered writer of the tests column in {FEATURE_DIR}/traceability.yml (see docs/templates/traceability-matrix.md). After generating the cases, update the matrix:

  • For each requirement row, set tests: [TC-*] to the case IDs that cover it (matched via the row's story / journeys keys and each case's **Story:** / **Journey:** header).
  • In the journeys block, fill each journey's tests: (journey-level E2E/smoke cases) and each edge's tests: (the per-edge TC-E2E cases).
  • Do not advance status here — planning a test does not make a row tested; status: tested is set later by test-run. If the matrix does not yet exist (tasks Phase 5 not run), skip this step and leave the column null for a later run.

Step 9: Create Bugs Folder

Initialize {BUGS_DIR}/README.md:

# Bug Tracker: {Feature Name}

> Feature: `{feature-slug}` | Testing started: {date}

## Dashboard

| Severity | Open | Fixed | Retested ✅ | Won't Fix |
|----------|------|-------|------------|-----------|
| P0 Blocker | 0 | 0 | 0 | 0 |
| P1 Critical | 0 | 0 | 0 | 0 |
| P2 High | 0 | 0 | 0 | 0 |
| P3 Medium | 0 | 0 | 0 | 0 |
| P4 Low | 0 | 0 | 0 | 0 |
| **Total** | **0** | **0** | **0** | **0** |

## Bug List

| ID | Title | Severity | Status | Test Case | Assigned |
|----|-------|----------|--------|-----------|---------|

## Status Legend
🔴 Open · 🟡 In Progress · 🟢 Fixed · ✅ Verified · ❌ Won't Fix

Step 10: Update Status

phases:
  test_plan: completed
testing:
  types: [smoke, e2e, api, regression]
  test_case_count: {N}
  playwright_files: {N}
  environment_url: "{FRONTEND_URL}"
last_updated: "{ISO timestamp}"

Update feature README.md — add Phase 8A as ✅ Complete.


Step 11: Present Results

📋 Test Plan Created: {Feature Name}

Test cases generated: {N} total
  • Smoke:       {N} cases — playwright-tests/{slug}-smoke.spec.ts
  • E2E:         {N} cases — {N} Playwright files
  • API:         {N} cases — documented in test-cases.md
  • Regression:  {N} cases — playwright-tests/{slug}-regression.spec.ts

Coverage: {N}/{N} Must Have stories covered ✅

Files created:
  testing/test-plan.md
  testing/test-cases.md
  testing/env.md          ← ⚠️ Add to .gitignore
  testing/playwright-tests/{slug}-*.spec.ts

Execution options:
  Agent-driven  → /speckit.product-forge.test-run  (uses playwright-cli, recommended)
  CI/CD pipeline → npx playwright test testing/playwright-tests/{slug}-*.spec.ts

Before running (either mode):
  1. Ensure playwright-cli is installed (see README → Requirements)
  2. Install Playwright browsers: npx playwright install
  3. Set credentials in testing/env.md
  4. Ensure app is running at {FRONTEND_URL}

Then present the structured gate (template: docs/templates/interaction-prompts.md → Gate):

[Gate] Test plan complete — {N} cases across {types}, {N}/{N} Must-Have stories covered. How do you want to proceed?

  1. Approve (recommended) — accept the test plan and continue to Phase 8B: Test Execution
  2. Revise — re-run test planning with feedback
  3. Skip Phase 8B — move on without running tests (a reason may be required)
  4. Rollback — return to an earlier phase by name
  5. Abort — stop the lifecycle for this feature
  (or type your own answer)

Record the choice as a gates[] decision ∈ approved | approved_with_conditions | revised | skipped | rolled_back | aborted (with rolled_back_to when rolling back) per policy §2.