# E2E Test Harness Cucumber + Playwright end-to-end tests for the project. The intent is that **every locked workflow has a `.feature` file before it gets a line of implementation code** (see "Phase 7.5 — Acceptance scenarios" in the discovery playbook). ## Layout ``` e2e/ package.json — cucumber, @playwright/test, tsx cucumber.mjs — runner config tests/ features/*.feature — Gherkin scenarios (one feature per locked workflow) steps/*.steps.ts — step definitions; common.steps.ts is generic support/ world.ts — AppWorld (browser, page, login, gotoApp, expectText) hooks.ts — spawns servers (optional), DB reset hook, browser lifecycle types.ts — config (URLs, timeouts) read from env reports/ — generated HTML reports ``` ## Running The harness assumes the app is reachable. Either: **A. Start servers yourself** (recommended during dev): ```sh yarn dev # in repo root — starts frontend + backend yarn workspace @project/e2e test ``` **B. Let the harness manage the servers**: ```sh E2E_MANAGE_SERVERS=1 \ E2E_BACKEND_CMD="yarn dev:backend" \ E2E_FRONTEND_CMD="yarn dev" \ yarn workspace @project/e2e test ``` ## Configuration All env vars optional; defaults match the kanban-board-template's ports. | Var | Default | Purpose | |----------------------|----------------------------|-----------------------------------------------| | `APP_URL` | `http://localhost:7104` | Vite frontend URL | | `API_URL` | `http://localhost:4003` | Pikku backend URL | | `E2E_TIMEOUT` | `30000` | Per-step Playwright timeout (ms) | | `E2E_MANAGE_SERVERS` | unset | Set to `1` to have hooks spawn dev servers | | `E2E_BACKEND_CMD` | `yarn dev:backend` | Used when `E2E_MANAGE_SERVERS=1` | | `E2E_FRONTEND_CMD` | `yarn dev` | Used when `E2E_MANAGE_SERVERS=1` | | `E2E_RESET_URL` | unset | POST URL that resets DB to seed between tests | | `HEADED` | unset | Set to `1` to run with a visible browser | ## DB reset between scenarios Scenarios pollute each other. To get a deterministic DB state per scenario, implement a `__test_reset` Pikku function gated behind a test-only flag, and point `E2E_RESET_URL` at it. The harness will POST to it in the `Before` hook. If the URL is unset or unreachable the harness logs a warning and continues. Example shape: ```ts export const testReset = pikkuSessionlessFunc({ expose: true, description: 'Reset DB to seed state. Test-only — gated by NODE_ENV.', func: async ({ kysely, config }) => { if (config.env !== 'test') throw new Error('forbidden') await kysely.deleteFrom('booking').execute() // ... drop + reseed all mutable tables return { ok: true as const } }, }) ``` ## Writing a feature ```gherkin Feature: Scenario: Given I am logged in as "demo@yoga-retreat.example" with password "demo1234" When I visit "/bookings" Then I see "Summer Yoga Retreat" ``` Generic steps live in `tests/steps/common.steps.ts`: - `Given I visit ""` - `Given I am logged in as "" with password ""` - `When I log in as "..." with password "..."` - `When I log out` - `When I fill "" with ""` - `When I click "