Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7e01c62
test(web-e2e): add a Playwright smoke suite for every page of the web…
mthines Oct 17, 2026
e37d85f
test(cli): smoke every CLI command and MCP tool, and fail a PR that a…
mthines Oct 17, 2026
dbad003
docs(testing): document the smoke and e2e suites and how to extend them
mthines Oct 17, 2026
23d4e46
ci: add the workflow wiring as a patch for a maintainer to apply
mthines Oct 17, 2026
b4af607
test(web-e2e): write e2e tests as Markdown specs in the ui-verify gra…
mthines Oct 17, 2026
529b8c5
fix(smoke): address the adversarial review of the e2e and smoke suites
mthines Oct 17, 2026
91c8a0d
ci: regenerate the workflow patch — sign-in can only warn, secrets sc…
mthines Oct 17, 2026
28a3cb7
fix(web-e2e): renumber specs when exporting a ui-verify block
mthines Oct 17, 2026
75607e2
fix(web-e2e): recognise an IPv6 loopback Supabase URL as local
mthines Oct 17, 2026
60591ed
chore: simplify pass (mechanical refactors)
mthines Oct 17, 2026
a7a03e2
fix(web-e2e): treat an IPv6 loopback base URL as a local target
mthines Oct 17, 2026
38530b9
docs(testing): read the local service-role key the way CI does
mthines Oct 17, 2026
e923e3b
fix(web-e2e): never record a trace where a secret or production sessi…
mthines Oct 17, 2026
f1f0fe2
docs(testing): run spec:import / spec:export through the package filter
mthines Oct 17, 2026
05268a9
docs(claude): name spec:import / spec:export through the package filter
mthines Oct 17, 2026
5ec143d
ci: name the deploy-time artifact uploads after what they hold
mthines Oct 17, 2026
af351d3
fix(web-e2e): scope the landing header journeys to the Site nav
mthines Oct 17, 2026
11e8366
fix(web-e2e): refuse a network assertion that carries a query string
mthines Oct 17, 2026
c8442e4
docs(web-e2e): point the login journey at its spec by title, not number
mthines Oct 17, 2026
b4b7006
ci: run the e2e and smoke suites on every PR and gate deploys on them
mthines Oct 17, 2026
e71d75a
ci: drop the applied workflow patch
mthines Oct 18, 2026
643537d
fix(web-e2e): stop the spec guard from building the whole website
mthines Oct 18, 2026
27664a4
fix(web-e2e): pass allowed console errors as a fixture tuple
mthines Oct 18, 2026
30d7345
fix(web): stop /onboarding crashing React when it redirects signed in
mthines Oct 18, 2026
f16dc99
fix(web-e2e): find the sidebar's Settings link outside the nav landmark
mthines Oct 18, 2026
026fbf4
fix(web-e2e): tolerate in-flight 401s when the sign-out journey revok…
mthines Oct 18, 2026
8776912
fix(ci): point the production e2e at www.lorekit.io, not the redirect…
mthines Oct 18, 2026
6297f24
chore: simplify pass (mechanical refactors)
mthines Oct 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 125 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ jobs:
# `code` — any non-docs file touched → gates the `check` job.
# `api` — API/backend paths → gates the (expensive) `integration` job.
# `plugin` — Claude-plugin paths → gates the `plugin` smoke job.
# `web` — dashboard paths (broad) → gates the `web-test` Storybook job.
# `web` — dashboard paths (broad) → gates the `web-test` Storybook job
# and the `web-e2e` Playwright job.
# `web_preview` — the narrower, Vercel-quota-metered subset → gates
# `web-preview`. Two filters, not one, because the test job is
# free CI compute and the preview is a metered deployment.
Expand Down Expand Up @@ -114,7 +115,10 @@ jobs:
# is precisely what the visual baselines are for. `ci.yml` is here for
# the same reason as in every sibling gate: so an edit to this job runs
# the job.
if printf '%s\n' "$CHANGED" | grep -qE '^(packages/web/|packages/schemas/|package\.json$|pnpm-lock\.yaml$|nx\.json$|\.github/workflows/ci\.yml$)'; then
#
# `packages/web-e2e/` rides along: a change to the e2e suite itself must
# run the `web-e2e` job that executes it.
if printf '%s\n' "$CHANGED" | grep -qE '^(packages/web/|packages/web-e2e/|packages/schemas/|package\.json$|pnpm-lock\.yaml$|nx\.json$|\.github/workflows/ci\.yml$)'; then
echo "web=true" >> "$GITHUB_OUTPUT"
else
echo "web=false" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -361,6 +365,124 @@ jobs:
BASE: ${{ github.event.pull_request.base.sha }}
run: npx vitest run --config vitest.storybook.config.ts --changed="$BASE" --passWithNoTests

# ── Web e2e — Playwright against a local production build ────────────────────
# The page smoke suite (packages/web-e2e; docs/smoke-tests.md). The site is
# built with NEXT_PUBLIC_USE_LOCAL_SUPABASE=true and a local Supabase stack
# runs behind it, so the job needs NO secrets (fork PRs included) and
# touches nothing real. It proves that every public page renders, that every
# dashboard page sends a signed-out visitor to /login, and — signed in as a
# throwaway user the suite provisions on the local stack — that every
# dashboard page renders. Any uncaught exception, console error or 5xx on
# a page fails its test, not just a missing heading.
#
# The same suite runs again at deploy time: against the staged production
# build before its domain is assigned, and against lorekit.io after
# (deploy.yml `e2e-web-staged` / `smoke-production`).
#
# `npx playwright`, not `pnpm exec` / `nx run`, for the stdio reason given
# on web-test above.
web-e2e:
name: Web — Playwright e2e (local build + local Supabase)
runs-on: ubuntu-latest
needs: [changes]
if: needs.changes.outputs.web == 'true'
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/[email protected]

- name: Set up pnpm
uses: pnpm/action-setup@v6

- name: Set up Node.js
uses: actions/[email protected]
with:
node-version: 20
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Same key as web-test, so both jobs share one browser cache.
- name: Resolve Playwright version
id: pw
working-directory: packages/web-e2e
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.pw.outputs.version }}

- name: Install Playwright Chromium (+ OS deps)
run: pnpm --filter @lorekit/web-e2e exec playwright install --with-deps chromium

- name: Install Supabase CLI
uses: supabase/setup-cli@v3
with:
# Same pin as the integration job — see the reason there.
version: 2.115.0

- name: Start local Supabase (auth + edge functions behind the dashboard)
run: supabase start

- name: Capture local service-role key
id: supabase
run: |
supabase status -o json > /tmp/status.json
KEY="$(jq -r '.SERVICE_ROLE_KEY // .SECRET_KEY // .service_role_key // .Secret // empty' /tmp/status.json)"
if [ -z "$KEY" ]; then
echo "::error::No service-role/secret key in supabase status. Available keys:"
jq -r 'keys[]' /tmp/status.json
exit 1
fi
echo "::add-mask::$KEY"
echo "service_role_key=$KEY" >> "$GITHUB_OUTPUT"

- name: Cache the Next.js build cache
uses: actions/cache@v6
with:
path: packages/web/.next/cache
key: web-e2e-next-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('packages/web/src/**', 'packages/schemas/src/**') }}
restore-keys: |
web-e2e-next-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-

# NEXT_PUBLIC_* values are inlined at build time, so THIS step decides
# that the site talks to the local stack and nothing else.
- name: Build the website (pointed at the local stack)
working-directory: packages/web
env:
NEXT_PUBLIC_USE_LOCAL_SUPABASE: 'true'
NEXT_TELEMETRY_DISABLED: '1'
run: npx next build

# Playwright starts `next start` itself (playwright.config.ts webServer).
# E2E_SUPABASE_* lets the setup project create a throwaway confirmed user
# on the local stack (refused for any non-local URL — src/support/env.ts).
- name: Playwright — every page, signed out and signed in
working-directory: packages/web-e2e
env:
E2E_SUPABASE_URL: http://127.0.0.1:54321
E2E_SUPABASE_SERVICE_ROLE_KEY: ${{ steps.supabase.outputs.service_role_key }}
run: npx playwright test

- name: Upload the Playwright report (traces + screenshots)
if: failure()
uses: actions/upload-artifact@v7
with:
name: web-e2e-report
path: |
packages/web-e2e/playwright-report
packages/web-e2e/test-results
retention-days: 14

- name: Stop local Supabase
if: always()
run: supabase stop --no-backup

# ── Web preview — deploy a Vercel preview, ONLY when web paths changed ────────
# Replaces Vercel's native Git integration for the dashboard project. That
# integration deployed a preview on EVERY PR/branch push — even docs- or
Expand Down Expand Up @@ -924,7 +1046,7 @@ jobs:
# `always()` so the gate still runs (and reports) even when an upstream job
# failed, was cancelled, or was skipped — otherwise it would be skipped too.
if: always()
needs: [changes, check, plugin, integration, web-test, migration-order, deploy-scope, skill-version-guard, seed-tests, preview-filter, bench-tests, edge-typecheck]
needs: [changes, check, plugin, integration, web-test, web-e2e, migration-order, deploy-scope, skill-version-guard, seed-tests, preview-filter, bench-tests, edge-typecheck]
permissions: {}
steps:
- name: Aggregate job results into a pass/fail gate
Expand Down
Loading
Loading