- Full testing kit (suites, live, Docker): Testing
- Update and plugin package validation: Testing updates and plugins
Agent default
Agent sessions run one/few focused tests and cheap static checks locally only for trusted source and when the existing dependency install is ready. Never execute untrusted repository tooling locally. Larger suites, changed gates with typecheck/lint fan-out, builds, Docker, package lanes, E2E, live proof, and cross-platform validation run remotely through Crabbox. Trusted maintainer heavy proof defaults to Blacksmith Testbox. The configured Testbox workflow hydrates credentials, so untrusted contributor or fork code must use secretless fork CI or sanitized direct AWS Crabbox instead. Do not pre-warm for anticipated work. Acquire the backend lazily when the first heavy command is ready, reuse the returnedtbx_... id for later heavy
commands, sync the current checkout on every run, and stop it before handoff.
After the first successful reuse, the wrapper records the lease’s base,
dependency, and Testbox workflow fingerprint under .crabbox/testbox-leases/.
Source-only edits keep reusing the warmed box. A changed merge base, lockfile,
package-manager input, wrapper, or Testbox workflow fails closed and requires a
fresh lease. Every run still syncs the current checkout.
OPENCLAW_TESTBOX_ALLOW_STALE=1 is only for intentional diagnostics, not
release proof.
Local test commands below are for human workflows and bounded agent proof.
Remote-provider unavailability must be reported; it is not permission to
silently run a broad local gate.
For untrusted heavy proof, lazily warm with --provider aws. Every run must set
CRABBOX_ENV_ALLOW=CI, pass --provider aws --no-hydrate, and use
a fresh temporary remote HOME before installing dependencies or running
tests. Use a newly warmed lease dedicated to that untrusted source; never reuse
a trusted or previously hydrated lease. Launch an installed trusted Crabbox
binary from a clean trusted main checkout and fetch only the remote PR with
--fresh-pr; never execute the untrusted checkout’s wrapper or config locally.
Unset CRABBOX_AWS_INSTANCE_PROFILE and fail closed unless resolved
aws.instanceProfile is empty. Before any install/test, use trusted
absolute-path tools to require an IMDSv2 token, prove the IAM credentials
endpoint returns 404, and verify remote git rev-parse HEAD equals the full
reviewed PR head SHA. Bind the lease to that SHA and stop/rewarm when the head
changes. Upload trusted scripts/crabbox-untrusted-bootstrap.sh from clean
main alongside --fresh-pr; it installs pinned Node/pnpm, verifies the SHA
and package-manager pin, isolates HOME, installs dependencies, then executes
the requested test. If the broker cannot prove no role or no remote PR exists,
use secretless fork CI. Do not use hydrate-github, --no-sync, or a
credential-hydrated Testbox workflow.
Unset all CRABBOX_TAILSCALE* overrides, force --network public --tailscale=false, clear exit-node/LAN flags, and require crabbox inspect to
report public networking with no Tailscale state before uploading any script.
Routine local order
pnpm test:changedfor changed-scope Vitest proof.pnpm test <path-or-filter>for one file, directory, or explicit target.pnpm testonly when you intentionally need the full local Vitest suite.
pnpm test* / pnpm check* / pnpm crabbox:run:
- Bounded focused proof with ready dependencies:
node scripts/run-vitest.mjs <path-or-filter>. - Classify-first changed check:
node scripts/check-changed.mjs; docs-only, no-change, and small metadata plans stay local when dependencies are ready, while heavy or dependency-missing plans delegate to Testbox. - Explicit kept-lease broad proof:
node scripts/crabbox-wrapper.mjs run --provider blacksmith-testbox ... -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changedso pnpm runs inside Testbox. - The wrapper’s final
exitCodeand timing JSON are the command result. A delegated Blacksmith GitHub Actions run may showcancelledafter a successful SSH command because the Testbox is stopped from outside the keepalive action; check the wrapper summary and command output before treating that as a failure. OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree <local-heavy-check command>: keeps heavy-check serialization inside the current worktree instead of the Git common dir for commands such aspnpm check:changedand targetedpnpm test .... Use it only on high-capacity local hosts when you intentionally run independent checks across linked worktrees.
Core commands
Test wrapper runs end with a short[test] passed|failed|skipped ... in ... summary; Vitest’s own duration line stays the per-shard detail.
Shared test state and process helpers
src/test-utils/openclaw-test-state.ts: use from Vitest when a test needs an isolatedHOME,OPENCLAW_STATE_DIR,OPENCLAW_CONFIG_PATH, config fixture, workspace, agent dir, or auth-profile store.pnpm test:env-mutations:report: non-blocking report of tests/harnesses that mutateHOME,OPENCLAW_STATE_DIR,OPENCLAW_CONFIG_PATH,OPENCLAW_WORKSPACE_DIR, or related env keys directly. Use it to find migration candidates for the shared test-state helper.test/helpers/openclaw-test-instance.ts: process-level E2E tests needing a running Gateway, CLI env, log capture, and cleanup in one place.- Docker/Bash E2E lanes that source
scripts/lib/docker-e2e-image.shcan passdocker_e2e_test_state_shell_b64 <label> <scenario>into the container and decode it withscripts/lib/openclaw-e2e-instance.sh; multi-home scripts can passdocker_e2e_test_state_function_b64and callopenclaw_test_state_create <label> <scenario>in each flow.node scripts/lib/openclaw-test-state.mjs -- create --label <name> --scenario <name> --env-file <path> --jsonwrites a sourceable host env file (the--beforecreatekeeps newer Node runtimes from treating--env-fileas a Node flag). Lanes that launch a Gateway can sourcescripts/lib/openclaw-e2e-instance.shfor entrypoint resolution, mock OpenAI startup, foreground/background launch, readiness probes, state env export, log dumps, and process cleanup.
Control UI, TUI, and extension lanes
- Control UI mocked E2E:
pnpm test:ui:e2eruns the Vitest + Playwright lane that starts the Vite Control UI and drives a real Chromium page against a mocked Gateway WebSocket. Tests live inui/src/**/*.e2e.test.ts; shared mocks/controls live inui/src/test-helpers/control-ui-e2e.ts.pnpm test:e2eincludes this lane. Agent runs default to Testbox/Crabbox, including targeted proof; usenode scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/chat-flow.e2e.test.tsonly for an explicit local fallback. - TUI PTY tests:
node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.tsruns the fast fake-backend PTY lane.OPENCLAW_TUI_PTY_INCLUDE_LOCAL=1orpnpm tui:pty:test:watch --mode localruns the slowertui --localsmoke, which mocks only the external model endpoint. Assert stable visible text or fixture calls, not raw ANSI snapshots. pnpm test:extensionsandpnpm test extensionsrun all extension/plugin shards. Heavy channel plugins, the browser plugin, and OpenAI run as dedicated shards; other plugin groups stay batched.pnpm test extensions/<id>runs one bundled plugin lane.- Source files with sibling tests map to that sibling before falling back to wider directory globs. Helper edits under
src/channels/plugins/contracts/test-helpers,src/plugin-sdk/test-helpers, andsrc/plugins/contractsuse a local import graph to run importing tests instead of broad-running every shard when the dependency path is precise. - Contract directory targets fan out to their contract lanes:
pnpm test src/channels/plugins/contractsruns the four channel contract configs andpnpm test src/plugins/contractsruns the plugin contracts config, since the genericchannels/pluginsprojects excludecontracts/**. auto-replysplits into three dedicated configs (core,top-level,reply) so the reply harness does not dominate the lighter top-level status/token/helper tests.- Selected
plugin-sdkandcommandstest files route through dedicated light lanes that keep onlytest/setup.ts, leaving runtime-heavy cases on their existing lanes. - Base Vitest config defaults to
pool: "threads"andisolate: false, with the shared non-isolated runner enabled across repo configs. pnpm test:channelsrunsvitest.channels.config.ts.
Gateway and E2E
- Gateway integration is opt-in:
OPENCLAW_TEST_INCLUDE_GATEWAY=1 pnpm testorpnpm test:gateway. pnpm test:e2e: repo E2E aggregate =pnpm test:e2e:gateway && pnpm test:ui:e2e.pnpm test:e2e:gateway: gateway end-to-end smoke tests (multi-instance WS/HTTP/node pairing). Defaults tothreads+isolate: falsewith adaptive workers invitest.e2e.config.ts; tune withOPENCLAW_E2E_WORKERS=<n>, verbose logs withOPENCLAW_E2E_VERBOSE=1.pnpm test:live: provider live tests (Claude/Minimax/DeepSeek/z.ai/etc, gated by*.live.test.ts). Requires API keys andLIVE=1(orOPENCLAW_LIVE_TEST=1) to unskip; verbose output withOPENCLAW_LIVE_TEST_QUIET=0.
Full Docker suite (pnpm test:docker:all)
Builds the shared live-test image, packs OpenClaw once as an npm tarball, builds/reuses a bare Node/Git runner image plus a functional image that installs that tarball into /app, then runs Docker smoke lanes through a weighted scheduler. scripts/package-openclaw-for-docker.mjs is the single local/CI package packer and validates the tarball plus dist/postinstall-inventory.json before Docker consumes it.
- Bare image (
OPENCLAW_DOCKER_E2E_BARE_IMAGE): installer/update/plugin-dependency lanes; mounts the prebuilt tarball instead of copied repo sources. - Functional image (
OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE): normal built-app functionality lanes. - Lane definitions:
scripts/lib/docker-e2e-scenarios.mjs. Planner:scripts/lib/docker-e2e-plan.mjs. Executor:scripts/test-docker-all.mjs. node scripts/test-docker-all.mjs --plan-jsonemits the scheduler-owned CI plan (lanes, image kinds, package/live-image needs, state scenarios, credential checks) without building or running Docker.
Env var pattern for resource caps is
OPENCLAW_DOCKER_ALL_<RESOURCE>_LIMIT (resource name uppercased, non-alphanumerics collapsed to _).
Other behavior: the runner preflights Docker by default, cleans stale OpenClaw E2E containers, shares provider CLI tool caches between compatible lanes, and stops scheduling new pooled lanes after the first failure unless OPENCLAW_DOCKER_ALL_FAIL_FAST=0 is set. If one lane exceeds the effective weight/resource cap on a low-parallelism host, it can still start from an empty pool and run alone until it releases capacity. Per-lane logs, summary.json, failures.json, and phase timings write under .artifacts/docker-tests/<run-id>/; use pnpm test:docker:timings <summary.json> to inspect slow lanes and pnpm test:docker:rerun <run-id|summary.json|failures.json> to print cheap targeted rerun commands.
Notable Docker lanes
Local PR gate
For local PR land/gate checks, run:pnpm check:changedpnpm checkpnpm check:test-typespnpm buildpnpm testpnpm check:docs
pnpm test flakes on a loaded host, rerun once before treating it as a regression, then isolate with pnpm test <path/to/test>. For memory-constrained hosts:
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm testOPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache pnpm test:changed
Test performance tooling
pnpm test:perf:imports: enables Vitest import-duration + import-breakdown reporting, while still using scoped lane routing for explicit file/directory targets.pnpm test:perf:imports:changedscopes the same profiling to files changed sinceorigin/main.pnpm test:perf:changed:bench -- --ref <git-ref>benchmarks the routed changed-mode path against the native root-project run for the same committed git diff;pnpm test:perf:changed:bench -- --worktreebenchmarks the current worktree change set without committing first.pnpm test:perf:profile:mainwrites a CPU profile for the Vitest main thread (.artifacts/vitest-main-profile);pnpm test:perf:profile:runnerwrites CPU + heap profiles for the unit runner (.artifacts/vitest-runner-profile).pnpm test:perf:groups --full-suite --allow-failures --output .artifacts/test-perf/baseline-before.json: runs every full-suite Vitest leaf config serially and writes grouped duration data plus per-config JSON/log artifacts. Full-suite reports isolate files by default so retained module graphs and GC pauses from earlier files are not charged to later assertions; pass-- --no-isolateonly when intentionally profiling shared-worker accumulation. The Test Performance Agent uses this as its baseline before attempting slow-test fixes.pnpm test:perf:groups:compare .artifacts/test-perf/baseline-before.json .artifacts/test-perf/after-agent.jsoncompares grouped reports after a performance-focused change.- Full, extension, and include-pattern shard runs update local timing data in
.artifacts/vitest-shard-timings.json; later whole-config runs use those timings to balance slow and fast shards. Include-pattern CI shards append the shard name to the timing key, which keeps filtered shard timings visible without replacing whole-config timing data. SetOPENCLAW_TEST_PROJECTS_TIMINGS=0to ignore the local timing artifact.
Benchmarks
Model latency (scripts/bench-model.ts)
Model latency (scripts/bench-model.ts)
MINIMAX_API_KEY, MINIMAX_BASE_URL, MINIMAX_MODEL, ANTHROPIC_API_KEY. Default prompt: “Reply with a single word: ok. No punctuation or extra text.”CLI startup (scripts/bench-cli-startup.ts)
CLI startup (scripts/bench-cli-startup.ts)
startup:--version,--help,health,health --json,status --json,statusreal:health,status,status --json,sessions,sessions --json,tasks --json,tasks list --json,tasks audit --json,agents list --json,gateway status,gateway status --json,gateway health --json,config get gateway.portall: both presets combined
sampleCount, avg, p50, p95, min/max, exit-code/signal distribution, and max RSS per command. --cpu-prof-dir / --heap-prof-dir write V8 profiles per run.Saved output: pnpm test:startup:bench:smoke writes .artifacts/cli-startup-bench-smoke.json; pnpm test:startup:bench:save writes .artifacts/cli-startup-bench-all.json (runs=5 warmup=1). Checked-in fixture: test/fixtures/cli-startup-bench.json, refreshed by pnpm test:startup:bench:update, compared by pnpm test:startup:bench:check.Gateway startup (scripts/bench-gateway-startup.ts)
Gateway startup (scripts/bench-gateway-startup.ts)
Defaults to the built CLI entry at Case ids:
dist/entry.js; run pnpm build first. Pass --entry scripts/run-node.mjs to measure the source runner instead, and keep those results separate from built-entry baselines.default, skipChannels (channel startup skipped), oneInternalHook, allInternalHooks, fiftyPlugins (50 manifest plugins), fiftyStartupLazyPlugins (50 startup-lazy manifest plugins).Output includes first process output, /healthz, /readyz, HTTP listen log time, Gateway ready log time, CPU time, CPU core ratio, max RSS, heap, startup trace metrics, event-loop delay, and plugin lookup-table detail metrics. The script sets OPENCLAW_GATEWAY_STARTUP_TRACE=1 in the child Gateway environment./healthz is liveness (HTTP server can answer). /readyz is usable readiness (startup plugin sidecars, channels, and ready-critical post-attach work have settled). Startup hooks dispatch asynchronously and are not part of the readiness guarantee. Ready log time is the Gateway’s internal timestamp, useful for process-side attribution but not a substitute for the external /readyz probe.Use JSON output or --output when comparing changes. Use --cpu-prof-dir only after trace output points at import, compile, or CPU-bound work that phase timings alone cannot explain.Gateway restart (scripts/bench-gateway-restart.ts)
Gateway restart (scripts/bench-gateway-restart.ts)
macOS and Linux only (uses SIGUSR1 for in-process restarts; fails immediately on Windows). Same built-entry default and Case ids:
--entry scripts/run-node.mjs override as gateway startup above.skipChannels, skipChannelsAcpxProbe (ACPX startup probe on), skipChannelsNoAcpxProbe (probe off), default, fiftyPlugins.Output includes next /healthz, next /readyz, downtime, restart ready timing, CPU, RSS, startup trace metrics for the replacement process, and restart trace metrics for signal handling, active-work drain, close phases, next start, ready timing, and memory snapshots. The script sets OPENCLAW_GATEWAY_STARTUP_TRACE=1 and OPENCLAW_GATEWAY_RESTART_TRACE=1.Use this benchmark when a change touches restart signaling, close handlers, startup-after-restart, sidecar shutdown, service handoff, or readiness after restart. Start with skipChannels to isolate Gateway mechanics from channel startup; use default or plugin-heavy cases only after the narrow case explains the restart path. Trace metrics are attribution hints, not verdicts — judge a restart change from multiple samples, the matching owner span, /healthz//readyz behavior, and the user-visible restart contract.Onboarding E2E (Docker)
Optional; only needed for containerized onboarding smoke tests. Full cold-start flow in a clean Linux container:openclaw health.