diff --git a/scripts/e2e/npm-onboard-channel-agent-docker.sh b/scripts/e2e/npm-onboard-channel-agent-docker.sh index ed55221e5bf..77c85ac7968 100644 --- a/scripts/e2e/npm-onboard-channel-agent-docker.sh +++ b/scripts/e2e/npm-onboard-channel-agent-docker.sh @@ -52,7 +52,8 @@ if ! docker run --rm \ -i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF' set -euo pipefail -eval "$(printf "%s" "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}" | base64 -d)" +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}" export NPM_CONFIG_PREFIX="$HOME/.npm-global" export PATH="$NPM_CONFIG_PREFIX/bin:$PATH" export OPENAI_API_KEY="sk-openclaw-npm-onboard-e2e" @@ -63,6 +64,7 @@ PORT="18789" MOCK_PORT="44080" SUCCESS_MARKER="OPENCLAW_AGENT_E2E_OK_ASSISTANT" MOCK_REQUEST_LOG="/tmp/openclaw-mock-openai-requests.jsonl" +export SUCCESS_MARKER MOCK_REQUEST_LOG mock_pid="" case "$CHANNEL" in @@ -81,17 +83,14 @@ case "$CHANNEL" in esac cleanup() { - if [ -n "${mock_pid:-}" ] && kill -0 "$mock_pid" 2>/dev/null; then - kill "$mock_pid" 2>/dev/null || true - wait "$mock_pid" 2>/dev/null || true - fi + openclaw_e2e_stop_process "${mock_pid:-}" } trap cleanup EXIT dump_debug_logs() { local status="$1" echo "npm onboard/channel/agent scenario failed with exit code $status" >&2 - for file in \ + openclaw_e2e_dump_logs \ /tmp/openclaw-install.log \ /tmp/openclaw-onboard.json \ /tmp/openclaw-channel-add.log \ @@ -100,12 +99,7 @@ dump_debug_logs() { /tmp/openclaw-agent.err \ /tmp/openclaw-agent.json \ /tmp/openclaw-mock-openai.log \ - "$MOCK_REQUEST_LOG"; do - if [ -f "$file" ]; then - echo "--- $file ---" >&2 - sed -n '1,220p' "$file" >&2 || true - fi - done + "$MOCK_REQUEST_LOG" } trap 'status=$?; dump_debug_logs "$status"; exit "$status"' ERR @@ -136,15 +130,8 @@ assert_dep_present() { fi } -MOCK_PORT="$MOCK_PORT" SUCCESS_MARKER="$SUCCESS_MARKER" MOCK_REQUEST_LOG="$MOCK_REQUEST_LOG" node scripts/e2e/mock-openai-server.mjs >/tmp/openclaw-mock-openai.log 2>&1 & -mock_pid="$!" -for _ in $(seq 1 80); do - if node -e "fetch('http://127.0.0.1:${MOCK_PORT}/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"; then - break - fi - sleep 0.1 -done -node -e "fetch('http://127.0.0.1:${MOCK_PORT}/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" +mock_pid="$(openclaw_e2e_start_mock_openai "$MOCK_PORT" /tmp/openclaw-mock-openai.log)" +openclaw_e2e_wait_mock_openai "$MOCK_PORT" echo "Running non-interactive onboarding..." openclaw onboard --non-interactive --accept-risk \ diff --git a/scripts/e2e/onboard-docker.sh b/scripts/e2e/onboard-docker.sh index 70872751133..306538b9f91 100755 --- a/scripts/e2e/onboard-docker.sh +++ b/scripts/e2e/onboard-docker.sh @@ -4,54 +4,27 @@ 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-onboard-e2e" OPENCLAW_ONBOARD_E2E_IMAGE)" -OPENCLAW_TEST_STATE_FUNCTION_B64="$( - node "$ROOT_DIR/scripts/lib/openclaw-test-state.mjs" shell-function \ - | base64 \ - | tr -d '\n' -)" +OPENCLAW_TEST_STATE_FUNCTION_B64="$(docker_e2e_test_state_function_b64)" docker_e2e_build_or_reuse "$IMAGE_NAME" onboard +docker_e2e_harness_mount_args echo "Running onboarding E2E..." docker run --rm -t \ -e "OPENCLAW_TEST_STATE_FUNCTION_B64=$OPENCLAW_TEST_STATE_FUNCTION_B64" \ + "${DOCKER_E2E_HARNESS_ARGS[@]}" \ "$IMAGE_NAME" bash -lc ' set -euo pipefail trap "" PIPE export TERM=xterm-256color + source scripts/lib/openclaw-e2e-instance.sh eval "$(printf "%s" "${OPENCLAW_TEST_STATE_FUNCTION_B64:?missing OPENCLAW_TEST_STATE_FUNCTION_B64}" | base64 -d)" ONBOARD_FLAGS="--flow quickstart --auth-choice skip --skip-channels --skip-skills --skip-daemon --skip-ui" - # tsdown may emit dist/index.js or dist/index.mjs depending on runtime/bundler. - if [ -f dist/index.mjs ]; then - OPENCLAW_ENTRY="dist/index.mjs" - elif [ -f dist/index.js ]; then - OPENCLAW_ENTRY="dist/index.js" - else - echo "Missing dist/index.(m)js (build output):" - ls -la dist || true - exit 1 - fi + OPENCLAW_ENTRY="$(openclaw_e2e_resolve_entrypoint)" export OPENCLAW_ENTRY # Provide a minimal trash shim to avoid noisy "missing trash" logs in containers. - export PATH="/tmp/openclaw-bin:$PATH" - mkdir -p /tmp/openclaw-bin - cat > /tmp/openclaw-bin/trash <<'"'"'TRASH'"'"' -#!/usr/bin/env bash -set -euo pipefail -trash_dir="$HOME/.Trash" -mkdir -p "$trash_dir" -for target in "$@"; do - [ -e "$target" ] || continue - base="$(basename "$target")" - dest="$trash_dir/$base" - if [ -e "$dest" ]; then - dest="$trash_dir/${base}-$(date +%s)-$$" - fi - mv "$target" "$dest" -done -TRASH - chmod +x /tmp/openclaw-bin/trash + openclaw_e2e_install_trash_shim send() { local payload="$1" @@ -120,29 +93,12 @@ TRASH } start_gateway() { - node "$OPENCLAW_ENTRY" gateway --port 18789 --bind loopback --allow-unconfigured > /tmp/gateway-e2e.log 2>&1 & - GATEWAY_PID="$!" + GATEWAY_PID="$(openclaw_e2e_start_gateway "$OPENCLAW_ENTRY" 18789 /tmp/gateway-e2e.log)" } wait_for_gateway() { for _ in $(seq 1 20); do - if node --input-type=module -e " - import net from 'node:net'; - const socket = net.createConnection({ host: '127.0.0.1', port: 18789 }); - const timeout = setTimeout(() => { - socket.destroy(); - process.exit(1); - }, 500); - socket.on('connect', () => { - clearTimeout(timeout); - socket.end(); - process.exit(0); - }); - socket.on('error', () => { - clearTimeout(timeout); - process.exit(1); - }); - " >/dev/null 2>&1; then + if openclaw_e2e_probe_tcp 127.0.0.1 18789 500 >/dev/null 2>&1; then return 0 fi if [ -f /tmp/gateway-e2e.log ] && grep -E -q "listening on ws://[^ ]+:18789" /tmp/gateway-e2e.log; then @@ -158,11 +114,7 @@ TRASH } stop_gateway() { - local gw_pid="$1" - if [ -n "$gw_pid" ]; then - kill "$gw_pid" 2>/dev/null || true - wait "$gw_pid" || true - fi + openclaw_e2e_stop_process "$1" } run_wizard_cmd() { @@ -229,32 +181,6 @@ TRASH openclaw_test_state_create "$state_ref" empty } - assert_file() { - local file_path="$1" - if [ ! -f "$file_path" ]; then - echo "Missing file: $file_path" - exit 1 - fi - } - - assert_dir() { - local dir_path="$1" - if [ ! -d "$dir_path" ]; then - echo "Missing dir: $dir_path" - exit 1 - fi - } - - run_case_logged() { - local label="$1" - shift - local log_path="/tmp/openclaw-onboard-${label}.log" - if ! "$@" >"$log_path" 2>&1; then - cat "$log_path" - exit 1 - fi - } - select_skip_hooks() { # Hooks multiselect: pick "Skip for now". wait_for_log "Enable hooks?" 60 @@ -306,7 +232,7 @@ TRASH run_case_local_basic() { set_isolated_openclaw_env local-basic - run_case_logged local-basic node "$OPENCLAW_ENTRY" onboard \ + openclaw_e2e_run_logged local-basic node "$OPENCLAW_ENTRY" onboard \ --non-interactive \ --accept-risk \ --flow quickstart \ @@ -322,10 +248,10 @@ TRASH config_path="$OPENCLAW_CONFIG_PATH" sessions_dir="$OPENCLAW_STATE_DIR/agents/main/sessions" - assert_file "$config_path" - assert_dir "$sessions_dir" + openclaw_e2e_assert_file "$config_path" + openclaw_e2e_assert_dir "$sessions_dir" for file in AGENTS.md BOOTSTRAP.md IDENTITY.md SOUL.md TOOLS.md USER.md; do - assert_file "$workspace_dir/$file" + openclaw_e2e_assert_file "$workspace_dir/$file" done CONFIG_PATH="$config_path" WORKSPACE_DIR="$workspace_dir" node --input-type=module - <<'"'"'NODE'"'"' @@ -380,7 +306,7 @@ NODE run_case_remote_non_interactive() { set_isolated_openclaw_env remote-non-interactive # Smoke test non-interactive remote config write. - run_case_logged remote-non-interactive node "$OPENCLAW_ENTRY" onboard --non-interactive --accept-risk \ + openclaw_e2e_run_logged remote-non-interactive node "$OPENCLAW_ENTRY" onboard --non-interactive --accept-risk \ --mode remote \ --remote-url ws://gateway.local:18789 \ --remote-token remote-token \ @@ -388,7 +314,7 @@ NODE --skip-health config_path="$OPENCLAW_CONFIG_PATH" - assert_file "$config_path" + openclaw_e2e_assert_file "$config_path" CONFIG_PATH="$config_path" node --input-type=module - <<'"'"'NODE'"'"' import fs from "node:fs"; @@ -431,7 +357,7 @@ NODE } JSON - run_case_logged reset-config node "$OPENCLAW_ENTRY" onboard \ + openclaw_e2e_run_logged reset-config node "$OPENCLAW_ENTRY" onboard \ --non-interactive \ --accept-risk \ --flow quickstart \ @@ -444,7 +370,7 @@ JSON --skip-health config_path="$OPENCLAW_CONFIG_PATH" - assert_file "$config_path" + openclaw_e2e_assert_file "$config_path" CONFIG_PATH="$config_path" node --input-type=module - <<'"'"'NODE'"'"' import fs from "node:fs"; @@ -475,7 +401,7 @@ NODE run_wizard_cmd channels channels "node \"$OPENCLAW_ENTRY\" configure --section channels" send_channels_flow config_path="$OPENCLAW_CONFIG_PATH" - assert_file "$config_path" + openclaw_e2e_assert_file "$config_path" CONFIG_PATH="$config_path" node --input-type=module - <<'"'"'NODE'"'"' import fs from "node:fs"; @@ -526,7 +452,7 @@ JSON run_wizard_cmd skills "$home_dir" "node \"$OPENCLAW_ENTRY\" configure --section skills" send_skills_flow config_path="$OPENCLAW_CONFIG_PATH" - assert_file "$config_path" + openclaw_e2e_assert_file "$config_path" CONFIG_PATH="$config_path" node --input-type=module - <<'"'"'NODE'"'"' import fs from "node:fs"; @@ -552,18 +478,9 @@ if (errors.length > 0) { NODE } - assert_log_not_contains() { - local file_path="$1" - local needle="$2" - if grep -q "$needle" "$file_path"; then - echo "Unexpected log output: $needle" - exit 1 - fi - } - validate_local_basic_log() { local log_path="$1" - assert_log_not_contains "$log_path" "systemctl --user unavailable" + openclaw_e2e_assert_log_not_contains "$log_path" "systemctl --user unavailable" } run_case_local_basic diff --git a/scripts/lib/openclaw-e2e-instance.sh b/scripts/lib/openclaw-e2e-instance.sh index 635818d73d6..05b8bcad07e 100644 --- a/scripts/lib/openclaw-e2e-instance.sh +++ b/scripts/lib/openclaw-e2e-instance.sh @@ -20,6 +20,24 @@ openclaw_e2e_write_state_env() { printf 'export PI_CODING_AGENT_DIR=%q\n' "${PI_CODING_AGENT_DIR-}" } >"$target" } +openclaw_e2e_install_trash_shim() { + export PATH="/tmp/openclaw-bin:$PATH" + mkdir -p /tmp/openclaw-bin + cat >/tmp/openclaw-bin/trash <<'TRASH' +#!/usr/bin/env bash +set -euo pipefail +trash_dir="$HOME/.Trash" +mkdir -p "$trash_dir" +for target in "$@"; do + [ -e "$target" ] || continue + base="$(basename "$target")" + dest="$trash_dir/$base" + [ -e "$dest" ] && dest="$trash_dir/${base}-$(date +%s)-$$" + mv "$target" "$dest" +done +TRASH + chmod +x /tmp/openclaw-bin/trash +} openclaw_e2e_stop_process() { local pid="${1:-}" _ [ -n "$pid" ] || return 0 @@ -71,6 +89,16 @@ openclaw_e2e_probe_tcp() { 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_assert_file() { [ -f "$1" ] || { echo "Missing file: $1"; exit 1; }; } +openclaw_e2e_assert_dir() { [ -d "$1" ] || { echo "Missing dir: $1"; exit 1; }; } +openclaw_e2e_assert_log_not_contains() { + ! grep -q "$2" "$1" || { echo "Unexpected log output: $2"; exit 1; } +} +openclaw_e2e_run_logged() { + local label="$1" log_path="/tmp/openclaw-onboard-${1}.log" + shift + "$@" >"$log_path" 2>&1 || { cat "$log_path"; exit 1; } +} openclaw_e2e_dump_logs() { local path for path in "$@"; do