Skip to content

feat(ci): daily scheduled Lighthouse CI audit for the frontend#196

Closed
Moonwalker-rgb wants to merge 3 commits into
ChainForgee:mainfrom
Moonwalker-rgb:feat/lighthouse-scheduled-ci
Closed

feat(ci): daily scheduled Lighthouse CI audit for the frontend#196
Moonwalker-rgb wants to merge 3 commits into
ChainForgee:mainfrom
Moonwalker-rgb:feat/lighthouse-scheduled-ci

Conversation

@Moonwalker-rgb

Copy link
Copy Markdown
Contributor

What

Follows up PR #195 by promoting Lighthouse CI from a local-only opt-in to a daily scheduled GitHub Actions workflow. The CI runs @lhci/cli against the production build once a day at 00:30 UTC, gates accessibility and SEO, and publishes the per-route HTML report as a CI artifact so regressions surface within hours rather than at the recipient's keyboard.

This is non-gating for PRs (PR #195 keeps jest-axe as the fast pre-merge gate) but is the canonical source of truth for whole-page a11y + SEO going forward.

Changes

  • .github/workflows/lighthouse.yml (new) — scheduled cron 30 0 * * * (sidesteps the 00:00 queue spike), workflow_dispatch for manual re-runs, concurrency: cancel-in-progress so a slipped schedule doesn't queue stale runs, permissions: contents: read, NEXT_TELEMETRY_DISABLED=1 + NODE_ENV=production, pnpm + actions/cache@v4 with a Node-version-aware key (no stale store restore on Node upgrades), explicit pnpm build before autorun, actions/upload-artifact@v4 for 14-day retention of ./lhci-reports.

  • app/frontend/lighthouserc.json (replaces the placeholder)

    Field Value Why
    startServerCommand pnpm start Boots the prebuilt app
    startServerReadyPattern "Ready in" Substring-matches Next.js 16's ✓ Ready in Nms
    startServerReadyTimeout 60000 (ms) Generous buffer for cold runners
    numberOfRuns 2 Median over noise
    settings.preset desktop Reproducible across runners
    url /, /en/dashboard, /en/help, /en/verification-review All real [locale]/* routes
    categories:accessibility error, ≥ 0.9 WCAG — non-negotiable
    categories:seo error, ≥ 0.9 Public pages must be indexable
    categories:best-practices warn, ≥ 0.8 Mostly about secure context
    categories:performance warn, ≥ 0.5 Not gated — LCP scores flap
    categories:pwa off Not a PWA
    upload.target filesystem./lhci-reports Artifact upload; no external LHCI server
  • app/frontend/LIGHTHOUSE_CI.md (rewritten) — operator playbook: assertion table, audited route list, how to fetch the artifact from a failed run, local-repro recipe with Chrome installed.

  • app/frontend/package.json@lhci/cli ^0.15.1 devDep, pnpm lhci:autorun script.

  • pnpm-lock.yaml — updated for @lhci/cli.

Why a separate workflow

jest-axe (a11y.test.tsx, ~6 tests) is fast and deterministic, runs in jsdom, and is the right gate for PR diffs. Lighthouse is slow, requires a real Chrome + a built bundle + a running server, and is best run against the production output daily. The two are complementary, not redundant.

Caveats documented in LIGHTHOUSE_CI.md

  • GitHub-hosted cron is best-effort — runs can be delayed 5–15 min or skipped under heavy load. Stale badge ≠ regression.
  • The first run after this lands will populate ./lhci-reports and surface any pre-existing a11y/SEO failures; treat them as the new baseline.

Notes for reviewers

  • Auth-gated [locale]/dashboard + verification-review render their shell for unauthenticated visitors (no middleware.ts in this repo). The audit scores what an unauthenticated user actually sees, which is the correct surface for an aid-recipient app.
  • The cache key includes steps.node-version.outputs.node-version which is the documented output of actions/setup-node@v4. The restore-keys fallback is scoped to the same node version so a Node upgrade doesn't poison the fallback with a stale store.

Reviewers: @Lansa-18 @maramina for the workflow + config; @gbengaeben @CodeMayor for any Python-adjacent concerns (this is pure TS/CI, so unlikely).

ChainForgee#134)

Adds three Prometheus instruments updated on every circuit breaker
transition, so unhealthy providers surface in dashboards rather than
from user-visible degradation:

  - Gauge   circuit_breaker_state            (0=CLOSED, 1=HALF_OPEN, 2=OPEN)
  - Counter circuit_breaker_failure_count_total (cumulative failures)
  - Histogram circuit_breaker_recovery_time_seconds (OPEN -> HALF_OPEN lag)

Metrics are emitted inside the same lock that guards state, so the
exported values can never diverge from the underlying state. Failure
count tracks cumulative failures since the last reset, matching the
Prometheus counter contract documented in the issue. The initial state
is published in __init__ so every instantiated breaker appears in the
gauge even before any traffic flows.
…hainForgee#132)

Introduces jest-axe as a first-class test dependency and wires it into
the unit-test runner via a shared @jest-environment jsdom suite, so any
new a11y regression surfaces in CI rather than in production.

Adds:
  - jest-axe + axe-core devDeps, jest-axe ambient types
  - jest.setup.a11y.ts registering toHaveNoViolations as a matcher
  - jest.config.ts setupFilesAfterEnv wiring
  - a11yTestUtils + a11y-mocks helper modules
  - a11y.test.tsx covering ErrorInline (banner + card), ActivityCenter,
    Navbar, ErrorBoundary, EvidenceArtifactViewer
  - frontend-ci.yml running type-check, lint, jest on app/frontend/**
  - lighthouserc.json + LIGHTHOUSE_CI.md (manual opt-in audit config,
    not gating CI per issue scope)

Fixes uncovered violations:
  - icon-only close/remove buttons get aria-label + aria-hidden=true
    on the inner SVG (ErrorInline, ActivityCenter)
  - notification-count badge gains sr-only text in ActivityCenter
  - EvidenceArtifactViewer filename heading promoted from <h3> to <h2>
    to satisfy heading-order axe rule against pages that provide <h1>
Adds a scheduled GitHub Actions workflow that runs @lhci/cli against the
production build once a day at 00:30 UTC, surfaces accessibility and SEO
regressions early, and exposes the per-route HTML report via the
`lighthouse-reports` artifact.

Replaces the placeholder lighthouserc.json (one URL, no server-readiness
guardrails) with a production-ready config:
  * startServerReadyPattern "Ready in" + 60s timeout (avoid the next start race)
  * desktop preset, 2 runs/URL for noise, 4 [locale] routes
  * categories:accessibility + categories:seo gated as errors >= 0.9
  * categories:best-practices + categories:performance warn-only
  * filesystem upload to ./lhci-reports

Other changes:
  - .github/workflows/lighthouse.yml (cron 30 0 * * *, workflow_dispatch,
    concurrency cancel-in-progress, permissions contents: read,
    pnpm + actions/cache, build -> autorun -> upload-artifact v4 retention
    14 days)
  - app/frontend/LIGHTHOUSE_CI.md rewritten with the new operator playbook
    (cron best-effort note, audited routes, artifact retrieval)
  - @lhci/cli ^0.15.1 devDep + `pnpm lhci:autorun` script
  - pnpm-lock.yaml updated accordingly

Follows up PR ChainForgee#195 which left the Lighthouse gate out-of-band pending this
integration. The new workflow is non-gating for PRs (jest-axe unit tests
on PR ChainForgee#195 remain the fast pre-merge gate).

Copy link
Copy Markdown
Contributor Author

cc @Lansa-18 @maramina — frontend reviewers. Specifically curious about: (1) whether the four [locale]/* audit URLs match how you think about smoke coverage, (2) whether categories:performance should stay warn-only or get a tighter floor once we have a baseline, and (3) whether the lighthouse-reports artifact retention of 14 days is enough for trend review.

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