---
phase: 08-gitea-ci
plan: 03
subsystem: testing
tags: [playwright, ci, gitea, mariadb, webkit, chromium, dev-auth-bypass]

# Dependency graph
requires:
  - phase: 07-mobile-test-harness
    provides: Phase 7 Playwright specs (both device profiles) run unchanged in CI
  - phase: 08-02
    provides: ci.yml with fast-checks + api jobs; runner-mode (ubuntu-latest, Docker-executor, services:)
provides:
  - harness job in .gitea/workflows/ci.yml bringing up the full dev stack in CI and running the Phase 7 mobile specs across both device profiles
  - four infrastructure fixes resolving API-reap, IPv6/IPv4 mismatch, missing dev-user seed, and double-pnpm reporter forwarding
affects: [08-04, phase-09, phase-10, phase-11, phase-12]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - 'Combine API-start + readiness + test run in a single CI step so the API is not reaped at a step boundary'
    - 'NODE_OPTIONS=--dns-result-order=ipv4first when Vite is IPv4-only and the runner resolves localhost to ::1 first'
    - 'Idempotent seed step (INSERT IGNORE) for the DEV_AUTH_BYPASS user before global-setup runs — FK chain requires it'
    - 'Call the PWA test:e2e script directly with --filter instead of root test:e2e -- -- to avoid double-pnpm arg forwarding'

key-files:
  created: []
  modified:
    - .gitea/workflows/ci.yml

key-decisions:
  - "FIX-1 (53a989c): Start API + run e2e in a single step — bare 'node &' in an early step is reaped when that step exits; the API must remain a child of the test shell through the entire Playwright run"
  - 'FIX-2 (7389740): Use PLAYWRIGHT_BASE_URL=http://127.0.0.1:5173 and NODE_OPTIONS=--dns-result-order=ipv4first — Vite binds IPv4-only; Node fetch does not fall back from ::1 to 127.0.0.1 unlike curl'
  - 'FIX-3 (e486c6b): Seed dev user id=1 (INSERT IGNORE) after migrate, before API start — DEV_AUTH_BYPASS injects the user in-memory only; on a fresh CI DB the FK constraint silently aborted the calendars seed'
  - "FIX-4 (03e8088): Call 'pnpm --filter @familysync/pwa test:e2e --reporter=list,html' directly — 'pnpm test:e2e -- --reporter=list,html' double-forwards '--' through two pnpm layers; Playwright treats --reporter as a test-file filter and finds no tests"
  - 'Phase 7 harness files (playwright.config.ts, global-setup.ts, vite.config.ts, all specs) were NOT modified — CI owns stack bring-up only (D-01/D-02 phase boundary held)'
  - "Reporter override --reporter=list,html kept: Gitea does not render 'github' annotations; list output is legible in the log"

patterns-established:
  - 'Harness step pattern: install browsers, then start API + wait for :3000/health, then run Playwright — all in one step'
  - "Idempotent user seed step: INSERT IGNORE + upsert pattern for DEV_AUTH_BYPASS user before global-setup's FK-dependent seeds"

requirements-completed: [CI-01]

# Metrics
duration: ~2h (including CI iteration across 4 infrastructure fixes)
completed: 2026-06-11
---

# Phase 08 Plan 03: Harness CI Job Summary

**Gitea Actions harness job brings up MariaDB + API (DEV_AUTH_BYPASS) + Playwright Vite on every PR and runs 58 Phase 7 specs across iPhone/WebKit + Pixel/Chromium in 1.6 min — four infrastructure fixes required, no harness file modified**

## Performance

- **Duration:** ~2h (task authoring + 4 CI fix iterations)
- **Started:** 2026-06-11
- **Completed:** 2026-06-11
- **Tasks:** 2 auto + 1 checkpoint (human-verified)
- **Files modified:** 1 (.gitea/workflows/ci.yml)

## Accomplishments

- Harness job added to ci.yml: MariaDB 11 service → migrate → seed dev user → API background (DEV_AUTH_BYPASS=true, :3000) → Playwright starts Vite (:5173) → both iphone (WebKit) + pixel (Chromium) profiles → traces upload on failure
- 58 Phase 7 specs passed green on Gitea Actions run #11 (PR #3, pull_request) — cold CI stack, 1.6 min
- All four CI-side infrastructure fixes resolved without touching any Phase 7 harness file (phase boundary D-01/D-02 held)
- Artifact upload confirmed working: playwright-traces-10 downloaded from Gitea UI on run #10

## Task Commits

1. **Task 1: Add harness job — DB + migrate + API background + :3000 readiness** - `d55e347` (feat)
2. **Task 2: Add Playwright install + run (both profiles) + artifact upload** - `71c8909` (feat)
3. **Fix 1: Keep API alive during harness — start API + run e2e in one step** - `53a989c` (fix)
4. **Fix 2: Harness uses 127.0.0.1 + ipv4first — Vite is IPv4-only** - `7389740` (fix)
5. **Fix 3: Seed dev user id=1 — global-setup assumes it exists** - `e486c6b` (fix)
6. **Fix 4: Call pwa test:e2e directly so --reporter forwards cleanly** - `03e8088` (fix)

## Files Created/Modified

- `.gitea/workflows/ci.yml` — harness job added; fast-checks + api jobs unchanged

## Decisions Made

**D-08-03-COMBINE-STEP:** API start + readiness wait + `pnpm test:e2e` run combined into a single CI step. When the API was started with `node &` in a standalone step, the backgrounded process was reaped when that step exited — the multi-minute browser install that followed caused the API to die before the test step. Confirmed the API does not self-crash when left as a background child of the test shell.

**D-08-03-IPV4FIRST:** `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5173` and `NODE_OPTIONS=--dns-result-order=ipv4first` set on the harness step. The Gitea runner resolves `localhost` to `::1` (IPv6) first; Vite binds IPv4-only (`127.0.0.1:5173`); Node `fetch` does not fall back to IPv4 unlike `curl`. Proven: `[::1]:5173 ECONNREFUSED` vs `127.0.0.1:5173 200`. The API is dual-stack so its `localhost:3000` references were unaffected.

**D-08-03-SEED-USER:** An idempotent "Seed dev user (id=1)" step runs after `db:migrate` and before the API starts. `DEV_AUTH_BYPASS` in `devBypass.ts` injects the user entirely in-memory — on a fresh CI database there is no `users` row, so the `global-setup.ts` `INSERT IGNORE INTO calendars` silently fails on the FK constraint and calendar id=10 is absent, causing a cascade FK error on `calendar_events`. The seed is `INSERT IGNORE INTO users (id, oidc_iss, oidc_sub, display_name, color) VALUES (1, 'dev', 'dev-user', 'Dev User', '#4A90D9')`.

**D-08-03-REPORTER-FORWARD:** The root `test:e2e` script is `pnpm --filter @familysync/pwa test:e2e`. Calling `pnpm test:e2e -- --reporter=list,html` from the root passes `--` through two pnpm layers, resulting in `playwright test -- --reporter=list,html` where `--reporter=list,html` is treated as a test-file path filter — Playwright finds no tests. Fix: call `pnpm --filter @familysync/pwa test:e2e --reporter=list,html` directly. Validated: 58 specs listed vs 0 with the broken invocation.

## Deviations from Plan

### Auto-fixed Issues (all Rule 3 — blocking)

**1. [Rule 3 - Blocking] API reaped at step boundary**

- **Found during:** CI run after Task 1+2 commits
- **Issue:** Bare `node apps/api/dist/index.js &` in an early step was reaped when that step exited. The browser install (multi-minute) ran next, then the test step found no API.
- **Fix:** Merged API start + curl :3000/health readiness loop + `pnpm test:e2e` into a single step; moved browser install to the step immediately before it.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** 53a989c

**2. [Rule 3 - Blocking] global-setup ECONNREFUSED on Vite :5173**

- **Found during:** CI run post fix 1
- **Issue:** `global-setup.ts` fetched `${PLAYWRIGHT_BASE_URL}/health`; `PLAYWRIGHT_BASE_URL` defaulted to `http://localhost:5173`; runner resolved `localhost` → `::1`; Vite bound only `127.0.0.1:5173` → `ECONNREFUSED`.
- **Fix:** Added `PLAYWRIGHT_BASE_URL: http://127.0.0.1:5173` and `NODE_OPTIONS: --dns-result-order=ipv4first` to the harness step env.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** 7389740

**3. [Rule 3 - Blocking] Missing dev user id=1 causes FK error in global-setup seed**

- **Found during:** CI run post fix 2
- **Issue:** `global-setup.ts` seeds `calendars` + `calendar_events` for `user_id=1`. `DEV_AUTH_BYPASS` injects that user in-memory only (no DB row). On a fresh CI DB, the `INSERT IGNORE INTO calendars` silently aborted on the `users` FK; calendar id=10 was absent; the `calendar_events` insert then failed on the calendars FK.
- **Fix:** Added a "Seed dev user" step after `db:migrate`: `INSERT IGNORE INTO users` with `id=1, oidc_iss='dev', oidc_sub='dev-user', display_name='Dev User', color='#4A90D9'`.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** e486c6b

**4. [Rule 3 - Blocking] --reporter flag treated as test-file filter**

- **Found during:** CI run post fix 3
- **Issue:** `pnpm test:e2e -- --reporter=list,html` from the workspace root double-forwarded `--` through two pnpm invocations, delivering `playwright test -- --reporter=list,html`; Playwright interpreted `--reporter=list,html` as a test-file path and found no tests.
- **Fix:** Changed invocation to `pnpm --filter @familysync/pwa test:e2e --reporter=list,html` — bypasses the root script delegation entirely.
- **Files modified:** .gitea/workflows/ci.yml
- **Committed in:** 03e8088

---

**Total deviations:** 4 auto-fixed (all Rule 3 — blocking CI failures). All were infrastructure/orchestration issues. No Phase 7 harness files (playwright.config.ts, global-setup.ts, vite.config.ts, or any spec) were modified.

## Verified CI Result

**Gitea Actions run #11** (PR #3, `pull_request` event) — conclusion **SUCCESS**

- **Harness result:** 58 passed in 1.6 min
- **Profiles:** iphone (WebKit) + pixel (Chromium), both passing
- **Co-running jobs:** fast-checks (191 PWA tests) + api (238 API tests) — all green in the same run
- **Artifact upload:** Confirmed working on run #10 — `playwright-traces-10` uploaded with a download URL via `ChristopherHX/gitea-upload-artifact@v4`
- **Phase boundary:** Zero Phase 7 files modified — confirmed via `git diff --quiet -- apps/pwa/playwright.config.ts apps/pwa/e2e/global-setup.ts apps/pwa/vite.config.ts`

## CI Stack Bring-Up Order (confirmed working)

1. `services: mariadb:11` container (DB_HOST=mariadb, Docker-executor mode)
2. `actions/checkout@v4` + `setup-node@v4` (Node 22) + `corepack enable pnpm`
3. `pnpm install --frozen-lockfile`
4. mysql2 readiness loop until mariadb port 3306 accepts connections
5. `pnpm --filter @familysync/api db:migrate` (never push — verified no `db:push` in ci.yml)
6. Seed dev user id=1 (INSERT IGNORE — idempotent)
7. `pnpm --filter @familysync/api build` → dist/index.js
8. `npx playwright install --with-deps webkit chromium` (from apps/pwa working-directory)
9. Combined step: `NODE_ENV=development DEV_AUTH_BYPASS=true ... node apps/api/dist/index.js &` → curl :3000/health readiness loop → `pnpm --filter @familysync/pwa test:e2e --reporter=list,html`
10. `if: failure()` — artifact upload via `ChristopherHX/gitea-upload-artifact@v4`
11. `if: always()` — kill API background process

## Issues Encountered

WebKit deps install: clean exit 0 — confirmed on this runner (D-PROBE-05 from plan 01, re-verified here). No issues encountered.

Reporter legibility: `list` reporter produced readable per-test output in the Gitea log; `html` report built but is only accessible via artifact download.

## Known Stubs

None.

## Threat Flags

None — no new network endpoints or auth paths introduced. The `DEV_AUTH_BYPASS=true` flag is scoped to the harness job only; it does not appear in the publish job (Plan 04). Threat mitigations T-08-06, T-08-07, T-08-08 confirmed implemented.

## Next Phase Readiness

- Plan 04 (publish job) is unblocked: harness green, CI-01 harness half complete
- ROADMAP CI-01 criteria 3 (failure gates merge) and 4 (readiness waits) satisfied
- Plan 04 needs `GITEA_REGISTRY_PAT` (deferred D-PROBE-08) — operator must create the PAT before the publish step can push to the Gitea container registry

---

_Phase: 08-gitea-ci_
_Completed: 2026-06-11_
