Testing strategy
Choose evidence from the behavior changed, then run the narrowest Bazel target that proves it. Use the local-validation stack for integrated browser/API behavior and scripts/test-local.sh for repository-owned batteries. Do not bypass Bazel with direct Gradle, Maven, npm, or pnpm test commands.
Select targets before running them
Ask Bazel for the owning tests:
bazel query 'tests(//apps/microservices/merchant-api:all)'
bazel query 'tests(//apps/websites/portals/retail:all)'
Run exact labels when known:
bazel test //apps/microservices/merchant-api:core_test
bazel test //apps/websites/portals/retail:test
bazel test //apps/websites/portals/retail:lint
Use --build_tests_only when a wildcard can expand to build targets as well as tests:
bazel test --build_tests_only //apps/microservices/...
bazel test --build_tests_only //apps/websites/...
A manually tagged test is intentionally absent from normal broad lanes. Find it, read its prerequisites, and invoke its exact label only when the required service, device, credentials, or host is available:
bazel query 'attr("tags", "manual", tests(//apps/microservices/...))'
Do not interpret a skipped manual or external gate as a pass.
Repository test batteries
scripts/test-local.sh is the public local test runner. Every mode writes a Markdown report under .dev-logs/test-local/reports/ and per-step logs under .dev-logs/test-local/.
| Command | Purpose |
|---|---|
./scripts/test-local.sh doctor | Validate Gateway Maven credentials and availability of Bazel, Docker, Python, and Google Cloud CLI. |
./scripts/test-local.sh quick | Run broad Bazel tests across scripts, libraries, specifications, services, websites, and static iOS/mobile lanes. |
./scripts/test-local.sh contracts | Run contract drift, local proxy, docs render, PCI redaction, and QA harness tests. |
./scripts/test-local.sh local | Run seed/boundary checks plus microservice and website component tests. It does not start the stack. |
./scripts/test-local.sh no-heavy | Run static/syntax/Python contract checks without Bazel, Docker Compose, Playwright, Android tooling, live databases, or Gateway Maven credentials. |
./scripts/test-local.sh external-gates | Record availability/results for device, Apple, real-provider, Cloudflare/Cloud Run, App Check, delivery, and store-console evidence. |
./scripts/test-local.sh full | Run doctor, quick, contracts, local, and external-gates in sequence. |
./scripts/test-local.sh status | Show local-validation stack status. |
no-heavy is a constrained static battery, not proof that the repository compiles. external-gates is evidence collection; unavailable gates remain unavailable. For a one-command runtime boot and deep local smoke, use ./scripts/local-validation.sh full instead.
Local integrated behavior
Start the stack before an integrated browser/API smoke:
./scripts/local-validation.sh up
./scripts/local-validation.sh smoke all
Use a focused scope for a focused change:
./scripts/local-validation.sh smoke auth
./scripts/local-validation.sh smoke purchasing
./scripts/local-validation.sh smoke browser
./scripts/local-validation.sh smoke terminal-lifecycle
The smoke command owns its browser runner, seeded credentials, App Check token, and provider stubs. Do not replace it with a direct Playwright command as release evidence. See Local development and manual QA for startup, ports, and debugging.
Remote execution and host-specific work
Plain developer Bazel commands use the BuildBuddy remote cache and prefer BuildBuddy remote execution with local fallback. This is the normal mode for JVM, web, service, and other Linux-compatible targets.
Use the explicit local escape hatch only when the target requires local host state or a locally retained artifact:
bazel test --config=local //path/to/package:target_test
Apple work uses a macOS host and local execution:
bazel test --config=macos //apps/ios:peak_pos_client_lane_test
Android requires an Android platform configuration. The wrapper isolates Android's platform/NDK analysis state from the repository's default output base:
scripts/bazel-android.sh build //apps/android:android_app_local_staging
BAZEL_ANDROID_CONFIG=android_x86_64 scripts/bazel-android.sh test //apps/android/app/src/androidTest:android_app_launch_smoke_contract_test
A target that launches a device also requires the expected emulator or physical device. A successful APK build proves compilation/package creation, not installation, hardware behavior, or payment movement.
CI boundaries
CI is split by platform and deployment owner rather than implemented as one interchangeable local command:
.github/workflows/bazel-build.ymltests non-deploy targets with--config=ci --config=rbeand excludes services, websites, Android, and the private-docs build because those have dedicated lanes..github/workflows/container-images-deploy.ymlqueries non-manual microservice tests and runs them before image publication/deployment..github/workflows/cloudflare-workers.ymlbuilds and tests website artifacts before the staging or production deployment job..github/workflows/android.ymlbuilds/tests Android using Bazel's default ARM64 Android platform and explicitly excludesmanualandrequires-android-devicetests from its regular unit-test query..github/workflows/docs.ymlbuilds//docs/private-docs:buildlocally with network-capable execution and remote/disk caches disabled.
Use the same focused target locally to debug a failure. Use --config=ci --config=rbe only when the failure is specific to the CI environment or remote executor; the developer default already exercises remote execution with an outage-safe fallback.
Evidence expectations
For every changed behavior, record:
- the exact command and Bazel label or smoke scope;
- whether it ran locally, on BuildBuddy, in local validation, in staging, or on a device;
- the final pass/fail/skip result;
- the BuildBuddy invocation URL or
.dev-logsreport path when emitted; - any required gate that was unavailable, without relabeling it as passing.
Use these minimum combinations:
| Change | Required proof |
|---|---|
| Backend logic or API | Focused service test; contract test when the public shape changed; local smoke for the affected routed flow. |
| Portal behavior | Portal component test and lint target; local browser smoke for the changed user flow. |
| Android behavior | Focused JVM test and configured app build; device smoke when behavior depends on Android/runtime hardware. |
| Apple behavior | Static lane test where applicable and Mac/Xcode build or simulator evidence for native behavior. |
| Bazel/config/tooling | Owning target plus one representative consumer. |
| Documentation | docs-index, docs-render, and //docs/private-docs:build. |
Do not substitute a broad green run for a missing behavior-specific check, and do not run a production or destructive operation to manufacture test evidence.