Skip to content

test(e2e): hermetic Playwright browser suite for visibility states#65

Merged
rmjoia merged 3 commits into
mainfrom
claude/e2e-visibility-playwright
May 29, 2026
Merged

test(e2e): hermetic Playwright browser suite for visibility states#65
rmjoia merged 3 commits into
mainfrom
claude/e2e-visibility-playwright

Conversation

@rmjoia

@rmjoia rmjoia commented May 26, 2026

Copy link
Copy Markdown
Owner

What this PR delivers

Adds the browser-rendering half of the per-field visibility coverage, plus wires it into CI as a deploy-gating step (your requirement: dependency/security + unit + e2e all gate CD).

The server-side filtering math is already unit-tested. This suite asserts the browser paints the right state given a canned API response — the thing source-regex tests can only approximate.

Approach (per the agreed design): hermetic + mocked API. Serves the production build via astro preview; page.route() intercepts /.auth/me, /api/get-roles, /api/profile-by-username, /api/profiles. The SWA /find/* → /find/profile/index.html rewrite (absent in astro preview) is emulated by fulfilling /find/<username> navigations with the built detail HTML, so the page's window.location.pathname username extraction runs exactly as in prod.

Coverage (10 tests):

  • Detail page: public profile renders all fields; bio stripped → hidden (not blank paragraph); skills stripped → section hidden; 403 → private state + @username; 404 → not-found state + @username; 500 → error state
  • Directory: "you're listed" when principal matches a row; "want to be listed" when not; case-insensitive listed match; both banners hidden on empty directory

CI wiring — runs as a step in validate (hermetic, pre-deploy). dev_deploy needs: validate, so a failing browser test now blocks the deploy. The header comment maps each gate (dependency/security, unit, e2e) to the CD-blocking relationship; the merge-blocking half is the existing branch-protection "Validate required check" setting.

⚠️ Reviewer note — local verification limit

The sandbox I authored this in can't download the Chromium binary (network-restricted), so the browser assertions first truly execute in this PR's CI run. Everything up to browser launch is locally verified: Playwright config valid, webServer boots, all 10 tests collected, spec loads clean (the __dirname-in-ESM fix was caught locally), and the full non-browser gate is green (lint, 485 unit tests, format, build, API typecheck/build, audits). Please watch the Validate job's Playwright step on this PR — if any assertion needs a tweak (timing, selector), I'll iterate.

Verified by

  • e2e-browser/visibility.spec.ts — 10 Playwright tests covering every visibility/status render state (see Coverage above). These execute for the first time in this PR's validate job — the local gate verified collection + config + spec load, not the assertions themselves (sandbox can't fetch the browser binary).
  • npm run test:run — 485 unit tests pass; confirms the new .spec.ts files are correctly excluded from the vitest run (still 485, no Playwright import errors)
  • Non-browser gate green locally: lint, format, build, API typecheck/build, frontend + API audits

Constitution compliance

  • P1 — no secrets added (canned principals are obviously fake test data)
  • P2 Code Quality — lint/format clean; suite is small + focused
  • P3 Security — no production code touched; test-only addition. The mocked principals never reach a real auth path
  • P4 Performance — adds ~1-2 min to the validate job (browser install + 10 hermetic tests); acceptable for a gating suite
  • P5–P8 — N/A (test infrastructure)
  • P9 Verified Quality (NON-NEGOTIABLE) — see "Verified by", including the honest caveat about first-real-run-in-CI

Platform constraints

  • New constraint surfaced + documented inline: the SWA /find/* rewrite doesn't exist in astro preview, so the hermetic suite emulates it via page.route fulfillment. Noted in the spec's header comment.

Operator action items

  • Confirm Validate remains the required status check on main (it now includes the browser e2e step — no settings change needed if it was already required; just flagging that the gate's scope grew).

Test plan

  • Local non-browser gate: lint, 485 unit tests, format, build, API typecheck/build, audits — all green
  • Playwright config + spec collection verified locally (10 tests discovered, webServer boots)
  • Browser assertions execute in this PR's CI Validate job (first real run)
  • Post-deploy E2E (existing vitest HTTP suite) unaffected

https://claude.ai/code/session_018WA3SvALW1EmxJKVmRUGrR


Generated by Claude Code

Adds the browser-rendering half of the per-field visibility coverage.
The server-side filtering math is unit-tested (visibility.test.ts,
profiles-list.test.ts, profile-by-username.test.ts); this suite asserts
the BROWSER paints the right state given a canned API response —
something the source-regex tests can only approximate.

Approach (per the agreed design): hermetic + mocked API.
  - Serves the production build via `astro preview`; no deploy, no
    Cosmos, no real OAuth.
  - page.route() intercepts /.auth/me, /api/get-roles,
    /api/profile-by-username and /api/profiles with per-test canned
    responses.
  - The SWA `/find/* → /find/profile/index.html` rewrite doesn't exist
    in astro preview, so the suite emulates it: fulfils any
    /find/<username> navigation with the built profile-detail HTML.
    The page's client script then reads window.location.pathname to
    extract the username exactly as it would behind the real rewrite.

Coverage (10 tests):
  detail page — public profile renders all fields; bio stripped →
    hidden (not blank paragraph); skills stripped → section hidden;
    403 → private state + @username; 404 → not-found state + @username;
    500 → error state
  directory — "you're listed" banner when principal matches a row;
    "want to be listed" when not; case-insensitive listed match;
    both banners hidden on empty directory (empty-state owns the CTA)

CI wiring (the gating requirement):
  - Runs as a STEP in the `validate` job (hermetic — nothing to wait
    for post-deploy). `npx playwright install --with-deps chromium`
    then `npm run test:e2e:browser`.
  - dev_deploy already `needs: validate`, so a failing browser test
    now blocks the deploy (CD). Header comment updated to map each
    gate (dependency/security, unit, e2e) to the CD-blocking
    relationship; the merge-blocking half is the existing branch-
    protection "Validate required check" setting.

Plumbing:
  - playwright.config.ts: testDir e2e-browser/, chromium project,
    webServer runs `npm run preview`, CI retries=2.
  - vitest.config.ts: e2e-browser/** added to exclude so the main
    `npm run test:run` doesn't grab the .spec.ts files (they import
    @playwright/test, not vitest).
  - package.json: test:e2e:browser script + @playwright/test devDep.
  - .gitignore: playwright artifact dirs.

Note: the spec derives __dirname from import.meta.url — the repo is
type:module and Playwright's ESM loader (unlike vitest) provides no
__dirname shim. Caught locally before push.

Local verification limit: the sandbox can't download the chromium
binary (network-restricted), so the assertions first truly execute in
CI. Everything up to browser launch is verified locally: config valid,
webServer boots, all 10 tests collected, spec loads clean, and the
full non-browser gate (lint, 485 unit tests, format, build, API
typecheck/build, audits) is green.

https://claude.ai/code/session_018WA3SvALW1EmxJKVmRUGrR

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a hermetic Playwright browser E2E suite to validate visibility-/status-dependent UI rendering, and wires it into CI so it gates the dev deploy (and therefore CD) alongside existing lint/audit/unit/build checks.

Changes:

  • Exclude the new Playwright .spec.ts suite from Vitest collection and add a dedicated Playwright config.
  • Add e2e-browser/visibility.spec.ts with mocked /.auth/me + /api/* routes and an emulated /find/* rewrite for astro preview.
  • Run Playwright (including browser install) in the validate job before deploy, and add Playwright artifacts to .gitignore.

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
vitest.config.ts Excludes e2e-browser/** from Vitest so Playwright specs aren’t executed by vitest run.
playwright.config.ts New Playwright configuration pointing at e2e-browser/ and using astro preview as the webServer.
package.json Adds test:e2e:browser script and @playwright/test dev dependency.
package-lock.json Locks Playwright-related dependencies (@playwright/test, playwright, playwright-core).
e2e-browser/visibility.spec.ts New hermetic browser E2E suite covering profile/detail and directory visibility states via route mocking.
.gitignore Ignores Playwright test artifacts (test-results/, reports, etc.).
.github/workflows/azure-static-web-apps.yml Installs Playwright Chromium and runs the browser suite as part of the validate gate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread e2e-browser/visibility.spec.ts
Comment thread e2e-browser/visibility.spec.ts Outdated
Comment thread package.json Outdated
claude added 2 commits May 26, 2026 12:02
Three polish findings on the Playwright suite:

1. Route handler didn't return the fulfill/continue promise.
   serveDetailPageAtAnyUsername's handler called route.fulfill() /
   route.continue() fire-and-forget. A handler that returns before
   the route resolves is a known Playwright flakiness source. Now
   returns the promise so Playwright awaits it.

2. Raw ENOENT on a fresh/cleaned checkout.
   The spec reads dist/find/profile/index.html at module-load time.
   Without a build, that threw a bare ENOENT during test discovery.
   Wrapped in try/catch with an actionable message pointing at
   `npm run build` / `npm run test:e2e:browser`.

3. test:e2e:browser didn't guarantee the build.
   Script is now `npm run build && playwright test` so a local run
   on a fresh checkout works without a separate build step. In CI the
   build already runs in the validate job's "Build frontend" step, so
   the workflow's e2e step calls `npx playwright test` directly to
   skip the redundant rebuild — documented inline so the CI/local
   divergence is intentional, not accidental.

No assertion changes — the 10 tests passed in CI on the prior commit;
these are robustness + DX improvements.

https://claude.ai/code/session_018WA3SvALW1EmxJKVmRUGrR
The previous push of `3417053` (Copilot polish: route promise return,
ENOENT error message, build-first script) did not produce check runs
on this PR head — a CI registration hiccup, not a code issue. The
prior commit `85db218` had all four checks green including the browser
e2e assertions.

This empty commit nudges the workflow to run again so the polish
commit gets the green pipeline it would otherwise have had.

https://claude.ai/code/session_018WA3SvALW1EmxJKVmRUGrR
@rmjoia rmjoia merged commit 1f1fde9 into main May 29, 2026
4 checks passed
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.

3 participants