This package serves two roles for the Realworld Angular frontend:
- LHCI server — a hosted Lighthouse CI server that stores audit history (PostgreSQL on Railway).
- LHCI client config — collection settings that audit realworld-angular across its main routes, with separate runs for guest, customer, and admin sessions.
The Angular app lives in ../realworld-angular. This folder only contains the server and the CI configuration that drives audits against production (or another environment via env vars).
pnpm install
pnpm lhci:autorun # resolve URLs, collect, uploadOr step by step:
pnpm lhci:prepare # fetch API + write .lhci/urls.json
pnpm lhci:collect
pnpm lhci:upload # requires LHCI_BUILD_TOKENlhci:prepare is required because lhci autorun loads lighthouserc.cjs synchronously — it cannot use an async config export and would otherwise look for a local dist/ folder. URL list is written to .lhci/urls.json first.
@lhci/cli is a dev dependency; the server runtime uses @lhci/server (see index.js).
Workflow: .github/workflows/lhci.yml
Triggered manually from Actions → Lighthouse CI → Run workflow (workflow_dispatch).
| Input | Default | Description |
|---|---|---|
report_name |
— (required) | Build label on the LHCI server (LHCI_BUILD_CONTEXT__COMMIT_MESSAGE), e.g. v1.2.0 regression or pre-deploy 2026-05-31 |
number_of_runs |
3 |
Passed to LHCI_NUMBER_OF_RUNS |
upload |
true |
pnpm lhci:autorun when on; pnpm lhci:collect only when off |
The workflow also sets LHCI_BUILD_CONTEXT__CURRENT_HASH (unique per Actions run), LHCI_BUILD_CONTEXT__AUTHOR (GitHub actor), and LHCI_BUILD_CONTEXT__EXTERNAL_BUILD_URL (link back to the Actions run) when uploading.
Locally, use the same variables before pnpm lhci:autorun:
export LHCI_BUILD_CONTEXT__COMMIT_MESSAGE="My audit label"
pnpm lhci:autorunRepository secret (required when upload is enabled):
| Secret | Description |
|---|---|
LHCI_BUILD_TOKEN |
Build token from the LHCI server project settings |
The job audits production (www.realworldangular.org / api.realworldangular.org) using the seeded demo accounts documented below. Expect roughly 1–2 hours for a full run (18 URLs × multiple Lighthouse runs each).
GitHub runners do not include Chrome by default. The workflow installs Chromium via Puppeteer (puppeteer devDependency + puppeteer browsers install chrome) and sets CHROME_PATH for Lighthouse and the auth script.
pnpm 10+ blocks install scripts by default. This repo allowlists Puppeteer in pnpm-workspace.yaml (allowBuilds.puppeteer: true), which avoids ERR_PNPM_IGNORED_BUILDS in CI. If you add other native deps later, run pnpm approve-builds and commit the updated file.
Locally, after pnpm install, run once:
pnpm exec puppeteer browsers install chrome
export CHROME_PATH="$(pnpm exec node -p "require('puppeteer').executablePath()")"Audits are driven by lighthouserc.cjs and the lhci/ helpers. The flow is:
flowchart LR
A[lighthouserc.cjs] --> B[resolve-urls.cjs]
B --> C[18 URLs]
A --> D[LHCI collect]
D --> E[puppeteer-auth.js per URL]
E --> F[Lighthouse audit]
F --> G[upload to LHCI server]
Every path to audit is listed once with a persona (who must be logged in before Lighthouse runs):
| Persona | Default account | Role |
|---|---|---|
guest |
— | Unauthenticated |
customer |
client@pizza.dev |
CUSTOMER |
admin |
admin@pizza.dev |
PIZZERIA_ADMIN |
Some routes also declare setup: 'cart' (see below).
Paths with {pizzeriaId} or {orderId} are templates; they are expanded when URLs are built.
Before collection starts, pnpm lhci:prepare runs buildUrls() and writes .lhci/urls.json. Then lighthouserc.cjs loads that list. The resolver:
- Fetches the public pizzeria list from the API and picks the first pizzeria that has at least one pizza (for
/pizzerias/{pizzeriaId}). - Uses the stable seeded order id
seed-order-deliveredfor/orders/{orderId}(owned by the demo customer in production seed data). - Prefixes each path with
LHCI_BASE_URL(defaulthttps://www.realworldangular.org).
This yields 18 URLs per collect run.
LHCI runs puppeteer-auth.js before each URL. The script:
- Parses the target pathname and looks up persona/setup in
routes.cjs. - Opens the app origin so API calls share the correct cookie domain.
- Guest — calls
POST /api/auth/logoutso no session cookie is sent. - Customer / admin — calls
POST /api/auth/loginwith credentials fromlhci/constants.cjs(seeded demo users; overridable via env). - Cart setup (
setup: 'cart') — after login, opens a pizzeria detail page, adds one pizza via the UI (order modal), so/checkout/*routes pass thecartNotEmptyGuard.
LHCI invokes the script as async (browser, context) => … — create a page with browser.newPage(), then page.close() when done so Lighthouse can open its own tab. Auth cookies persist on the shared browser (disableStorageReset: true).
Auth uses the real API (LHCI_API_BASE_URL, default https://api.realworldangular.org) with credentials: 'include', matching how the Angular app stores the access_token cookie.
- Runs per URL:
LHCI_NUMBER_OF_RUNS(default3) — median scores are computed across runs. - Preset:
desktop - Skipped audit:
bf-cache(often flaky in automated runs) - Upload: results go to the LHCI server (
LHCI_SERVER_BASE_URL+LHCI_BUILD_TOKEN)
| Path | Notes |
|---|---|
/pizzerias |
Pizzeria list |
/pizzerias/{pizzeriaId} |
Detail + menu |
/cart |
Empty cart |
/terms-and-conditions |
Legal |
/unauthorized |
403 page |
/lhci-not-found |
404 (intentional bad path) |
/auth/login |
Login form |
/auth/register |
Customer registration |
/auth/register-pizzeria |
Owner registration |
| Path | Notes |
|---|---|
/profile |
Account |
/orders |
Order list |
/orders/seed-order-delivered |
Order detail |
/checkout/delivery |
Cart seeded in Puppeteer |
/checkout/schedule |
Cart seeded; may redirect if delivery step incomplete |
/checkout/review |
Cart seeded; may redirect if prior steps incomplete |
| Path | Notes |
|---|---|
/pizzerias/admin/pizzas |
Menu management |
/pizzerias/admin/configuration |
Pizzeria settings |
/orders/admin |
Shop orders |
| Path | Reason |
|---|---|
/ |
Role-based redirect only |
/pizzerias/admin/new |
Demo admin already owns a pizzeria (noPizzeriaGuard) |
/invite/:token |
Not implemented in current Angular routes |
To add or remove routes, edit lhci/routes.cjs. If you add placeholders, resolve them in lhci/resolve-urls.cjs.
| Variable | Default | Purpose |
|---|---|---|
LHCI_BASE_URL |
https://www.realworldangular.org |
Frontend origin |
LHCI_API_BASE_URL |
https://api.realworldangular.org |
API for login/logout |
LHCI_CUSTOMER_EMAIL |
client@pizza.dev |
Customer login |
LHCI_CUSTOMER_PASSWORD |
password123 |
Customer login |
LHCI_ADMIN_EMAIL |
admin@pizza.dev |
Admin login |
LHCI_ADMIN_PASSWORD |
password123 |
Admin login |
LHCI_NUMBER_OF_RUNS |
3 |
Lighthouse runs per URL |
LHCI_SERVER_BASE_URL |
Railway production URL | Upload target |
LHCI_BUILD_TOKEN |
— | Required for upload (from LHCI server project) |
LHCI_BUILD_CONTEXT__COMMIT_MESSAGE |
(from git / CI) | Build label in the LHCI server UI |
LHCI_BUILD_CONTEXT__CURRENT_HASH |
(from git / CI) | Unique build hash; CI uses run_id-run_attempt |
LHCI_BUILD_CONTEXT__AUTHOR |
(from git / CI) | Shown on the build |
LHCI_BUILD_CONTEXT__EXTERNAL_BUILD_URL |
(optional) | Link from the build to your CI job |
Server deployment (index.js) uses PORT and DATABASE_URL for PostgreSQL storage.
realworld-angular-lhci-server/
├── index.js # LHCI server (Railway)
├── lighthouserc.cjs # LHCI client config (async URL list)
├── lhci/
│ ├── constants.cjs # Base URLs + demo credentials
│ ├── routes.cjs # Route catalog + persona mapping
│ ├── prepare-config.cjs # Write .lhci/urls.json (run before autorun)
│ ├── resolve-urls.cjs # Build full URL list for collect
│ └── puppeteer-auth.js # Pre-audit login / cart setup
└── package.json
Credentials match the API seed data (pnpm run db:seed in realworld-angular-api):
client@pizza.dev— customer with demo orders includingseed-order-deliveredadmin@pizza.dev— pizzeria admin with an existing shop
Password for both: password123.
Audits target production by default; ensure seed users exist on the environment you point at, or override the LHCI_*_EMAIL / LHCI_*_PASSWORD variables.