| 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" |
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.
$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.
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 fromtest-cases.mdand drives the browser step-by-step usingplaywright-cli open,playwright-cli click,playwright-cli fill,playwright-cli snapshot,playwright-cli screenshot, andplaywright-cli tracing-start/stop.
.spec.tsfiles serve as a CI/CD companion — run them withnpx playwright testin your pipeline. Both artifacts are generated together in Phase 8A and complement each other.
- Read
.forge-status.yml—verifymust becompleted(Phase 7 done) - Verify
spec.mdandproduct-spec/product-spec.mdexist - Verify all implementation tasks in
tasks.mdare[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/
Before asking the user, scan the codebase to collect environment info:
Scanning project for test configuration...
Detect:
- Test framework: Vitest / Jest / Mocha / pytest / RSpec / other
- Look for:
vitest.config.*,jest.config.*,package.json scripts.test,.mocharc.*
- Look for:
- E2E framework: Playwright / Cypress / Selenium
- Look for:
playwright.config.*,cypress.json,cypress.config.*
- Look for:
- Frontend entry point: Guess from config files
- Look for:
vite.config.*,next.config.*,nuxt.config.* - Extract
server.portordevscript port
- Look for:
- Backend entry point:
- Look for:
nest-cli.json,package.jsonmain script,.envPORT variable
- Look for:
- Existing test files:
- Count
*.spec.*,*.test.*,e2e/**,tests/**
- Count
- Docker / CI config:
- Look for:
docker-compose.yml,.github/workflows/,Dockerfile
- Look for:
- Environment files:
- Look for:
.env.example,.env.test,.env.local - Extract public variable names (NEVER log values)
- Look for:
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)
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.
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.
Read and synthesize:
product-spec/journeys/journeys.yml→ authoritative E2E source — everyJRN/STEP/EDGE(see docs/journeys.md).product-spec/product-spec.md→ Must Have user stories + acceptance criteriaproduct-spec/mockups/component-map.yml→ stable selectors perCMP-*regionspec.md→ Acceptance criteria (may be more detailed than product-spec)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 perJRN, eachSTEP.action→ a Playwright action, eachSTEP.expect/EDGE.then→ an assertion, with selectors resolved fromcomponent-map.yml.playwright-cliis the committed default runner (config:e2e_runner: playwright-cli).
Build a structured test case matrix:
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 |
[...]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 |
[...]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 |
[...]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} |
[...]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.
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).
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
JRN— gated on thea11y_gateconfig key (defaultaxe; see config-template.yml). Reada11y_gatefrom the merged config: when it isaxe, generate the check below; when it isnone, skip generation entirely (emit nothing for the a11y leg) and notea11y_gate: none — automated floor disabled by configin the test-plan's Entry Criteria so the omission is auditable rather than silent. When generated, the check uses@axe-core/playwright—new AxeBuilder({ page }).withTags(['wcag2a','wcag2aa', 'wcag21a','wcag21aa']).analyze()thenexpect(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/playwrightto 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 everymockups/component-map.ymlregion whosejourneys:includes thisJRN, read itscomponent(CMP-*),variant, look up the component'sselectorindesign-system/manifest.yml, and emit three executable assertions: (1) the component is visible at its manifestselector; (2)toHaveAttribute('data-variant', '<region.variant>')for the mapped variant; (3) a design-token check — resolve the relevantmanifest.tokens.*CSS-var on:rootviagetComputedStyleand assert the component's computed style equals it (catches token drift / hardcoded hex). These ride this Phase 8A generator only — neververify-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-testidselectors by default (most stable) - Tag each
test(...)with a per-case// Journey: JRN-NNN STEP-NNN..NNN / EDGE-NNNcomment (sameJRN/STEP/EDGEshape 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.envfor credentials (never hardcode) - Include setup/teardown for test isolation
- (v1.6, W5-B2) include the per-
JRN@axe-core/playwrightWCAG-AA test - (v1.6, W5-B5) include the per-
JRNcomponent/variant/token conformance test, one assertion block per mappedcomponent-map.ymlregion
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'] }},
],
});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
\`\`\`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-clicommand - Expected result (what to verify via snapshot / screenshot / DOM assertion)
- A per-case
**Journey:** JRN-NNN | **Steps:** STEP-NNN to NNN | **Edge:** EDGE-NNNheader 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 4This format lets Phase 8B translate steps mechanically — no ambiguity, no interpretation needed.
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'sstory/journeyskeys and each case's**Story:**/**Journey:**header). - In the
journeysblock, fill each journey'stests:(journey-level E2E/smoke cases) and each edge'stests:(the per-edgeTC-E2Ecases). - Do not advance
statushere — planning a test does not make a rowtested;status: testedis set later bytest-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.
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 Fixphases:
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.
📋 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.