Skip to content

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.

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:

Terminal window
pnpm install
pnpm dev:docker:up
pnpm test:e2e:prepare
pnpm -C apps/wavemap-front-end exec playwright install chromium

Run pnpm -C apps/wavemap-front-end dev in one shell. Once http://localhost:3000 is serving, run:

Terminal window
pnpm -C apps/wavemap-front-end test:e2e:smoke

For the built-runtime posture used by CI:

Terminal window
pnpm -C packages/i18n build-and-validate:i18n-assets
pnpm -C apps/wavemap-front-end build
PLAYWRIGHT_FRONTEND_RUNTIME=build pnpm -C apps/wavemap-front-end start

Then, from another shell:

Terminal window
pnpm -C apps/wavemap-front-end test:e2e:smoke -- --project=chromium

The 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.

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:

Terminal window
pnpm test:e2e:prepare
pnpm test:e2e:verify-source-runtime

The 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.

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.

JobRuns whenPrimary proof
workflow_contractsAlways.Pinned/allowlisted Actions, actionlint semantics, manual-delivery and trust-order contracts.
classify_assurance_changesTrusted runs.Exact changed paths mapped to database owners, browser jobs, docs, and Docker targets.
verify_tooling_and_codeTrusted runs.Formatting, lint, typecheck, and shared tool-config integration.
docs_buildClassifier says docs can be affected.Disclosure audit, static build, required routes/artifacts, all internal links.
i18n_verificationTrusted runs.Locale/source/ICU contracts and generated static assets.
i18n_pseudolocalization_verificationClassifier says rendered localization can be affected.Built frontend plus focused Playwright rendered-string audit.
ordinary_testsTrusted runs.Exact Turbo graph across ten hermetic test owners, including data-access and the public API.
backend_db_backed_testsAt least one database owner is affected.Selected API groups, sequentially on PostgreSQL; LocalStack only for selected S3 proof.
fe_e2e_smokeBrowser or its main-API dependency is affected.Built frontend, Docker backend with mocked media, and focused Chromium smoke.
docker_buildsClassifier says images can be affected.Selected deployment/dev Bake targets with bounded BuildKit concurrency.
verify_pull_request_base_freshnessPull 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.

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 BoundaryMain Database GroupsPublic Database Groups
Main auth/user handlersAuth, Users, Presets.None.
Main Artist handlersArtists, Events, Dashboard.Artists, Events, Series, Relationships, Runtime.
Main Venue handlersVenues, Events, Series, Dashboard.Venues, Events, Series, Relationships, Runtime.
Main Event handlersArtists, Venues, Events, Series, Dashboard.Events, Series, Relationships, Runtime.
Main Series/Edition handlersEvents, Series, Dashboard.Events, Series, Relationships, Runtime.
Main Dashboard handlersDashboard.None.
Main preset handlersPresets, Users, Dashboard.None.
Public Artist/Venue/Event/Series handlers, routes and operation metadataNone.That domain plus Relationships and Runtime.
Public Venue adapterNone.Venues, Events, Series, Relationships, Runtime.
Existing DB test fileIts declared group only.Its declared group only.
Shared persistence, data-access, contracts, schema, migrations, seeds, fixtures or unknown dependencyAll affected owners; ambiguous boundaries select both.All affected owners; ambiguous boundaries select both.
Frontend, curated docs or planning onlyNone.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:

Terminal window
bash bin/ci/classify-ci-assurance-paths.sh apps/wavemap-public-api/src/app.ts
bash bin/ci/classify-ci-assurance-paths.sh apps/wavemap-front-end/src/app/page.tsx

The 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.

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.

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.

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.

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.

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.

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.

Follow the row matching the failure instead of reading the workflow top to bottom:

FailureOrchestrationCommand/graph ownerDeeper source/test owner
Workflow syntax or trust.github/workflows/ci.ymlbin/verify-github-action-references.shpackages/cli/src/wavemap-cli/__tests__/ci-dormant-assurance-contract.test.ts and actionlint.
Wrong lane selectedclassify_assurance_changes jobbin/ci/classify-ci-assurance-paths.shclassifier cases in ci-dormant-assurance-contract.test.ts.
Tooling/codeverify_tooling_and_code jobroot package.json, turbo.jsonshared ESLint/TypeScript/Stylelint packages and consumer configs.
Docsdocs_build jobapps/wavemap-docs/package.json, apps/wavemap-docs/bin/audit-public-docs.mjs, apps/wavemap-docs/bin/smoke-docs-build.mjsapps/wavemap-docs/astro.config.mjs and curated content.
i18n sourcei18n_verification jobpackages/i18n/scripts/i18nlocale sheets, namespace registry, i18n tests.
Rendered i18npseudolocalization jobroot verify:i18n-pseudolocalization script and frontend suite runnerapps/wavemap-front-end/test/e2e/i18n/pseudolocalization-rendered-html.e2e.test.ts.
Hermetic testsordinary_tests jobpnpm check:tests, turbo.jsonowning package test:ci script and test files.
Database-backedbackend_db_backed_tests jobroot check:db-backed, bin/ci/run-database-assurance.sh, API-local Vitest configsapps/wavemap-back-end/test/db-backed and database safety/reset source, and apps/wavemap-public-api/test/database.
Browserfe_e2e_smoke jobapps/wavemap-front-end/playwright.config.ts, apps/wavemap-front-end/test/e2e/scripts/runPlaywrightSuite.jsfocused smoke specs, app runtime, and Docker backend.
Dockerdocker_builds jobbin/dev-docker/verify-docker-builds.sh, root docker-bake.verify.hclapp Dockerfiles and deployment build-input contracts.
Base freshnessfinal freshness jobworkflow shell checkPR 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.

Choose checks from affected ownership boundaries:

Terminal window
# Curated docs
pnpm -C apps/wavemap-docs smoke
# Workflow syntax, pins, and trust contracts
bash bin/verify-github-action-references.sh
pnpm exec prettier --check .github
# Exact ordinary test graph
pnpm check:tests
# Complete selected disposable-service API inventories
pnpm check:db-backed
# Playwright package/import discovery
pnpm test:e2e:verify-source-runtime
# Image build graph (only for Docker-relevant changes)
pnpm verify:docker-builds

Use Testing Overview to choose suite depth and Testing Authoring Patterns when adding a regression test.

CI uses disposable services and never touches the shared Development environment. Manual application delivery and operator recipes own deployed smoke:

SmokeDefault ownerData/runtime consequence
Endpoint and browser routingApplication-only delivery.Non-destructive; validates the selected runtime.
Database status/migrateOperator profile.Read-only status or explicit schema mutation.
Reset, seeded, and browserDestructive operator profile.Replaces disposable Development data.
Media and discrepancyMedia profile.Temporary live object mutation plus consistency readback.
Wake and cold startRecovery/lifecycle profile.May start or deliberately stop the shared host.
Docs public smokeManual 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.