test(e2e): cross-browser and mobile-viewport projects #380

Merged
james merged 1 commit from test/e2e-cross-browser-mobile into main 2026-06-29 17:35:42 +00:00
Owner

Extends the Playwright e2e suite (apps/e2e/) to the other shapes Carol's
universal client targets — other browser engines, and the narrow viewport
where the sidebar collapses to a slide-in drawer. Last child of #150.

Closes #327
Refs #150

This PR weakens one cookie attribute only behind a fail-closed e2e gate
please scrutinise that gate.

  • New apps/api/lib/auth/cookie-security.ts:
    cookieSecure() === (process.env.NODE_ENV === "production"
                        && process.env.E2E_TEST_ROUTES !== "1")
    
  • It replaces the inline NODE_ENV === "production" Secure decision in
    session.ts (sessionCookieOptions), themes/cookie.ts, and
    oauth-cookies.ts.
  • Why: the e2e harness boots via next start (so NODE_ENV=production)
    but serves over plain-HTTP http://127.0.0.1. Firefox and WebKit refuse to
    store a Secure cookie set over an insecure origin, which silently breaks
    the session cookie and every authed flow under those engines.
  • Fail-closed: the discriminator is E2E_TEST_ROUTES, the same flag that
    already gates the destructive POST /api/test/reset route. It is set only by
    apps/e2e/scripts/serve.sh, never by any real deployment. With the flag
    unset (every real deployment), cookies stay Secure. The gate is the env
    flag alone, not NODE_ENV, because the harness and a real deploy are both
    NODE_ENV=production — a NODE_ENV check couldn't tell them apart.
    httpOnly and SameSite are untouched.
  • Unit test (apps/api/tests/auth/cookie-security.test.ts): true in prod with
    the flag unset; false when E2E_TEST_ROUTES=1; still true when the flag is a
    non-"1" truthy value (exact match); false in dev. Env mutations are
    restored via vi.unstubAllEnvs() so the suite can't leak.
  • semgrep scan --config p/nodejsscan --config .semgrep --error apps/api
    0 findings (281 targets), so no suppression was needed.

The rest

  • Viewport-aware nav helpers (apps/e2e/fixtures/nav.ts):
    gotoViaSidebar / logoutViaSidebar branch on page.viewportSize() width
    < 720px — open the drawer via the hamburger (nav.expandSidebar) first on a
    narrow viewport, then act. nav.expandSidebar added to fixtures/strings.ts
    from the i18n catalog (no hardcoded copy).
  • Smoke refactor (smoke.spec.ts): its two nav clicks and log-out now go
    through the helpers, and the shell-renders assertion is viewport-aware
    (hamburger on mobile vs inline log-out on desktop). Still self-registers (no
    storageState).
  • New mobile-nav.spec.ts (mobile-authed project): asserts the drawer
    mechanics desktop can't — no permanent sidebar + hamburger on load; tap
    hamburger → drawer opens → tap Profile → routes to /profile and the drawer
    closes; open then dismiss via the scrim without navigating. Open/closed is
    detected with toBeInViewport() (the closed drawer sits off-screen at
    left: -240px).
  • New Playwright projects: firefox, webkit, mobile (Pixel 5) run the
    smoke spec; mobile-authed (Pixel 5 + storageState, deps setup) runs
    mobile-nav.spec.ts. Existing setup/chromium/smoke/admin unchanged; domain +
    admin specs stay Chromium-desktop.
  • CI (.forgejo/workflows/pr.yml): browser install now
    --with-deps chromium firefox webkit.
  • Docs (apps/e2e/README.md): documents the new projects/engines and the
    e2e cookie-Secure relaxation (test-only var, stays out of the README config
    tables).

Verification

  • pnpm -F @carol/api test — 1039 passed / 231 skipped (the skips are the
    Postgres legs; TEST_POSTGRES_URL unset locally — CI runs the PG matrix).
    New cookie-security test green.
  • semgrep ... apps/api — 0 findings. openapi:check + openapi:coverage
    clean. pnpm -F @carol/api lint, pnpm -F @carol/e2e exec tsc --noEmit,
    actionlint pr.yml all clean.
  • pnpm -F @carol/e2e test on the Chromium-capable projects (setup, chromium,
    smoke, mobile, mobile-authed, admin) — 15 passed, 1 skipped (the
    pre-existing profile-picture test.fixme). Smoke passes on desktop Chromium
    and the Pixel 5 drawer path; the mobile-nav spec passes.
  • Firefox/WebKit did not run locally: the binaries download but the host
    (Fedora) is missing their system libraries and the only offered fix is
    Debian apt/sudo, unavailable in this sandbox. WebKit's launch failed
    purely on missing host deps, not test logic. CI installs --with-deps, so
    the FF/WebKit smoke runs there — that is the real gate for those engines.

🤖 Generated with Claude Code

Extends the Playwright e2e suite (`apps/e2e/`) to the other shapes Carol's universal client targets — other browser engines, and the narrow viewport where the sidebar collapses to a slide-in drawer. Last child of #150. Closes #327 Refs #150 ## Security review: cookie `Secure` relaxation (read this first) This PR weakens one cookie attribute **only behind a fail-closed e2e gate** — please scrutinise that gate. - New `apps/api/lib/auth/cookie-security.ts`: ```ts cookieSecure() === (process.env.NODE_ENV === "production" && process.env.E2E_TEST_ROUTES !== "1") ``` - It replaces the inline `NODE_ENV === "production"` Secure decision in `session.ts` (`sessionCookieOptions`), `themes/cookie.ts`, and `oauth-cookies.ts`. - **Why:** the e2e harness boots via `next start` (so `NODE_ENV=production`) but serves over plain-HTTP `http://127.0.0.1`. Firefox and WebKit refuse to *store* a `Secure` cookie set over an insecure origin, which silently breaks the session cookie and every authed flow under those engines. - **Fail-closed:** the discriminator is `E2E_TEST_ROUTES`, the same flag that already gates the destructive `POST /api/test/reset` route. It is set only by `apps/e2e/scripts/serve.sh`, never by any real deployment. With the flag unset (every real deployment), cookies stay `Secure`. The gate is the env flag alone, not `NODE_ENV`, because the harness and a real deploy are both `NODE_ENV=production` — a `NODE_ENV` check couldn't tell them apart. `httpOnly` and `SameSite` are untouched. - Unit test (`apps/api/tests/auth/cookie-security.test.ts`): true in prod with the flag unset; false when `E2E_TEST_ROUTES=1`; still true when the flag is a non-`"1"` truthy value (exact match); false in dev. Env mutations are restored via `vi.unstubAllEnvs()` so the suite can't leak. - `semgrep scan --config p/nodejsscan --config .semgrep --error apps/api` → **0 findings** (281 targets), so no suppression was needed. ## The rest - **Viewport-aware nav helpers** (`apps/e2e/fixtures/nav.ts`): `gotoViaSidebar` / `logoutViaSidebar` branch on `page.viewportSize()` width < 720px — open the drawer via the hamburger (`nav.expandSidebar`) first on a narrow viewport, then act. `nav.expandSidebar` added to `fixtures/strings.ts` from the i18n catalog (no hardcoded copy). - **Smoke refactor** (`smoke.spec.ts`): its two nav clicks and log-out now go through the helpers, and the shell-renders assertion is viewport-aware (hamburger on mobile vs inline log-out on desktop). Still self-registers (no storageState). - **New `mobile-nav.spec.ts`** (mobile-authed project): asserts the drawer mechanics desktop can't — no permanent sidebar + hamburger on load; tap hamburger → drawer opens → tap Profile → routes to `/profile` and the drawer closes; open then dismiss via the scrim without navigating. Open/closed is detected with `toBeInViewport()` (the closed drawer sits off-screen at `left: -240px`). - **New Playwright projects**: `firefox`, `webkit`, `mobile` (Pixel 5) run the smoke spec; `mobile-authed` (Pixel 5 + storageState, deps `setup`) runs `mobile-nav.spec.ts`. Existing setup/chromium/smoke/admin unchanged; domain + admin specs stay Chromium-desktop. - **CI** (`.forgejo/workflows/pr.yml`): browser install now `--with-deps chromium firefox webkit`. - **Docs** (`apps/e2e/README.md`): documents the new projects/engines and the e2e cookie-Secure relaxation (test-only var, stays out of the README config tables). ## Verification - `pnpm -F @carol/api test` — 1039 passed / 231 skipped (the skips are the Postgres legs; `TEST_POSTGRES_URL` unset locally — CI runs the PG matrix). New cookie-security test green. - `semgrep ... apps/api` — 0 findings. `openapi:check` + `openapi:coverage` clean. `pnpm -F @carol/api lint`, `pnpm -F @carol/e2e exec tsc --noEmit`, `actionlint pr.yml` all clean. - `pnpm -F @carol/e2e test` on the Chromium-capable projects (setup, chromium, smoke, mobile, mobile-authed, admin) — **15 passed, 1 skipped** (the pre-existing profile-picture `test.fixme`). Smoke passes on desktop Chromium **and** the Pixel 5 drawer path; the mobile-nav spec passes. - **Firefox/WebKit did not run locally**: the binaries download but the host (Fedora) is missing their system libraries and the only offered fix is Debian `apt`/`sudo`, unavailable in this sandbox. WebKit's launch failed purely on missing host deps, not test logic. CI installs `--with-deps`, so the FF/WebKit smoke runs there — that is the real gate for those engines. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
test(e2e): cross-browser and mobile-viewport projects
All checks were successful
Commits / Conventional Commits (pull_request) Successful in 8s
PR / Client (web export smoke) (pull_request) Successful in 2m35s
PR / Static analysis (pull_request) Successful in 2m40s
PR / OpenAPI (pull_request) Successful in 3m1s
PR / pnpm audit (pull_request) Successful in 3m8s
PR / Build (pull_request) Successful in 3m27s
PR / Lint (pull_request) Successful in 3m38s
PR / Typecheck (pull_request) Successful in 3m45s
PR / OSV-Scanner (pull_request) Successful in 1m25s
PR / Test (sqlite) (pull_request) Successful in 4m12s
PR / Test (postgres) (pull_request) Successful in 4m19s
PR / Package age policy (soft) (pull_request) Successful in 1m0s
PR / Coverage (soft) (pull_request) Successful in 2m22s
PR / E2E (Playwright) (pull_request) Successful in 5m54s
Secrets / gitleaks (pull_request) Successful in 52s
PR / Trivy (image) (pull_request) Successful in 2m19s
5ec412b527
Extend the Playwright suite (#327, last child of #150) to the other shapes
Carol's universal client targets: Firefox + WebKit run the smoke flow, and a
narrow Pixel 5 viewport exercises the slide-in drawer nav.

- Relax the cookie Secure flag only under the e2e boot, gated on the existing
  fail-closed E2E_TEST_ROUTES flag (new lib/auth/cookie-security.ts, rewired
  into session/theme/oauth cookies). Firefox/WebKit reject Secure cookies over
  the harness's plain-HTTP loopback, which would break all auth. Production
  (flag unset) stays Secure; httpOnly/sameSite untouched.
- Viewport-aware nav helpers (fixtures/nav.ts) + smoke refactor so the one
  flow proves auth on both the permanent sidebar and the drawer.
- New mobile-nav.spec.ts asserts the drawer mechanics desktop can't.
- New firefox/webkit/mobile/mobile-authed Playwright projects.
- CI installs chromium+firefox+webkit with system deps.

Refs #150
Closes #327

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

📊 Test coverage

Patch coverage: no testable lines changed.

Overall (app/, lib/, db/, excluding UI per ADR-0019):

Metric Value Soft target
Lines 79.5% ≥ 50%
Branches 71.6% ⚠️ ≥ 75%
Functions 80.6% informational

Soft thresholds per ADR-0019. Coverage is informational and does not block merge.

<!-- coverage-comment --> ## 📊 Test coverage **Patch coverage:** no testable lines changed. **Overall** (`app/`, `lib/`, `db/`, excluding UI per ADR-0019): | Metric | Value | Soft target | |---|---|---| | Lines | 79.5% ✅ | ≥ 50% | | Branches | 71.6% ⚠️ | ≥ 75% | | Functions | 80.6% | informational | Soft thresholds per [ADR-0019](docs/adr/0019-coverage-soft-targets.md). Coverage is informational and does not block merge.
james merged commit 89d7f0d26d into main 2026-06-29 17:35:42 +00:00
james deleted branch test/e2e-cross-browser-mobile 2026-06-29 17:35:42 +00:00
Sign in to join this conversation.
No description provided.