Files
familysync/apps/pwa/e2e
Lucas BergerandClaude Opus 4.8 ddeb87cdba test(18): make timezone e2e deterministic and align with WR-01 fix
The timezone-verify spec assumed a first-run (unset) starting state, but
e2e global-setup truncated only the list/event tables — never app_config —
so a prior run's saved household_timezone leaked across runs. Clear that key
in global-setup so the spec always starts from isExplicitlySet:false.

Also repurpose the stale "Save disabled when unchanged" assertion: after the
WR-01 fix, first-run Save is correctly ENABLED when the input matches the
displayed default (saving confirms the detected zone). The disabled-when-
unchanged-and-explicit case remains covered by the persist-across-reload test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 23:07:36 -04:00
..

E2E Test Harness

Playwright test harness for the FamilySync PWA — mobile-emulated (iPhone 14/WebKit + Pixel 7/Chromium) and Desktop Chrome (1280×720), 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

global-setup.ts is fail-closed: it refuses to run (throws before any DB write) unless DEV_AUTH_BYPASS=true and NODE_ENV !== 'production' — the same handshake the API uses (see Security Guardrail). So DEV_AUTH_BYPASS=true must be exported in the test process environment (not only the API's). Source the DB credentials from the repo-root .env and point DB_HOST at the host-side MariaDB:

# Load DB creds, then run. DEV_AUTH_BYPASS=true is required by the global-setup guard.
set -a; source .env; set +a
export DEV_AUTH_BYPASS=true DB_HOST=127.0.0.1 DB_PORT=3306

# Full suite — iPhone (WebKit), Pixel (Chromium), Desktop Chrome profiles
pnpm --filter @familysync/pwa test:e2e

# Single profile (faster local iteration)
pnpm --filter @familysync/pwa exec playwright test --project=pixel
pnpm --filter @familysync/pwa exec playwright test --project=desktop

# 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. Fail-closed guard: throws immediately if NODE_ENV === 'production' or DEV_AUTH_BYPASS !== 'true', before opening any DB connection — so the TRUNCATE/seed can never run against a production (or unconfirmed) database.
  2. Polls PLAYWRIGHT_BASE_URL/health until 200 OK (60s timeout, then fails fast with a clear error).
  3. Truncates list_items, list_shares, lists, calendar_events (FK checks disabled around TRUNCATE).
  4. 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, DB_*, and DEV_AUTH_BYPASS=true env vars in the runner environment (the global-setup guard requires DEV_AUTH_BYPASS=true in the Playwright process, not only the API's)

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