diff --git a/apps/pwa/e2e/README.md b/apps/pwa/e2e/README.md new file mode 100644 index 0000000..9edf275 --- /dev/null +++ b/apps/pwa/e2e/README.md @@ -0,0 +1,104 @@ +# E2E Test Harness + +Playwright test harness for the FamilySync PWA — mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium), authenticated via `DEV_AUTH_BYPASS`, deterministically seeded, runs headlessly in CI. + +--- + +## Prerequisites + +**You bring up the dev stack first (D-09).** The harness waits for it — it does not start it. + +See `docs/deployment.md` under "Running locally (host-side, no Docker)" for the canonical bring-up command. + +The stack must include: +- API on `:3000` started with `DEV_AUTH_BYPASS=true` (see Security Guardrail below) +- PWA dev server on `:5173` (`pnpm --filter @familysync/pwa dev`) +- Dev MariaDB on `:3306` (exposed via `docker-compose.dev.yml`) +- Redis on `:6379` + +**`DEV_AUTH_BYPASS=true` MUST be set in the API's environment BEFORE the API process starts.** The harness cannot inject it at runtime — the API reads the env var once at startup. If the API is running without it, all `/api/*` requests return an auth redirect and every spec fails. + +--- + +## Run Commands + +```bash +# Full suite — both iPhone (WebKit) and Pixel (Chromium) profiles +pnpm --filter @familysync/pwa test:e2e + +# Single profile (faster local iteration) +pnpm --filter @familysync/pwa exec playwright test --project=pixel + +# Headed (local debug — shows the browser) +pnpm --filter @familysync/pwa exec playwright test --headed + +# UI mode (interactive test explorer) +pnpm --filter @familysync/pwa test:e2e:ui +``` + +--- + +## Env Vars + +The harness reads these from the environment. DB credentials are env-only — never hardcoded in seed scripts or specs. + +| Var | Default | Purpose | +|-----|---------|---------| +| `PLAYWRIGHT_BASE_URL` | `http://localhost:5173` | Base URL for all spec navigation and the /health readiness poll | +| `DB_HOST` | `127.0.0.1` | MariaDB host for the global-setup seed script | +| `DB_PORT` | `3306` | MariaDB port | +| `DB_USER` | `familysync` | MariaDB user | +| `DB_PASSWORD` | *(empty)* | MariaDB password — set in environment or `.env` | +| `DB_NAME` | `familysync` | MariaDB database name | + +Set `DB_PASSWORD` (and other non-default values) via the shell or the repo root `.env` file before running. The `.env` file is gitignored — never commit credentials. + +--- + +## Security Guardrail — DEV_AUTH_BYPASS + +`DEV_AUTH_BYPASS=true` is a **development-only bypass** that resolves all API requests to Dev User id 1 without OIDC authentication. + +The API enforces this via `apps/api/src/auth/devBypass.ts`: + +``` +if (process.env.NODE_ENV === 'production') → bypass is a no-op (always) +if (process.env.DEV_AUTH_BYPASS !== 'true') → bypass is a no-op +``` + +**The production Docker Compose (`docker-compose.yml`) MUST NOT set `DEV_AUTH_BYPASS`.** Setting it in production is an Elevation of Privilege vulnerability — any request would be resolved as the dev user with no authentication. + +The `docker-compose.dev.yml` override sets it for local dev and CI. Review it before any production deployment to confirm `DEV_AUTH_BYPASS` is absent from the production compose file. + +--- + +## No Session State File + +This harness uses **no `storageState` file** (D-01). There is no checked-in session cookie, no expiring auth artifact, and no per-run login flow. `DEV_AUTH_BYPASS=true` makes the API respond as user 1 unconditionally — the tests run repeatably day-over-day without re-authentication. See PITFALLS.md §Pitfall 14 for why `storageState` is excluded. + +--- + +## What globalSetup Does + +Before any spec runs, `global-setup.ts`: + +1. **Polls `PLAYWRIGHT_BASE_URL/health`** until 200 OK (60s timeout, then fails fast with a clear error). +2. **Truncates** `list_items`, `list_shares`, `lists`, `calendar_events` (FK checks disabled around TRUNCATE). +3. **Seeds** deterministic fixtures for Dev User 1: + - One timed calendar event (`'Seeded Test Event'`) on calendar_id=10 + - One shared list (`'E2E Grocery List'`) owned by user 1, with a `list_shares` row and two items (`'Milk'`, `'Eggs'`) + +This seeding is idempotent — two consecutive runs leave the same row counts, no stale rows, no duplicate-key errors. + +--- + +## CI (Phase 8) + +Phase 8 (Gitea CI) runs these specs unchanged as a PR UI-regression step. The CI workflow owns: +- Bringing up the dev stack (compose) with `DEV_AUTH_BYPASS=true` +- Waiting for the MariaDB health check before starting the API +- Setting `PLAYWRIGHT_BASE_URL` and `DB_*` env vars in the runner environment + +The harness handles its own readiness gate (`/health` poll) once the runner sets things up. No changes to spec files are needed for CI — the harness is stack-agnostic via env vars. + +CI Dockerfile must use `playwright install --with-deps webkit chromium` to install WebKit system deps (see RESEARCH.md §Pitfall 6).