From 3c89f5d5378d823ef383a94732be3002d2373b4b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 22 Apr 2026 20:02:13 +0100 Subject: [PATCH] ci: add scoped docker gateway e2e --- .github/workflows/install-smoke.yml | 6 ++++++ docs/ci.md | 2 +- scripts/ci-changed-scope.mjs | 2 +- scripts/e2e/gateway-network-docker.sh | 16 +++++++++++----- src/scripts/ci-changed-scope.test.ts | 9 +++++++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 61c7c0b420b..7940a30da9b 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -112,6 +112,12 @@ jobs: run: | docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version' + - name: Run Docker gateway network e2e + env: + OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE: openclaw-dockerfile-smoke:local + OPENCLAW_GATEWAY_NETWORK_E2E_SKIP_BUILD: "1" + run: bash scripts/e2e/gateway-network-docker.sh + # This smoke validates that the build-arg path preinstalls the matrix # runtime deps declared by the plugin and that matrix discovery stays # healthy in the final runtime image. diff --git a/docs/ci.md b/docs/ci.md index 32b71a73801..a852fae046e 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -46,7 +46,7 @@ Jobs are ordered so cheap checks fail before expensive ones run: Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. CI workflow edits validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes. -The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes. Its QR package smoke forces the Docker `pnpm install` layer to rerun while preserving the BuildKit pnpm store cache, so it still exercises installation without redownloading dependencies on every run. +The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes. Its QR package smoke forces the Docker `pnpm install` layer to rerun while preserving the BuildKit pnpm store cache, so it still exercises installation without redownloading dependencies on every run. Its gateway-network e2e reuses the runtime image built earlier in the job, so it adds real container-to-container WebSocket coverage without adding another Docker build. Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Release metadata-only version bumps run targeted version/config/root-dependency checks. Unknown root/config changes fail safe to all lanes. diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index c34d26f7c45..28f39bcc8d2 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -20,7 +20,7 @@ const CONTROL_UI_I18N_SCOPE_RE = const NATIVE_ONLY_RE = /^(apps\/android\/|apps\/ios\/|apps\/macos\/|apps\/macos-mlx-tts\/|apps\/shared\/|Swabble\/|appcast\.xml$)/; const CHANGED_SMOKE_SCOPE_RE = - /^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/install\.sh$|scripts\/postinstall-bundled-plugins\.mjs$|scripts\/test-install-sh-docker\.sh$|scripts\/docker\/|scripts\/e2e\/(?:Dockerfile\.qr-import|qr-import-docker\.sh)$|src\/plugins\/bundled-runtime-deps\.ts$|extensions\/[^/]+\/package\.json$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/; + /^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/install\.sh$|scripts\/postinstall-bundled-plugins\.mjs$|scripts\/test-install-sh-docker\.sh$|scripts\/docker\/|scripts\/e2e\/(?:Dockerfile\.qr-import|qr-import-docker|gateway-network-docker)\.sh$|src\/plugins\/bundled-runtime-deps\.ts$|extensions\/[^/]+\/package\.json$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/; /** * @param {string[]} changedPaths diff --git a/scripts/e2e/gateway-network-docker.sh b/scripts/e2e/gateway-network-docker.sh index 07694e566ef..327941ce38b 100644 --- a/scripts/e2e/gateway-network-docker.sh +++ b/scripts/e2e/gateway-network-docker.sh @@ -3,7 +3,8 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" source "$ROOT_DIR/scripts/lib/docker-e2e-logs.sh" -IMAGE_NAME="openclaw-gateway-network-e2e" +IMAGE_NAME="${OPENCLAW_GATEWAY_NETWORK_E2E_IMAGE:-openclaw-gateway-network-e2e}" +SKIP_BUILD="${OPENCLAW_GATEWAY_NETWORK_E2E_SKIP_BUILD:-0}" PORT="18789" TOKEN="e2e-$(date +%s)-$$" @@ -16,8 +17,12 @@ cleanup() { } trap cleanup EXIT -echo "Building Docker image..." -run_logged gateway-network-build docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" +if [ "$SKIP_BUILD" = "1" ]; then + echo "Reusing Docker image: $IMAGE_NAME" +else + echo "Building Docker image..." + run_logged gateway-network-build docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" +fi echo "Creating Docker network..." docker network create "$NET_NAME" >/dev/null @@ -83,9 +88,10 @@ run_logged gateway-network-client docker run --rm \ -e "GW_URL=ws://$GW_NAME:$PORT" \ -e "GW_TOKEN=$TOKEN" \ "$IMAGE_NAME" \ - bash -lc "node --import tsx - <<'NODE' + bash -lc "node --input-type=module - <<'NODE' import { WebSocket } from \"ws\"; -import { PROTOCOL_VERSION } from \"./src/gateway/protocol/index.ts\"; + +const PROTOCOL_VERSION = 3; const url = process.env.GW_URL; const token = process.env.GW_TOKEN; diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index df99cb25844..e0f4dca00c2 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -219,6 +219,15 @@ describe("detectChangedScope", () => { runChangedSmoke: true, runControlUiI18n: false, }); + expect(detectChangedScope(["scripts/e2e/gateway-network-docker.sh"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: true, + runSkillsPython: false, + runChangedSmoke: true, + runControlUiI18n: false, + }); expect(detectChangedScope(["scripts/postinstall-bundled-plugins.mjs"])).toEqual({ runNode: true, runMacos: false,