---
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
