From 7ca2f9fed52226d0e02128872d087268d07c62b2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Apr 2026 01:22:50 +0100 Subject: [PATCH] test(docker): align package harness image --- docs/concepts/qa-e2e-automation.md | 12 ++--- docs/help/testing.md | 2 +- scripts/e2e/Dockerfile | 8 ++- scripts/e2e/docker-observability-smoke.sh | 61 ----------------------- scripts/lib/docker-e2e-scenarios.mjs | 14 ------ scripts/qa-otel-smoke.ts | 10 ++-- tsdown.config.ts | 26 ++++++++++ 7 files changed, 41 insertions(+), 92 deletions(-) delete mode 100644 scripts/e2e/docker-observability-smoke.sh diff --git a/docs/concepts/qa-e2e-automation.md b/docs/concepts/qa-e2e-automation.md index b0a55d8e4a2..fb75fb73ed9 100644 --- a/docs/concepts/qa-e2e-automation.md +++ b/docs/concepts/qa-e2e-automation.md @@ -65,14 +65,10 @@ model calls must not export `StreamAbandoned` on successful turns; raw diagnosti `openclaw.content.*` attributes must stay out of the trace. It writes `otel-smoke-summary.json` next to the QA suite artifacts. -The normal Docker aggregate and release-path core chunk also run an -observability lane. It reuses the shared package-installed functional Docker -image, mounts the QA harness files read-only, runs the OTEL trace smoke inside -the container, then runs the `docker-prometheus-smoke` QA scenario with the -`diagnostics-prometheus` plugin enabled. Set -`OPENCLAW_DOCKER_OBSERVABILITY_LOOPS=` to repeat both checks inside one -Docker run while preserving per-loop artifacts under -`.artifacts/docker-observability/...`. +Observability QA stays source-checkout only. The npm tarball intentionally omits +QA Lab, so package Docker release lanes do not run `qa` commands. Use +`pnpm qa:otel:smoke` from a built source checkout when changing diagnostics +instrumentation. For a transport-real Matrix smoke lane, run: diff --git a/docs/help/testing.md b/docs/help/testing.md index c08edc47d37..f8eff284744 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -617,7 +617,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or - CLI backend smoke: `pnpm test:docker:live-cli-backend` (script: `scripts/test-live-cli-backend-docker.sh`) - Codex app-server harness smoke: `pnpm test:docker:live-codex-harness` (script: `scripts/test-live-codex-harness-docker.sh`) - Gateway + dev agent: `pnpm test:docker:live-gateway` (script: `scripts/test-live-gateway-models-docker.sh`) -- Docker observability smoke: included in `pnpm test:docker:all`, `pnpm test:docker:local:all`, and the release-path `core` chunk (script: `scripts/e2e/docker-observability-smoke.sh`). It runs QA-lab OTEL and Prometheus diagnostics checks inside the shared package-installed functional Docker image, with only QA harness files mounted read-only. Set `OPENCLAW_DOCKER_OBSERVABILITY_LOOPS=` to repeat both checks in one container run. +- Observability smoke: `pnpm qa:otel:smoke` is a private QA source-checkout lane. It is intentionally not part of package Docker release lanes because the npm tarball omits QA Lab. - Open WebUI live smoke: `pnpm test:docker:openwebui` (script: `scripts/e2e/openwebui-docker.sh`) - Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`) - Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, verifies doctor repairs activated plugin runtime deps, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`. diff --git a/scripts/e2e/Dockerfile b/scripts/e2e/Dockerfile index dbda16a418f..0c1be69bb3e 100644 --- a/scripts/e2e/Dockerfile +++ b/scripts/e2e/Dockerfile @@ -6,8 +6,10 @@ FROM node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb AS e2e-runner +# python3 covers package/plugin install paths that execute helper scripts while +# staying below a full build-essential toolchain. RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates git \ + && apt-get install -y --no-install-recommends ca-certificates git python3 \ && rm -rf /var/lib/apt/lists/* RUN corepack enable @@ -40,10 +42,14 @@ FROM bare AS functional # The app under test enters through the named BuildKit context, not by copying # checkout sources into the image. COPY --from=openclaw_package --chown=appuser:appuser openclaw-current.tgz /tmp/openclaw-current.tgz +# Preserve package self-reference imports such as openclaw/plugin-sdk/* after +# copying the installed package out of npm's global node_modules tree. RUN npm install -g --prefix /tmp/openclaw-prefix /tmp/openclaw-current.tgz --no-fund --no-audit \ && cp -a /tmp/openclaw-prefix/lib/node_modules/openclaw/. /app/ \ && mkdir -p "$HOME/.local/bin" \ && ln -sf /app/openclaw.mjs "$HOME/.local/bin/openclaw" \ + && mkdir -p /app/node_modules \ + && ln -sf /app /app/node_modules/openclaw \ && rm -rf /tmp/openclaw-prefix /tmp/openclaw-current.tgz CMD ["bash"] diff --git a/scripts/e2e/docker-observability-smoke.sh b/scripts/e2e/docker-observability-smoke.sh deleted file mode 100644 index caa08d1b5c1..00000000000 --- a/scripts/e2e/docker-observability-smoke.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -# Runs QA diagnostics smoke checks inside the shared package-installed Docker -# E2E image. The OpenClaw app under test comes from the prepared npm tarball; -# only QA harness files are mounted read-only. -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh" - -IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-docker-observability-e2e:local" OPENCLAW_DOCKER_OBSERVABILITY_E2E_IMAGE OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE)" -SKIP_BUILD="${OPENCLAW_DOCKER_OBSERVABILITY_E2E_SKIP_BUILD:-0}" -LOOPS="${OPENCLAW_DOCKER_OBSERVABILITY_LOOPS:-1}" -OUTPUT_DIR="${OPENCLAW_DOCKER_OBSERVABILITY_OUTPUT_DIR:-$ROOT_DIR/.artifacts/docker-observability/$(date +%Y%m%d-%H%M%S)}" - -if ! [[ "$LOOPS" =~ ^[1-9][0-9]*$ ]]; then - echo "OPENCLAW_DOCKER_OBSERVABILITY_LOOPS must be a positive integer, got: $LOOPS" >&2 - exit 1 -fi - -mkdir -p "$OUTPUT_DIR" - -docker_e2e_build_or_reuse "$IMAGE_NAME" docker-observability "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD" -docker_e2e_harness_mount_args - -echo "Running Docker observability smoke with $LOOPS loop(s)..." -run_logged docker-observability docker run --rm \ - -e "OPENCLAW_DOCKER_OBSERVABILITY_LOOPS=$LOOPS" \ - "${DOCKER_E2E_HARNESS_ARGS[@]}" \ - -v "$ROOT_DIR/scripts/qa-otel-smoke.ts:/app/scripts/qa-otel-smoke.ts:ro" \ - -v "$ROOT_DIR/qa:/app/qa:ro" \ - -v "$OUTPUT_DIR:/app/.artifacts/docker-observability-current" \ - "$IMAGE_NAME" \ - bash -lc ' -set -euo pipefail - -loops="${OPENCLAW_DOCKER_OBSERVABILITY_LOOPS:-1}" -artifact_root=".artifacts/docker-observability-current" -mkdir -p "$artifact_root" - -for i in $(seq 1 "$loops"); do - iteration_dir="$artifact_root/loop-$i" - mkdir -p "$iteration_dir" - - echo "== docker observability loop $i/$loops: otel ==" - # The functional image has a global tsx runner for mounted harness files; the - # published package intentionally does not ship tsx as an app dependency. - tsx scripts/qa-otel-smoke.ts \ - --provider-mode mock-openai \ - --output-dir "$iteration_dir/otel" - - echo "== docker observability loop $i/$loops: prometheus ==" - node openclaw.mjs qa suite \ - --provider-mode mock-openai \ - --scenario docker-prometheus-smoke \ - --concurrency 1 \ - --fast \ - --output-dir "$iteration_dir/prometheus" -done -' - -echo "Docker observability smoke passed. Artifacts: $OUTPUT_DIR" diff --git a/scripts/lib/docker-e2e-scenarios.mjs b/scripts/lib/docker-e2e-scenarios.mjs index 227adae9d00..d08982f2628 100644 --- a/scripts/lib/docker-e2e-scenarios.mjs +++ b/scripts/lib/docker-e2e-scenarios.mjs @@ -184,13 +184,6 @@ export const mainLanes = [ { resources: ["service"], weight: 3 }, ), serviceLane("gateway-network", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:gateway-network"), - serviceLane( - "observability", - "OPENCLAW_SKIP_DOCKER_BUILD=1 bash scripts/e2e/docker-observability-smoke.sh", - { - weight: 3, - }, - ), serviceLane( "agents-delete-shared-workspace", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:agents-delete-shared-workspace", @@ -345,13 +338,6 @@ const releasePathChunks = { "pi-bundle-mcp-tools", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:pi-bundle-mcp-tools", ), - serviceLane( - "observability", - "OPENCLAW_SKIP_DOCKER_BUILD=1 bash scripts/e2e/docker-observability-smoke.sh", - { - weight: 3, - }, - ), serviceLane("mcp-channels", "OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:mcp-channels", { resources: ["npm"], weight: 3, diff --git a/scripts/qa-otel-smoke.ts b/scripts/qa-otel-smoke.ts index 0df2099309d..3455f6d7b54 100644 --- a/scripts/qa-otel-smoke.ts +++ b/scripts/qa-otel-smoke.ts @@ -287,14 +287,10 @@ function startLocalOtlpTraceReceiver() { } function openClawEntryArgs(): string[] { - if ( - existsSync(path.join(process.cwd(), "openclaw.mjs")) && - (existsSync(path.join(process.cwd(), "dist", "entry.js")) || - existsSync(path.join(process.cwd(), "dist", "entry.mjs"))) - ) { - return ["openclaw.mjs"]; + if (existsSync(path.join(process.cwd(), "scripts", "run-node.mjs"))) { + return ["scripts/run-node.mjs"]; } - return ["scripts/run-node.mjs"]; + return ["openclaw.mjs"]; } function spawnOpenClaw(args: string[], env: NodeJS.ProcessEnv): ChildProcess { diff --git a/tsdown.config.ts b/tsdown.config.ts index 67f8777dc67..7327921efd3 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -234,7 +234,32 @@ function buildCoreDistEntries(): Record { }; } +function buildDockerE2eHarnessEntries(): Record { + return { + // Mounted Docker harnesses run against the npm tarball image, so any + // internal module they assert must have a stable package dist entry. + "agents/pi-bundle-mcp-materialize": "src/agents/pi-bundle-mcp-materialize.ts", + "agents/pi-bundle-mcp-runtime": "src/agents/pi-bundle-mcp-runtime.ts", + "agents/pi-embedded-runner/effective-tool-policy": + "src/agents/pi-embedded-runner/effective-tool-policy.ts", + "agents/pi-embedded-runner/run/runtime-context-prompt": + "src/agents/pi-embedded-runner/run/runtime-context-prompt.ts", + "auto-reply/reply/commands-crestodian": "src/auto-reply/reply/commands-crestodian.ts", + "cli/run-main": "src/cli/run-main.ts", + "config/config": "src/config/config.ts", + "crestodian/crestodian": "src/crestodian/crestodian.ts", + "crestodian/rescue-message": "src/crestodian/rescue-message.ts", + "gateway/protocol/index": "src/gateway/protocol/index.ts", + "infra/errors": "src/infra/errors.ts", + "infra/ws": "src/infra/ws.ts", + "plugin-sdk/provider-onboard": "src/plugin-sdk/provider-onboard.ts", + "plugins/tools": "src/plugins/tools.ts", + "shared/string-coerce": "src/shared/string-coerce.ts", + }; +} + const coreDistEntries = buildCoreDistEntries(); +const dockerE2eHarnessEntries = buildDockerE2eHarnessEntries(); const stagedBundledPluginBuildEntries = bundledPluginBuildEntries.filter(({ packageJson }) => shouldStageBundledPluginRuntimeDependencies(packageJson), ); @@ -247,6 +272,7 @@ const rootBundledPluginBuildEntries = bundledPluginBuildEntries.filter( function buildUnifiedDistEntries(): Record { return { ...coreDistEntries, + ...dockerE2eHarnessEntries, // Internal compat artifact for the root-alias.cjs lazy loader. "plugin-sdk/compat": "src/plugin-sdk/compat.ts", ...Object.fromEntries(