Testing¶
Last reviewed: 2026-07-30
This page explains what each test layer proves and where the remaining test-documentation risk sits.
Test Layers¶
| Layer | Command | Proves |
|---|---|---|
| Laravel PHPUnit | vendor/bin/phpunit --testsuite=Laravel,LaravelMigrated --colors=always |
Backend routes, controllers, services, tenant boundaries, auth, money paths, and migrations. |
| PHPStan / Larastan | vendor/bin/phpstan analyse --no-progress --memory-limit=512M --error-format=github |
Static-analysis regressions beyond the configured baseline. |
| React type check | cd react-frontend && npx tsc --noEmit |
TypeScript correctness for the primary frontend. |
| React build | cd react-frontend && npm run build |
Production build viability. |
| Vitest (local) | cd react-frontend && npm test |
Component, hook, and frontend behavior tests. |
| Vitest (the CI gate) | cd react-frontend && node scripts/run-vitest-shard.mjs --shard 1/8 |
The whole suite minus the quarantine list, split across eight shards. This — not npm test — is what gates a release. See "Frontend quarantine" below. |
| Playwright E2E | npm run test:e2e |
Browser behavior against the React frontend and Laravel API. |
| Events enterprise E2E | npm run test:events:e2e:enterprise |
The destructive five-step create, publication, registration, waitlist, check-in, cancellation, notification, and cleanup lifecycle against an isolated fixture environment. |
| Accessible frontend | npm run build:accessible-frontend, npm run test:accessible-frontend:php, npm run test:accessible-frontend:a11y |
HTML-first frontend build, PHP route behavior, and accessibility smoke coverage. |
| Android native release | cd mobile && npm run verify:release && npm run type-check && npm test -- --runInBand |
OTA/release policy, native configuration contracts, TypeScript, and mobile behavior before Expo prebuild. |
| Documentation | npm run check:docs, npm run check:version, npx markdownlint-cli2, Redocly, strict MkDocs build |
Public-doc hygiene, version/changelog integrity, Markdown structure, OpenAPI validity, and publishable site navigation. |
Frontend quarantine — what a green pipeline proves¶
The eight-shard React Full Suite job has been blocking since 2026-07-28. It
skips the suites listed in react-frontend/src/test/failing-suites.baseline.json,
so a green pipeline currently proves 1,228 of 1,283 suites. Before that job
existed the blocking Vitest steps covered roughly 150 files — about 88% of the
suite could break with a green build, which is why frontend breakage was only ever
discovered in large batches.
The list is a fix-and-remove queue, not a set of exemptions:
- It may only shrink.
react-frontend/scripts/check-quarantine-budget.mjscarries aBASELINEconstant that must be lowered in the same commit as any removal. It runs in theReact Build & Testsjob rather than in the shard job, so the list cannot be grown to turn a red shard green. - A listed path that no longer exists fails the runner, rather than rotting there because a rename quietly excluded it forever.
- A non-gating visibility step runs the quarantined suites on shard 1, so a suite that gets fixed elsewhere is noticed instead of sitting there unrun.
- Verify a fix with
--retry=0. The shard runner passes--retry=1, so a suite can pass by retry rescue; removing one on that evidence puts a flaky suite into the gate. - Record why each entry fails. Entries sharing a root cause get fixed as a group; lumping unrelated failures together is how the queue becomes an exemption list.
Local concurrency differs from CI on purpose¶
react-frontend/vitest.config.ts derives its fork count from
os.availableParallelism(). A developer machine runs test files concurrently
(half its logical cores); CI stays on the original serial settings
(maxForks: 2, fileParallelism: false) because every ci.yml job runs on a
4-vCPU ubuntu-latest runner and the eight-shard gate was stabilised there.
Two consequences worth knowing:
- A suite that depends on file execution order or on shared module state can
pass in one mode and fail in the other. Reproduce serially with
NEXUS_VITEST_MAX_FORKS=1before concluding a test is flaky. - Do not pin
--maxWorkersor--no-file-parallelismin scripts; those flags override the config and reimpose serial execution everywhere.
See LOCAL-PERFORMANCE.md for the measured figures and for the container file-I/O limit that dominates PHP-side timings.
Two ways a test passes locally and fails in CI¶
Both have cost real debugging time, and both are properties of the environment rather than of the test:
$_SERVERis not populated by Laravel's test HTTP kernel. PHP-FPM always setsREQUEST_METHOD; the test kernel dispatches aRequestobject without writing the superglobal, so code reading it directly returns 500 under test only. Read from the request object instead.- Config sourced from a developer
.envis absent in CI. A test that needs a signing key, an API credential, or a feature flag must set it insetUp()rather than inherit it. Reproduce a suspected case by clearing the variable on the command line (FOO= vendor/bin/phpunit <path>) before concluding the test is sound.
E2E Status¶
The Playwright suite combines broad smoke coverage with real journey assertions. CI does not treat a configured zero-test run as green, but some lower-priority specs still contain defensive presence checks; those checks are not substitutes for outcome assertions on release-critical flows.
Before treating E2E as release evidence, prefer tests that assert real outcomes:
- account state changed;
- balances or ledgers changed correctly;
- a message, notification, listing, event, or review persists after reload;
- route protection works for signed-out and cross-tenant users;
- validation errors are visible and keyboard reachable.
The Events enterprise journey is deliberately excluded from the broad Chromium,
Firefox, and mobile projects. Run it only through
npm run test:events:e2e:enterprise; it refuses Project NEXUS production hosts
and requires an explicit opt-in for any other non-loopback fixture target. CI
runs it against a disposable database with CI-local actors, not repository or
environment secrets.
Generated Reports¶
Playwright reports under e2e/reports/, coverage reports, raw PHPStan output, and temporary static-analysis dumps are generated artifacts. Do not commit them as maintained docs. If a one-off report must be retained locally, put it under .local-docs-archive/.
Test Documentation Rules¶
- Keep test instructions near the test harness they describe (
tests/README.md,e2e/README.md,mobile/README.md). - Put platform-wide testing policy here.
- Update this page when a test layer changes meaningfully, especially if a green check no longer proves what this page says it proves.