--- phase: 07-mobile-test-harness plan: '02' subsystem: test-harness tags: [playwright, e2e, global-setup, db-seed, mysql2, readiness-gate] dependency_graph: requires: - 'apps/pwa/playwright.config.ts (07-01) — globalSetup path reference' - 'apps/api dev MariaDB :3306 — seed target' - 'apps/api DEV_AUTH_BYPASS=true — required in API process before harness runs' provides: - 'global-setup.ts — /health readiness poll + deterministic reset-and-seed' - 'e2e/README.md — run instructions and security guardrails' - 'mysql2@3.22.4 devDependency in apps/pwa' affects: - 'Phase 07 plans 03-04 (specs depend on this seed for populated-state assertions)' - 'Phase 08 CI (globalSetup runs unchanged in the CI runner)' tech_stack: added: - 'mysql2@3.22.4 devDependency in apps/pwa — enables mysql2/promise in global-setup.ts' patterns: - 'TRUNCATE + INSERT (not INSERT IGNORE) for seed rows — D-06 deterministic reset' - 'INSERT IGNORE INTO calendars guard — ensures calendar_id=10 FK satisfied on fresh CI DB (Pitfall 4)' - 'SET FOREIGN_KEY_CHECKS=0/1 around TRUNCATE — FK-safe truncate ordering' - 'fetch() for /health poll — native Node.js 22, no @playwright/test import (Pitfall 2)' key_files: created: - apps/pwa/e2e/README.md modified: - apps/pwa/e2e/global-setup.ts - apps/pwa/package.json - pnpm-lock.yaml decisions: - 'D-07-02-mysql2-in-pwa: Added mysql2@3.22.4 as devDependency to apps/pwa — global-setup.ts needs mysql2/promise for TypeScript types; the package was already in the monorepo (apps/api), so pnpm install just linked it without downloading' - "D-07-02-deadline-check: Added explicit deadline check after the health poll loop to distinguish 'loop exited via break (success)' from 'loop exited via deadline expiry' — ensures throw fires correctly on timeout" - 'D-07-02-dtend-in-vevent: Added DTEND line to the minimal VCALENDAR seed string for spec compatibility — some CalDAV parsers reject VEVENTs without DTEND' metrics: duration_seconds: 196 completed_date: '2026-06-11' tasks_completed: 2 files_changed: 4 --- # Phase 07 Plan 02: globalSetup Readiness Gate + DB Seed Summary **One-liner:** Playwright globalSetup with 60s /health readiness poll and deterministic TRUNCATE+INSERT seed onto calendar_id=10 and user_id=1 lists — idempotent run-over-run. ## What Was Built - `apps/pwa/e2e/global-setup.ts` — full implementation replacing the Plan 01 stub: - Step 1 (D-08): polls `${PLAYWRIGHT_BASE_URL}/health` with a 60-second deadline; swallows ECONNREFUSED; breaks on first `res.ok`; throws with a clear diagnostic message if the deadline passes - Step 2 (D-06/D-07): direct mysql2 connection using exact env-var names from `apps/api/src/db/client.ts`; `SET FOREIGN_KEY_CHECKS=0` → TRUNCATE list_items/list_shares/lists/calendar_events → `SET FOREIGN_KEY_CHECKS=1` → INSERT IGNORE calendars guard (id=10) → one timed calendar_event → E2E Grocery List (owner_id=1, is_shared=true) + list_shares row + Milk/Eggs items - No `@playwright/test` imports — plain Node.js (Pitfall 2 compliant) - `apps/pwa/e2e/README.md` — operator reference documenting: - Prerequisites: dev stack (API + PWA + MariaDB + Redis) with DEV_AUTH_BYPASS=true - Run commands: `pnpm --filter @familysync/pwa test:e2e`, single profile, headed, UI mode - Env var contract: PLAYWRIGHT_BASE_URL, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME — credentials env-only, never hardcoded (T-07-05) - Security guardrail: DEV_AUTH_BYPASS is dev-only, NODE_ENV !== 'production' hard guard, production compose MUST NOT set it (T-07-04) - No storageState (D-01 — designed out) - CI scope note: Phase 8 brings up the stack; harness handles its own readiness gate - `apps/pwa/package.json` — mysql2@3.22.4 added as devDependency (same version as apps/api; pnpm linked without downloading) ## Verification Evidence - `grep "^import mysql from 'mysql2/promise'" apps/pwa/e2e/global-setup.ts` — found - `grep "from '@playwright/test'" apps/pwa/e2e/global-setup.ts` — absent (Pitfall 2 pass) - `grep "TRUNCATE TABLE" apps/pwa/e2e/global-setup.ts` — 4 tables (list_items, list_shares, lists, calendar_events) - `grep "INSERT IGNORE INTO calendars" apps/pwa/e2e/global-setup.ts` — found with VALUES (10, 1, ...) - `grep "list_shares\|Milk\|Eggs" apps/pwa/e2e/global-setup.ts` — all present - `grep -c 'DEV_AUTH_BYPASS|NODE_ENV|storageState|PLAYWRIGHT_BASE_URL|test:e2e' apps/pwa/e2e/README.md` → 15 (acceptance criteria: non-zero count) - `tsc --noEmit --project tsconfig.e2e.json` (apps/pwa) → 0 errors - `tsc --noEmit` (apps/pwa src) → 0 errors ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] mysql2 not available in apps/pwa** - **Found during:** Task 1 TypeScript check — `tsc --noEmit --project tsconfig.e2e.json` emitted `TS2307: Cannot find module 'mysql2/promise'` - **Issue:** mysql2 is in `apps/api/dependencies` but not linked to `apps/pwa`. The global-setup imports `mysql2/promise` which requires the package to be a direct or devDependency of apps/pwa for TypeScript resolution. - **Fix:** Added `"mysql2": "3.22.4"` to `apps/pwa/devDependencies` (same version as apps/api to stay in sync). `pnpm install` linked it from the pnpm store in 4s with zero downloads — the binary was already present from apps/api. - **Files modified:** `apps/pwa/package.json`, `pnpm-lock.yaml` - **Commit:** 53498e3 **2. [Rule 2 - Missing Critical] Explicit deadline-exceeded throw after poll loop** - **Found during:** Task 1 implementation review — the research pattern's while loop exits via `break` on success OR when `Date.now() >= deadline`. After the loop, without an explicit check, code would silently proceed to the DB seed on a timed-out poll, causing confusing mysql2 errors rather than a clear "stack is not up" message. - **Fix:** Added `if (Date.now() >= deadline) { throw new Error(...) }` immediately after the while loop so timeout is distinguishable from success. - **Files modified:** `apps/pwa/e2e/global-setup.ts` - **Commit:** 53498e3 **3. [Rule 2 - Missing Critical] DTEND in minimal VCALENDAR seed string** - **Found during:** Task 1 implementation — minimal VCALENDAR without DTEND may fail CalDAV/ical.js parsing in some spec paths. Plan said "minimal VCALENDAR/VEVENT" but no explicit DTEND. - **Fix:** Added DTEND line (futureStart + 1 hour) to the VCALENDAR seed string for spec compatibility. Does not affect seed idempotency. - **Files modified:** `apps/pwa/e2e/global-setup.ts` - **Commit:** 53498e3 ## Known Stubs None — the Plan 01 stub in global-setup.ts is fully replaced with the real implementation. ## Threat Surface Scan No new network endpoints, auth paths, or schema changes. All changes are test-infrastructure files only. Threat mitigations from plan threat model: - **T-07-04 (Elevation of Privilege / DEV_AUTH_BYPASS):** README explicitly documents that DEV_AUTH_BYPASS is dev-only, that the API guards on `NODE_ENV !== 'production'`, and that the production compose MUST NOT set it. global-setup does not set the env var (it cannot — it runs after the API is already up). - **T-07-05 (Information Disclosure / DB credentials):** global-setup reads DB_HOST/DB_PORT/DB_USER/DB_PASSWORD/DB_NAME from env exclusively, mirroring `apps/api/src/db/client.ts`. No credential is hardcoded. README states this explicitly. - **T-07-06 (Information Disclosure / OIDC session state):** No storageState.json is written by the harness. README states this. Designed out per D-01. ## Self-Check: PASSED - `apps/pwa/e2e/global-setup.ts` — exists - `apps/pwa/e2e/README.md` — exists - Task 1 commit `53498e3` — exists - Task 2 commit `535ba11` — exists