Skip to content

fix(e2e): add Playwright auth fixture to unblock 356 UI tests - #12

Merged
mborgeson merged 1 commit into
mainfrom
fix/e2e-auth-fixture
Apr 30, 2026
Merged

fix(e2e): add Playwright auth fixture to unblock 356 UI tests#12
mborgeson merged 1 commit into
mainfrom
fix/e2e-auth-fixture

Conversation

@mborgeson

Copy link
Copy Markdown
Owner

Root cause

The 356/403 E2E failures on PR #8 trace to a single defect: every browser-context test in e2e/ calls page.goto(...) on a protected route without an authenticated browser session. RequireAuth (defined in src/app/router.tsx) sees isAuthenticated === false, returns <Navigate to=\"/login\" replace />, and the assertion expect(page.locator('main')).toBeVisible() times out because <main> only renders inside AppLayout — which lives under RequireAuth.

The 47 passing tests are all API-only — they use Playwright's request fixture and never spin up a browser, so the auth gate is irrelevant.

Fix

Implement Playwright storageState global setup so all browser-context tests start authenticated.

  • e2e/global.setup.ts (new): a Playwright "setup" test that drives the UI login form with TEST_CREDENTIALS.admin and persists cookies + localStorage (where authStore.ts writes the JWT) to e2e/.auth/admin.json via context.storageState({ path }).
  • playwright.config.ts: adds a setup project keyed off global.setup.ts, and wires both chromium and ci projects to dependencies: ['setup'] plus use.storageState: 'e2e/.auth/admin.json'.
  • e2e/auth.spec.ts — "Protected Routes (Frontend)" describe block: when written, the app had no auth gate and these tests asserted that <main> renders without auth. They now use test.use({ storageState: { cookies: [], origins: [] } }) to opt out of the global storage state and assert that RequireAuth redirects to /login.
  • .gitignore: adds e2e/.auth/ so the storage file (which contains a real JWT) never gets committed.

Expected impact

Roughly 340 of the 356 currently-failing browser tests should flip to passing once the setup project runs successfully and the storage state seeds an authenticated session into each browser context. The remaining ~16 failures are likely unrelated (selector drift, fixture data, timing) and can be triaged on the next CI run.

Type sanity check: npx tsc --noEmit playwright.config.ts e2e/global.setup.ts e2e/auth.spec.ts exits 0. The full E2E suite was not run locally — CI will validate.

Branch hygiene

Branched from origin/main. Does not touch ci/fix-e2e-seed-and-bcrypt (PR #8) or feat/test-quality-system (PR #11).

Follow-ups (not in this PR)

These are the "right fix" items the architect flagged for a separate change:

  • Workflow seed-gate — make the E2E job fail fast if seed users are missing instead of letting tests time out.
  • Smoke fail-fast — run a tiny smoke check before the full suite so credentials/seed/auth-gate breakage surfaces in seconds, not minutes.
  • Per-role storage states — extend the setup project to produce analyst.json (and any future roles) so role-gated tests can opt in without UI logging in each time.

Test plan

  • CI green on chromium project (storage state authenticates correctly, ~340 tests flip from fail to pass)
  • Protected Routes (Frontend) describe still verifies redirect-to-login behavior (now via empty storageState override)
  • No regression in the 47 API-only tests that already passed
  • e2e/.auth/admin.json is not committed (verify via git ls-files e2e/.auth/)

Generated with Claude Code

E2E browser-context tests called page.goto(...) on protected routes
without an authenticated browser session. RequireAuth redirected them to
/login, and assertions like `expect(page.locator('main')).toBeVisible()`
timed out because <main> only renders inside AppLayout (gated by
RequireAuth). Result: 356 of 403 tests failed; only the 47 API-only
tests (which use the request fixture) passed.

Fix:
  - Add e2e/global.setup.ts: a Playwright "setup" project that drives
    the UI login flow with TEST_CREDENTIALS.admin and persists cookies +
    localStorage to e2e/.auth/admin.json via context.storageState().
  - Wire chromium and ci projects to depend on the setup project and
    consume the persisted state via use.storageState.
  - Override the "Protected Routes (Frontend)" describe block in
    auth.spec.ts with empty storageState so it can verify RequireAuth
    redirects to /login when unauthenticated.
  - Add e2e/.auth/ to .gitignore — the storage file holds a real JWT.

Type-checked with `npx tsc --noEmit playwright.config.ts e2e/global.setup.ts e2e/auth.spec.ts` (exit 0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mborgeson
mborgeson force-pushed the fix/e2e-auth-fixture branch from 37271a2 to acf3324 Compare April 30, 2026 10:29
@mborgeson
mborgeson merged commit f5dac7b into main Apr 30, 2026
1 check failed
@mborgeson
mborgeson deleted the fix/e2e-auth-fixture branch April 30, 2026 10:29
mborgeson added a commit that referenced this pull request Apr 30, 2026
Add a one-shot smoke project that runs before every other Playwright
project. It validates the most fundamental contract — dev server up,
React app served, router redirects unauthenticated users to /login —
in a single navigation. When it fails, Playwright skips downstream
projects via the new dependencies wiring, so the suite exits in <2 min
instead of running ~40 min of redundant red across 400+ tests.

The smoke test is intentionally standalone: no auth, no storageState,
no backend dependency beyond the login page being reachable. This lets
it compose cleanly with the auth setup project that PR #12 introduces;
chromium and ci can list both as dependencies in any order without
coupling smoke to credential availability.

- e2e/smoke.spec.ts: single test, asserts /login redirect and email
  field visible (proves React rendered, not a hydration crash)
- playwright.config.ts: new "smoke" project (testMatch on smoke spec,
  retries: 0). chromium and ci gain dependencies: ['smoke'] and
  testIgnore on smoke.spec.ts so the smoke test only runs once.

Verified: tsc --noEmit clean; playwright test --list shows 1 smoke
test under the smoke project and 404 tests under chromium (smoke
excluded as expected).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mborgeson added a commit that referenced this pull request Apr 30, 2026
)

Add a one-shot smoke project that runs before every other Playwright
project. It validates the most fundamental contract — dev server up,
React app served, router redirects unauthenticated users to /login —
in a single navigation. When it fails, Playwright skips downstream
projects via the new dependencies wiring, so the suite exits in <2 min
instead of running ~40 min of redundant red across 400+ tests.

The smoke test is intentionally standalone: no auth, no storageState,
no backend dependency beyond the login page being reachable. This lets
it compose cleanly with the auth setup project that PR #12 introduces;
chromium and ci can list both as dependencies in any order without
coupling smoke to credential availability.

- e2e/smoke.spec.ts: single test, asserts /login redirect and email
  field visible (proves React rendered, not a hydration crash)
- playwright.config.ts: new "smoke" project (testMatch on smoke spec,
  retries: 0). chromium and ci gain dependencies: ['smoke'] and
  testIgnore on smoke.spec.ts so the smoke test only runs once.

Verified: tsc --noEmit clean; playwright test --list shows 1 smoke
test under the smoke project and 404 tests under chromium (smoke
excluded as expected).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant