Testing Runtime And CI
This page connects local runtime checks to .github/workflows/ci.yml. Start with the job graph, then follow the source
trail to the package, script, or test owner you need to change.
Browser E2E Runtime Contract
Section titled “Browser E2E Runtime Contract”Local browser debugging normally uses next dev. CI uses a built frontend runtime so the smoke result covers production
build output rather than on-demand compilation.
Prepare local dependencies and the Docker-backed backend:
pnpm installpnpm dev:docker:uppnpm test:e2e:preparepnpm -C apps/wavemap-front-end exec playwright install chromiumRun pnpm -C apps/wavemap-front-end dev in one shell. Once http://localhost:3000 is serving, run:
pnpm -C apps/wavemap-front-end test:e2e:smokeFor the built-runtime posture used by CI:
pnpm -C packages/i18n build-and-validate:i18n-assetspnpm -C apps/wavemap-front-end buildPLAYWRIGHT_FRONTEND_RUNTIME=build pnpm -C apps/wavemap-front-end startThen, from another shell:
pnpm -C apps/wavemap-front-end test:e2e:smoke -- --project=chromiumThe app runtime must already be listening; apps/wavemap-front-end/playwright.config.ts does not own a webServer.
The browser suite also expects the local backend, app environment files, Chromium, and the seeded route named in the
smoke specs.
Why source-runtime preparation exists
Section titled “Why source-runtime preparation exists”Playwright specs are source files, but workspace package exports resolve through built dist outputs. A clean checkout
can therefore fail during test discovery before a browser opens. Use:
pnpm test:e2e:preparepnpm test:e2e:verify-source-runtimeThe first command builds required workspace outputs. The second prepares them and asks Playwright to discover specs in built-runtime mode. It is a cheap import-boundary check, not a browser smoke replacement.
CI Mental Model
Section titled “CI Mental Model”continuous_integration runs for pull requests targeting develop or main, pushes to develop, and manual dispatch.
Pull-request CI proves a proposed merge. Push CI proves the exact merged develop SHA and is the evidence required by a
later, manually dispatched application delivery.
Event-scoped concurrency cancels stale work for the same pull request or branch without allowing a manual run to cancel merged-state verification. Dependabot GitHub Actions proposals run only the credential-free workflow-contract lane; trusted jobs use the repository’s private package boundary only for human pull requests and merged source.
CI never deploys Wavemap and never dispatches CD.
Current Job Graph
Section titled “Current Job Graph”| Job | Runs when | Primary proof |
|---|---|---|
workflow_contracts | Always. | Pinned/allowlisted Actions, actionlint semantics, manual-delivery and trust-order contracts. |
classify_assurance_changes | Trusted runs. | Exact changed paths mapped to database owners, browser jobs, docs, and Docker targets. |
verify_tooling_and_code | Trusted runs. | Formatting, lint, typecheck, and shared tool-config integration. |
docs_build | Classifier says docs can be affected. | Disclosure audit, static build, required routes/artifacts, all internal links. |
i18n_verification | Trusted runs. | Locale/source/ICU contracts and generated static assets. |
i18n_pseudolocalization_verification | Classifier says rendered localization can be affected. | Built frontend plus focused Playwright rendered-string audit. |
ordinary_tests | Trusted runs. | Exact Turbo graph across ten hermetic test owners, including data-access and the public API. |
backend_db_backed_tests | At least one database owner is affected. | Selected API groups, sequentially on PostgreSQL; LocalStack only for selected S3 proof. |
fe_e2e_smoke | Browser or its main-API dependency is affected. | Built frontend, Docker backend with mocked media, and focused Chromium smoke. |
docker_builds | Classifier says images can be affected. | Selected deployment/dev Bake targets with bounded BuildKit concurrency. |
verify_pull_request_base_freshness | Pull requests after all relevant jobs settle. | Reviewed base SHA still equals the live target-branch SHA. |
Database assurance, browser smoke, rendered pseudolocalization and Docker image jobs wait for ordinary_tests to succeed before allocating their runners and services. A failed ordinary graph therefore avoids those downstream minutes. This gate trades a longer successful-run critical path for lower failure-path cost; the ordinary graph itself is not assumed to be fast. Docs, static i18n and tooling retain their independent ownership.
Jobs stay separate so failure ownership is visible. Database-backed failures should not look like hermetic unit failures; Docker build cost should not be paid for docs-only paths; and a stale PR base should not hide behind an earlier green run.
Repository-Owned Change Classification
Section titled “Repository-Owned Change Classification”bin/ci/classify-ci-assurance-paths.sh owns the expensive-lane dependency boundaries. CI compares the exact PR base/head or push before/head and passes changed paths into it. Manual dispatch selects full assurance. Outputs include main_groups, public_groups, database_s3, database_harness, backend_db, public_db, database_owner (main, public, both, or none), browser, docs, pseudolocalization, docker, and a stable comma-separated docker_targets list.
| Changed Boundary | Main Database Groups | Public Database Groups |
|---|---|---|
| Main auth/user handlers | Auth, Users, Presets. | None. |
| Main Artist handlers | Artists, Events, Dashboard. | Artists, Events, Series, Relationships, Runtime. |
| Main Venue handlers | Venues, Events, Series, Dashboard. | Venues, Events, Series, Relationships, Runtime. |
| Main Event handlers | Artists, Venues, Events, Series, Dashboard. | Events, Series, Relationships, Runtime. |
| Main Series/Edition handlers | Events, Series, Dashboard. | Events, Series, Relationships, Runtime. |
| Main Dashboard handlers | Dashboard. | None. |
| Main preset handlers | Presets, Users, Dashboard. | None. |
| Public Artist/Venue/Event/Series handlers, routes and operation metadata | None. | That domain plus Relationships and Runtime. |
| Public Venue adapter | None. | Venues, Events, Series, Relationships, Runtime. |
| Existing DB test file | Its declared group only. | Its declared group only. |
| Shared persistence, data-access, contracts, schema, migrations, seeds, fixtures or unknown dependency | All affected owners; ambiguous boundaries select both. | All affected owners; ambiguous boundaries select both. |
| Frontend, curated docs or planning only | None. | None. |
Known main media/upload/sync/delete handlers additionally select the shared Media group and S3 smoke. Public-only source never selects S3. Shared baseline/setup/configuration, CI tooling and full fallback also select the lifecycle harness. The Node selector owns the finer adapter exceptions and defaults unclassified public source to all public groups. It deliberately leaves query/helper/data-access changes broad because these have cross-domain and cross-API consumers.
bin/ci/database-test-inventory.json assigns every database test exactly once. bin/ci/select-database-assurance.mjs validates that inventory against discovered files before package installation or service startup in CI; unassigned, duplicate and stale entries fail. Add new tests to their owning group. Exact selected file lists enter the Vitest configurations through validated environment values; groups do not create additional jobs or per-group seed cycles.
Multiple paths union their affected owners. The classifier writes selected/skipped reasons and image targets to the Actions summary. Shared fixture imports enter both database boundaries explicitly because they are test imports rather than workspace dependency edges. The public image target will join this graph when its deployment boundary is introduced; the current graph owns four main-app targets.
Regression examples and command composition checks live in packages/cli/src/wavemap-cli/__tests__/ci-owner-selection.test.ts. Run the owner directly when changing these boundaries:
bash bin/ci/classify-ci-assurance-paths.sh apps/wavemap-public-api/src/app.tsbash bin/ci/classify-ci-assurance-paths.sh apps/wavemap-front-end/src/app/page.tsxThe ordinary ten-owner cached test graph still runs. Selection skips unnecessary expensive lanes; selected database groups run once per owner, with full assurance available explicitly.
Lane Ownership
Section titled “Lane Ownership”Workflow contracts
Section titled “Workflow contracts”bin/verify-github-action-references.sh rejects unreviewed or floating external Actions, whole-context secret/variable
serialization, automatic application delivery, and selected-source execution before exact merged-state admission.
actionlint then checks workflow semantics. This lane installs no repository packages and receives no private package
credential.
Tooling and ordinary tests
Section titled “Tooling and ordinary tests”pnpm verify:tooling runs formatting, lint, typecheck, and targeted config-resolution checks. pnpm check:tests uses the
Turbo graph to build prerequisites and run every declared hermetic owner. Package scripts and turbo.json are the graph
authority; CI YAML only invokes them.
Docs and localization
Section titled “Docs and localization”pnpm -C apps/wavemap-docs smoke audits curated public source, builds once, verifies required routes/assets, and resolves
every built internal link. pnpm verify:i18n checks locale parity, namespaces, key shape/order, domain-code mappings,
seed wiring, and ICU arguments. The rendered pseudolocalization lane separately proves those transformations in built
HTML and lets the naked-string scanner catch a deterministic probe.
Disposable database and object services
Section titled “Disposable database and object services”backend_db_backed_tests starts one disposable PostgreSQL service only when at least one API owner is selected. LocalStack has an empty service image when S3 proof is not selected, so no S3 emulator container or bucket bootstrap runs. GitHub documents this conditional service behavior.
pnpm check:db-backed delegates to bin/ci/run-database-assurance.sh. It defaults to both owners locally and accepts -- --owner main|public|both|none. Without group flags, the command runs complete selected-owner inventories, main S3 smoke when applicable, then the lifecycle harness. CI additionally supplies --main-groups, --public-groups, --s3 and --harness. Group values are all, none or unique comma-separated names; service flags are true or false. Each selected regular inventory runs in one Vitest invocation. For example, pnpm check:db-backed -- --owner main --main-groups auth,users,presets --s3 false --harness false runs the private auth boundary. When both are selected, the owners run sequentially against fresh baselines. none invokes no package command. The caller supplies the selected services; this script never starts them or accesses Development.
The database task step has a 30-minute limit inside the 35-minute job, leaving diagnostic headroom. The existing pinned upload action retains streamed task logs, elapsed task records and Turbo prerequisite summaries for three days, including on failure. DATABASE_ASSURANCE_REPORT_DIR selects the local report directory; without it, the runner creates a temporary directory and prints its path.
Turbo builds prerequisites and caches those builds; database tasks themselves are uncached. The public task depends on its own executable build and the i18n output needed by seeding. The main tasks depend on their workspace builds. A dedicated database-assurance Actions cache persists prerequisite outputs between runs.
Baseline creation is owned by apps/wavemap-back-end/test/db-backed/support/baseline.ts, shared by the main reseed helper, public global setup and explicit disposable reset command. Pure environment defaults live alongside it. Public tests retain their restricted-role grants and cleanup. The shared reset restores the database-wide TEMP grant revoked by public fixtures, rejects active reader-role connections, and requires the exact disposable identity and test reset context. Set WAVEMAP_DB_TEST_TIMINGS=1 for reset, migration and seed-stage timings. Main run-mode tests prepare one invocation-owned PostgreSQL template and restore it between isolated files. Same-file resets and dedicated migration/seed tests still run the complete recipe; watch mode and the single-file S3 smoke retain full resets. Public tests prepare once per invocation and retain their existing per-scenario cleanup. Set WAVEMAP_DB_BASELINE_MODE=full to force the original main preparation policy.
The shared test coordinator holds a session lock for the complete invocation and serializes resets, template creation and restoration through a separate lock. Restores refuse active or idle target clients instead of terminating them, and the main runner closes its pool after each file. Guards allow up to five seconds for autovacuum alone to finish, recheck all backends on every poll and admit work only when no blocking sessions remain. Clients and unknown backend types still fail immediately. CI probes the maintenance database so health checks do not race test-database replacement. A durable marker prevents automatic takeover after owner/session loss or an interrupted destructive operation, including seed children that outlive a worker. If the runner reports that recovery is required, stop the failed invocation and its descendants, then recreate the disposable PostgreSQL container before retrying. Do not remove its marker while processes may still be running. This recovery applies only to the exact test service on port 55433; it is not a Development reset procedure.
These services are disposable test fixtures. They are not Development backups, staging substitutes, or evidence that the long-lived Development database has a recovery contract. Run local database owners sequentially; do not share the target between concurrent invocations.
Browser smoke
Section titled “Browser smoke”The browser job prepares package outputs, materializes ephemeral runner environment files, builds static i18n assets, starts the Docker backend and built frontend, waits for readiness, runs the focused Chromium suite, uploads Playwright artifacts on failure, and always tears down its processes. Its Azurite credential is the public emulator value owned by the checked-in Compose file, not a GitHub secret.
Docker builds
Section titled “Docker builds”The Docker lane passes the classifier’s target list to bin/dev-docker/verify-docker-builds.sh --cache-backend gha --targets target,.... Target validation runs before BuildKit setup or credential exposure. Unknown, empty and duplicate targets fail. Only selected targets enter Bake and receive target-scoped cache import/export arguments; frontend targets require the private npm read token.
The default local command builds five targets, including public-api-deploy. Public-app changes select that image without main images or a private npm token; shared data-access changes also select it. The separate private-npm proof group retains its original four targets. --validate-only checks selection without Docker or credentials. Builds use the existing digest-pinned BuildKit runtime and two-operation concurrency bound. Local verification retains its persistent repository-scoped builder and does not load verifier images into Docker Desktop. Run actual image builds when image inputs change; shell-selection edits can first use focused command-composition tests and Bake graph inspection.
Source Trail For A Failing Job
Section titled “Source Trail For A Failing Job”Follow the row matching the failure instead of reading the workflow top to bottom:
| Failure | Orchestration | Command/graph owner | Deeper source/test owner |
|---|---|---|---|
| Workflow syntax or trust | .github/workflows/ci.yml | bin/verify-github-action-references.sh | packages/cli/src/wavemap-cli/__tests__/ci-dormant-assurance-contract.test.ts and actionlint. |
| Wrong lane selected | classify_assurance_changes job | bin/ci/classify-ci-assurance-paths.sh | classifier cases in ci-dormant-assurance-contract.test.ts. |
| Tooling/code | verify_tooling_and_code job | root package.json, turbo.json | shared ESLint/TypeScript/Stylelint packages and consumer configs. |
| Docs | docs_build job | apps/wavemap-docs/package.json, apps/wavemap-docs/bin/audit-public-docs.mjs, apps/wavemap-docs/bin/smoke-docs-build.mjs | apps/wavemap-docs/astro.config.mjs and curated content. |
| i18n source | i18n_verification job | packages/i18n/scripts/i18n | locale sheets, namespace registry, i18n tests. |
| Rendered i18n | pseudolocalization job | root verify:i18n-pseudolocalization script and frontend suite runner | apps/wavemap-front-end/test/e2e/i18n/pseudolocalization-rendered-html.e2e.test.ts. |
| Hermetic tests | ordinary_tests job | pnpm check:tests, turbo.json | owning package test:ci script and test files. |
| Database-backed | backend_db_backed_tests job | root check:db-backed, bin/ci/run-database-assurance.sh, API-local Vitest configs | apps/wavemap-back-end/test/db-backed and database safety/reset source, and apps/wavemap-public-api/test/database. |
| Browser | fe_e2e_smoke job | apps/wavemap-front-end/playwright.config.ts, apps/wavemap-front-end/test/e2e/scripts/runPlaywrightSuite.js | focused smoke specs, app runtime, and Docker backend. |
| Docker | docker_builds job | bin/dev-docker/verify-docker-builds.sh, root docker-bake.verify.hcl | app Dockerfiles and deployment build-input contracts. |
| Base freshness | final freshness job | workflow shell check | PR base/head state in GitHub. |
This trail is also the ownership rule: preserve the workflow as orchestration, keep reusable behavior in repo-owned commands, and put regression assertions beside the owner that can violate the contract.
Proportional Local Verification
Section titled “Proportional Local Verification”Choose checks from affected ownership boundaries:
# Curated docspnpm -C apps/wavemap-docs smoke
# Workflow syntax, pins, and trust contractsbash bin/verify-github-action-references.shpnpm exec prettier --check .github
# Exact ordinary test graphpnpm check:tests
# Complete selected disposable-service API inventoriespnpm check:db-backed
# Playwright package/import discoverypnpm test:e2e:verify-source-runtime
# Image build graph (only for Docker-relevant changes)pnpm verify:docker-buildsUse Testing Overview to choose suite depth and Testing Authoring Patterns when adding a regression test.
Deployed Smoke Is Not CI
Section titled “Deployed Smoke Is Not CI”CI uses disposable services and never touches the shared Development environment. Manual application delivery and operator recipes own deployed smoke:
| Smoke | Default owner | Data/runtime consequence |
|---|---|---|
| Endpoint and browser routing | Application-only delivery. | Non-destructive; validates the selected runtime. |
| Database status/migrate | Operator profile. | Read-only status or explicit schema mutation. |
| Reset, seeded, and browser | Destructive operator profile. | Replaces disposable Development data. |
| Media and discrepancy | Media profile. | Temporary live object mutation plus consistency readback. |
| Wake and cold start | Recovery/lifecycle profile. | May start or deliberately stop the shared host. |
| Docs public smoke | Manual docs publication. | Read-only static-site requests; never wakes the app. |
See Deployment Workflows for admission and authority, and Runbooks before selecting a live or destructive recipe.