mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
refactor: reuse docker gateway e2e helpers
This commit is contained in:
@@ -16,7 +16,9 @@ title: "Tests"
|
||||
- `pnpm check:changed`: runs the smart changed check gate for the diff against `origin/main`. It runs typecheck, lint, and guard commands for the affected architectural lanes, but does not run Vitest tests. Use `pnpm test:changed` or explicit `pnpm test <target>` for test proof.
|
||||
- `pnpm test`: routes explicit file/directory targets through scoped Vitest lanes. Untargeted runs use fixed shard groups and expand to leaf configs for local parallel execution; the extension group always expands to the per-extension shard configs instead of one giant root-project process.
|
||||
- Test wrapper runs end with a short `[test] passed|failed|skipped ... in ...` summary. Vitest's own duration line stays the per-shard detail.
|
||||
- Shared OpenClaw test state: use `src/test-utils/openclaw-test-state.ts` from Vitest when a test needs an isolated `HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH`, config fixture, workspace, agent dir, or auth-profile store. For process-level E2E tests that need a running Gateway, use `test/helpers/openclaw-test-instance.ts` so state, config, CLI env, gateway startup, log capture, and cleanup stay together. Docker/Bash E2E lanes that source `scripts/lib/docker-e2e-image.sh` can pass `docker_e2e_test_state_shell_b64 <label> <scenario>` into the container and decode it with `scripts/lib/openclaw-e2e-instance.sh`; multi-home scripts can pass `docker_e2e_test_state_function_b64` and call `openclaw_test_state_create <label> <scenario>` in each flow. Lower-level callers can use `scripts/lib/openclaw-test-state.mjs shell --label <name> --scenario <name>` for an in-container shell snippet, or `node scripts/lib/openclaw-test-state.mjs -- create --label <name> --scenario <name> --env-file <path> --json` for a sourceable host env file. The `--` before `create` keeps newer Node runtimes from treating `--env-file` as a Node flag. Docker/Bash lanes that launch a Gateway can source `scripts/lib/openclaw-e2e-instance.sh` inside the container for entrypoint resolution, mock OpenAI startup, Gateway readiness, log dumps, and process cleanup.
|
||||
- Shared OpenClaw test state: use `src/test-utils/openclaw-test-state.ts` from Vitest when a test needs an isolated `HOME`, `OPENCLAW_STATE_DIR`, `OPENCLAW_CONFIG_PATH`, config fixture, workspace, agent dir, or auth-profile store.
|
||||
- Process E2E helpers: use `test/helpers/openclaw-test-instance.ts` when a Vitest process-level E2E test needs a running Gateway, CLI env, log capture, and cleanup in one place.
|
||||
- Docker/Bash E2E helpers: lanes that source `scripts/lib/docker-e2e-image.sh` can pass `docker_e2e_test_state_shell_b64 <label> <scenario>` into the container and decode it with `scripts/lib/openclaw-e2e-instance.sh`; multi-home scripts can pass `docker_e2e_test_state_function_b64` and call `openclaw_test_state_create <label> <scenario>` in each flow. Lower-level callers can use `scripts/lib/openclaw-test-state.mjs shell --label <name> --scenario <name>` for an in-container shell snippet, or `node scripts/lib/openclaw-test-state.mjs -- create --label <name> --scenario <name> --env-file <path> --json` for a sourceable host env file. The `--` before `create` keeps newer Node runtimes from treating `--env-file` as a Node flag. Docker/Bash lanes that launch a Gateway can source `scripts/lib/openclaw-e2e-instance.sh` inside the container for entrypoint resolution, mock OpenAI startup, Gateway foreground/background launch, readiness probes, state env export, log dumps, and process cleanup.
|
||||
- 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. Set `OPENCLAW_TEST_PROJECTS_TIMINGS=0` to ignore the local timing artifact.
|
||||
- Selected `plugin-sdk` and `commands` test files now route through dedicated light lanes that keep only `test/setup.ts`, leaving runtime-heavy cases on their existing lanes.
|
||||
- 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`, and `src/plugins/contracts` use a local import graph to run importing tests instead of broad-running every shard when the dependency path is precise.
|
||||
|
||||
@@ -41,6 +41,7 @@ EOF
|
||||
echo "Building Docker image: $IMAGE_NAME"
|
||||
docker_build_run browser-cdp-snapshot-build -t "$IMAGE_NAME" -f "$build_dir/Dockerfile" "$build_dir"
|
||||
fi
|
||||
docker_e2e_harness_mount_args
|
||||
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 browser-cdp-snapshot empty)"
|
||||
|
||||
echo "Starting browser CDP snapshot container..."
|
||||
@@ -55,19 +56,13 @@ docker_cmd docker run -d \
|
||||
-e OPENCLAW_SKIP_CRON=1 \
|
||||
-e OPENCLAW_SKIP_CANVAS_HOST=1 \
|
||||
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
|
||||
"${DOCKER_E2E_HARNESS_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
eval \"\$(printf '%s' \"\${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}\" | base64 -d)\"
|
||||
{
|
||||
printf 'export HOME=%q\n' \"\$HOME\"
|
||||
printf 'export OPENCLAW_HOME=%q\n' \"\$OPENCLAW_HOME\"
|
||||
printf 'export OPENCLAW_STATE_DIR=%q\n' \"\$OPENCLAW_STATE_DIR\"
|
||||
printf 'export OPENCLAW_CONFIG_PATH=%q\n' \"\$OPENCLAW_CONFIG_PATH\"
|
||||
printf 'export OPENCLAW_AGENT_DIR=%q\n' \"\${OPENCLAW_AGENT_DIR-}\"
|
||||
printf 'export PI_CODING_AGENT_DIR=%q\n' \"\${PI_CODING_AGENT_DIR-}\"
|
||||
} >/tmp/openclaw-test-state-env
|
||||
entry=dist/index.mjs
|
||||
[ -f \"\$entry\" ] || entry=dist/index.js
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 \"\${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}\"
|
||||
openclaw_e2e_write_state_env
|
||||
entry=\"\$(openclaw_e2e_resolve_entrypoint)\"
|
||||
mkdir -p /tmp/openclaw-browser-cdp/chrome
|
||||
find dist -maxdepth 1 -type f -name 'pw-ai-*.js' ! -name 'pw-ai-state-*' -exec mv {} /tmp/openclaw-browser-cdp/ \;
|
||||
cat > \"\$OPENCLAW_CONFIG_PATH\" <<'JSON'
|
||||
@@ -120,7 +115,7 @@ http
|
||||
})
|
||||
.listen($FIXTURE_PORT, '127.0.0.1');
|
||||
NODE
|
||||
node \"\$entry\" gateway --port $PORT --bind loopback --allow-unconfigured >/tmp/browser-cdp-gateway.log 2>&1" >/dev/null
|
||||
openclaw_e2e_exec_gateway \"\$entry\" $PORT loopback /tmp/browser-cdp-gateway.log" >/dev/null
|
||||
|
||||
echo "Waiting for Chromium and Gateway..."
|
||||
ready=0
|
||||
@@ -129,14 +124,9 @@ for _ in $(seq 1 180); do
|
||||
break
|
||||
fi
|
||||
if docker_cmd docker exec "$CONTAINER_NAME" bash -lc "
|
||||
node --input-type=module -e 'const res = await fetch(\"http://127.0.0.1:$CDP_PORT/json/version\"); if (!res.ok) process.exit(1);' >/dev/null &&
|
||||
node --input-type=module -e '
|
||||
import net from \"node:net\";
|
||||
const socket = net.createConnection({ host: \"127.0.0.1\", port: $PORT });
|
||||
const timeout = setTimeout(() => { socket.destroy(); process.exit(1); }, 400);
|
||||
socket.on(\"connect\", () => { clearTimeout(timeout); socket.end(); process.exit(0); });
|
||||
socket.on(\"error\", () => { clearTimeout(timeout); process.exit(1); });
|
||||
' >/dev/null
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
openclaw_e2e_probe_http_status http://127.0.0.1:$CDP_PORT/json/version
|
||||
openclaw_e2e_probe_tcp 127.0.0.1 $PORT
|
||||
" >/dev/null 2>&1; then
|
||||
ready=1
|
||||
break
|
||||
@@ -155,8 +145,8 @@ echo "Running browser CDP snapshot smoke..."
|
||||
docker_cmd docker exec "$CONTAINER_NAME" bash -lc "
|
||||
set -euo pipefail
|
||||
source /tmp/openclaw-test-state-env
|
||||
entry=dist/index.mjs
|
||||
[ -f \"\$entry\" ] || entry=dist/index.js
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
entry=\"\$(openclaw_e2e_resolve_entrypoint)\"
|
||||
base_args=(--url ws://127.0.0.1:$PORT --token '$TOKEN')
|
||||
node \"\$entry\" browser \"\${base_args[@]}\" --browser-profile docker-cdp doctor --deep >/tmp/browser-cdp-doctor.txt
|
||||
grep -q 'OK live-snapshot' /tmp/browser-cdp-doctor.txt
|
||||
|
||||
@@ -16,6 +16,7 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" config-reload "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD"
|
||||
docker_e2e_harness_mount_args
|
||||
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 config-reload empty)"
|
||||
|
||||
echo "Starting gateway container..."
|
||||
@@ -29,19 +30,13 @@ docker run -d \
|
||||
-e OPENCLAW_SKIP_CRON=1 \
|
||||
-e OPENCLAW_SKIP_CANVAS_HOST=1 \
|
||||
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
|
||||
"${DOCKER_E2E_HARNESS_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
eval \"\$(printf '%s' \"\${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}\" | base64 -d)\"
|
||||
{
|
||||
printf 'export HOME=%q\n' \"\$HOME\"
|
||||
printf 'export OPENCLAW_HOME=%q\n' \"\$OPENCLAW_HOME\"
|
||||
printf 'export OPENCLAW_STATE_DIR=%q\n' \"\$OPENCLAW_STATE_DIR\"
|
||||
printf 'export OPENCLAW_CONFIG_PATH=%q\n' \"\$OPENCLAW_CONFIG_PATH\"
|
||||
printf 'export OPENCLAW_AGENT_DIR=%q\n' \"\${OPENCLAW_AGENT_DIR-}\"
|
||||
printf 'export PI_CODING_AGENT_DIR=%q\n' \"\${PI_CODING_AGENT_DIR-}\"
|
||||
} >/tmp/openclaw-test-state-env
|
||||
entry=dist/index.mjs
|
||||
[ -f \"\$entry\" ] || entry=dist/index.js
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
openclaw_e2e_eval_test_state_from_b64 \"\${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}\"
|
||||
openclaw_e2e_write_state_env
|
||||
entry=\"\$(openclaw_e2e_resolve_entrypoint)\"
|
||||
cat > \"\$OPENCLAW_CONFIG_PATH\" <<'JSON'
|
||||
{
|
||||
\"gateway\": {
|
||||
@@ -65,7 +60,7 @@ cat > \"\$OPENCLAW_CONFIG_PATH\" <<'JSON'
|
||||
}
|
||||
}
|
||||
JSON
|
||||
node \"\$entry\" gateway --port $PORT --bind loopback --allow-unconfigured > /tmp/config-reload-e2e.log 2>&1" >/dev/null
|
||||
openclaw_e2e_exec_gateway \"\$entry\" $PORT loopback /tmp/config-reload-e2e.log" >/dev/null
|
||||
|
||||
echo "Waiting for gateway..."
|
||||
ready=0
|
||||
@@ -73,23 +68,7 @@ for _ in $(seq 1 180); do
|
||||
if [ "$(docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" 2>/dev/null || echo false)" != "true" ]; then
|
||||
break
|
||||
fi
|
||||
if docker exec "$CONTAINER_NAME" bash -lc "node --input-type=module -e '
|
||||
import net from \"node:net\";
|
||||
const socket = net.createConnection({ host: \"127.0.0.1\", port: $PORT });
|
||||
const timeout = setTimeout(() => {
|
||||
socket.destroy();
|
||||
process.exit(1);
|
||||
}, 400);
|
||||
socket.on(\"connect\", () => {
|
||||
clearTimeout(timeout);
|
||||
socket.end();
|
||||
process.exit(0);
|
||||
});
|
||||
socket.on(\"error\", () => {
|
||||
clearTimeout(timeout);
|
||||
process.exit(1);
|
||||
});
|
||||
' >/dev/null 2>&1"; then
|
||||
if docker exec "$CONTAINER_NAME" bash -lc "source scripts/lib/openclaw-e2e-instance.sh; openclaw_e2e_probe_tcp 127.0.0.1 $PORT" >/dev/null 2>&1; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
@@ -106,8 +85,8 @@ fi
|
||||
echo "Checking initial RPC status..."
|
||||
docker exec "$CONTAINER_NAME" bash -lc "
|
||||
source /tmp/openclaw-test-state-env
|
||||
entry=dist/index.mjs
|
||||
[ -f \"\$entry\" ] || entry=dist/index.js
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
entry=\"\$(openclaw_e2e_resolve_entrypoint)\"
|
||||
node \"\$entry\" gateway status --url ws://127.0.0.1:$PORT --token '$TOKEN' --require-rpc --timeout 30000 >/tmp/config-reload-status-before.log
|
||||
"
|
||||
|
||||
@@ -133,8 +112,8 @@ fi
|
||||
echo "Checking post-write RPC status..."
|
||||
docker exec "$CONTAINER_NAME" bash -lc "
|
||||
source /tmp/openclaw-test-state-env
|
||||
entry=dist/index.mjs
|
||||
[ -f \"\$entry\" ] || entry=dist/index.js
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
entry=\"\$(openclaw_e2e_resolve_entrypoint)\"
|
||||
node \"\$entry\" gateway status --url ws://127.0.0.1:$PORT --token '$TOKEN' --require-rpc --timeout 30000 >/tmp/config-reload-status-after.log
|
||||
"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" gateway-network "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD"
|
||||
docker_e2e_harness_mount_args
|
||||
|
||||
echo "Creating Docker network..."
|
||||
docker_cmd docker network create "$NET_NAME" >/dev/null
|
||||
@@ -37,8 +38,9 @@ docker_cmd docker run -d \
|
||||
-e "OPENCLAW_SKIP_GMAIL_WATCHER=1" \
|
||||
-e "OPENCLAW_SKIP_CRON=1" \
|
||||
-e "OPENCLAW_SKIP_CANVAS_HOST=1" \
|
||||
"${DOCKER_E2E_HARNESS_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail; entry=dist/index.mjs; [ -f \"\$entry\" ] || entry=dist/index.js; node \"\$entry\" config set gateway.controlUi.enabled false >/dev/null; node \"\$entry\" gateway --port $PORT --bind lan --allow-unconfigured > /tmp/gateway-net-e2e.log 2>&1" >/dev/null
|
||||
bash -lc "set -euo pipefail; source scripts/lib/openclaw-e2e-instance.sh; entry=\"\$(openclaw_e2e_resolve_entrypoint)\"; node \"\$entry\" config set gateway.controlUi.enabled false >/dev/null; openclaw_e2e_exec_gateway \"\$entry\" $PORT lan /tmp/gateway-net-e2e.log" >/dev/null
|
||||
|
||||
echo "Waiting for gateway to come up..."
|
||||
ready=0
|
||||
@@ -46,27 +48,7 @@ for _ in $(seq 1 180); do
|
||||
if [ "$(docker_cmd docker inspect -f '{{.State.Running}}' "$GW_NAME" 2>/dev/null || echo false)" != "true" ]; then
|
||||
break
|
||||
fi
|
||||
if docker_cmd docker exec "$GW_NAME" bash -lc "node --input-type=module -e '
|
||||
import net from \"node:net\";
|
||||
const socket = net.createConnection({ host: \"127.0.0.1\", port: $PORT });
|
||||
const timeout = setTimeout(() => {
|
||||
socket.destroy();
|
||||
process.exit(1);
|
||||
}, 400);
|
||||
socket.on(\"connect\", () => {
|
||||
clearTimeout(timeout);
|
||||
socket.end();
|
||||
process.exit(0);
|
||||
});
|
||||
socket.on(\"error\", () => {
|
||||
clearTimeout(timeout);
|
||||
process.exit(1);
|
||||
});
|
||||
' >/dev/null 2>&1"; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
if docker_cmd docker exec "$GW_NAME" bash -lc "grep -q \"listening on ws://\" /tmp/gateway-net-e2e.log 2>/dev/null"; then
|
||||
if docker_cmd docker exec "$GW_NAME" bash -lc "source scripts/lib/openclaw-e2e-instance.sh; openclaw_e2e_probe_tcp 127.0.0.1 $PORT || grep -q \"listening on ws://\" /tmp/gateway-net-e2e.log 2>/dev/null"; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
|
||||
@@ -74,8 +74,8 @@ docker_cmd docker run -d \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
entry=dist/index.mjs
|
||||
[ -f "$entry" ] || entry=dist/index.js
|
||||
source scripts/lib/openclaw-e2e-instance.sh
|
||||
entry="$(openclaw_e2e_resolve_entrypoint)"
|
||||
|
||||
openai_api_key="${OPENAI_API_KEY:?OPENAI_API_KEY required}"
|
||||
batch_file="$(mktemp /tmp/openclaw-openwebui-config.XXXXXX.json)"
|
||||
@@ -120,7 +120,7 @@ EOF
|
||||
EOF
|
||||
rm -f "$workspace/BOOTSTRAP.md"
|
||||
|
||||
exec node "$entry" gateway --port '"$PORT"' --bind lan --allow-unconfigured > /tmp/openwebui-gateway.log 2>&1
|
||||
openclaw_e2e_exec_gateway "$entry" '"$PORT"' lan /tmp/openwebui-gateway.log
|
||||
' >/dev/null
|
||||
|
||||
echo "Waiting for gateway HTTP surface..."
|
||||
|
||||
@@ -9,6 +9,17 @@ openclaw_e2e_resolve_entrypoint() {
|
||||
echo "OpenClaw entrypoint not found under dist/" >&2
|
||||
return 1
|
||||
}
|
||||
openclaw_e2e_write_state_env() {
|
||||
local target="${1:-/tmp/openclaw-test-state-env}"
|
||||
{
|
||||
printf 'export HOME=%q\n' "$HOME"
|
||||
printf 'export OPENCLAW_HOME=%q\n' "$OPENCLAW_HOME"
|
||||
printf 'export OPENCLAW_STATE_DIR=%q\n' "$OPENCLAW_STATE_DIR"
|
||||
printf 'export OPENCLAW_CONFIG_PATH=%q\n' "$OPENCLAW_CONFIG_PATH"
|
||||
printf 'export OPENCLAW_AGENT_DIR=%q\n' "${OPENCLAW_AGENT_DIR-}"
|
||||
printf 'export PI_CODING_AGENT_DIR=%q\n' "${PI_CODING_AGENT_DIR-}"
|
||||
} >"$target"
|
||||
}
|
||||
openclaw_e2e_stop_process() {
|
||||
local pid="${1:-}" _
|
||||
[ -n "$pid" ] || return 0
|
||||
@@ -31,6 +42,7 @@ openclaw_e2e_wait_mock_openai() {
|
||||
node -e "$probe" "$port"
|
||||
}
|
||||
openclaw_e2e_start_gateway() { node "$1" gateway --port "$2" --bind loopback --allow-unconfigured >"$3" 2>&1 & printf '%s\n' "$!"; }
|
||||
openclaw_e2e_exec_gateway() { exec node "$1" gateway --port "$2" --bind "${3:-loopback}" --allow-unconfigured >"$4" 2>&1; }
|
||||
openclaw_e2e_wait_gateway_ready() {
|
||||
local pid="$1" log="$2" attempts="${3:-300}" _
|
||||
for _ in $(seq 1 "$attempts"); do
|
||||
@@ -47,6 +59,18 @@ openclaw_e2e_wait_gateway_ready() {
|
||||
tail -n 120 "$log" 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
openclaw_e2e_probe_tcp() {
|
||||
node --input-type=module -e '
|
||||
import net from "node:net";
|
||||
const socket = net.createConnection({ host: process.argv[1], port: Number(process.argv[2]) });
|
||||
const timeout = setTimeout(() => { socket.destroy(); process.exit(1); }, Number(process.argv[3] ?? 400));
|
||||
socket.on("connect", () => { clearTimeout(timeout); socket.end(); process.exit(0); });
|
||||
socket.on("error", () => { clearTimeout(timeout); process.exit(1); });
|
||||
' "$1" "$2" "${3:-400}"
|
||||
}
|
||||
openclaw_e2e_probe_http_status() {
|
||||
node -e 'fetch(process.argv[1]).then(r=>process.exit(r.status===Number(process.argv[2])?0:1)).catch(()=>process.exit(1))' "$1" "${2:-200}"
|
||||
}
|
||||
openclaw_e2e_dump_logs() {
|
||||
local path
|
||||
for path in "$@"; do
|
||||
|
||||
Reference in New Issue
Block a user