Files
openclaw/scripts/e2e/lib/onboard/scenario.sh
2026-04-29 11:49:45 +01:00

282 lines
7.3 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
trap "" PIPE
export TERM=xterm-256color
source scripts/lib/openclaw-e2e-instance.sh
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_FUNCTION_B64:?missing OPENCLAW_TEST_STATE_FUNCTION_B64}"
ONBOARD_FLAGS="--flow quickstart --auth-choice skip --skip-channels --skip-skills --skip-daemon --skip-ui"
OPENCLAW_ENTRY="$(openclaw_e2e_resolve_entrypoint)"
export OPENCLAW_ENTRY
# Provide a minimal trash shim to avoid noisy "missing trash" logs in containers.
openclaw_e2e_install_trash_shim
send() {
local payload="$1"
local delay="${2:-0.4}"
# Let prompts render before sending keystrokes.
sleep "$delay"
printf "%b" "$payload" >&3 2>/dev/null || true
}
wait_for_log() {
local needle="$1"
local timeout_s="${2:-45}"
local quiet_on_timeout="${3:-false}"
local start_s
start_s="$(date +%s)"
while true; do
if [ -n "${WIZARD_LOG_PATH:-}" ] && [ -f "$WIZARD_LOG_PATH" ]; then
if grep -a -F -q "$needle" "$WIZARD_LOG_PATH"; then
return 0
fi
if node scripts/e2e/lib/onboard/log-contains.mjs "$WIZARD_LOG_PATH" "$needle"; then
return 0
fi
fi
if [ $(($(date +%s) - start_s)) -ge "$timeout_s" ]; then
if [ "$quiet_on_timeout" = "true" ]; then
return 1
fi
echo "Timeout waiting for log: $needle"
if [ -n "${WIZARD_LOG_PATH:-}" ] && [ -f "$WIZARD_LOG_PATH" ]; then
tail -n 140 "$WIZARD_LOG_PATH" || true
fi
return 1
fi
sleep 0.2
done
}
start_gateway() {
GATEWAY_PID="$(openclaw_e2e_start_gateway "$OPENCLAW_ENTRY" 18789 /tmp/gateway-e2e.log)"
}
wait_for_gateway() {
for _ in $(seq 1 20); do
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
if [ -n "${GATEWAY_PID:-}" ] && kill -0 "$GATEWAY_PID" 2>/dev/null; then
return 0
fi
fi
sleep 1
done
echo "Gateway failed to start"
cat /tmp/gateway-e2e.log || true
return 1
}
stop_gateway() {
openclaw_e2e_stop_process "$1"
}
run_wizard_cmd() {
local case_name="$1"
local state_ref="$2"
local command="$3"
local send_fn="$4"
local with_gateway="${5:-false}"
local validate_fn="${6:-}"
echo "== Wizard case: $case_name =="
set_isolated_openclaw_env "$state_ref"
input_fifo="$(mktemp -u "/tmp/openclaw-onboard-${case_name}.XXXXXX")"
mkfifo "$input_fifo"
local log_path="/tmp/openclaw-onboard-${case_name}.log"
WIZARD_LOG_PATH="$log_path"
export WIZARD_LOG_PATH
# Run under script to keep an interactive TTY for clack prompts.
script -q -f -c "$command" "$log_path" <"$input_fifo" >/dev/null 2>&1 &
wizard_pid=$!
exec 3>"$input_fifo"
local gw_pid=""
if [ "$with_gateway" = "true" ]; then
start_gateway
gw_pid="$GATEWAY_PID"
wait_for_gateway
fi
"$send_fn"
if ! wait "$wizard_pid"; then
wizard_status=$?
exec 3>&-
rm -f "$input_fifo"
stop_gateway "$gw_pid"
echo "Wizard exited with status $wizard_status"
if [ -f "$log_path" ]; then
tail -n 160 "$log_path" || true
fi
exit "$wizard_status"
fi
exec 3>&-
rm -f "$input_fifo"
stop_gateway "$gw_pid"
if [ -n "$validate_fn" ]; then
"$validate_fn" "$log_path"
fi
}
run_wizard() {
local case_name="$1"
local state_ref="$2"
local send_fn="$3"
local validate_fn="${4:-}"
# Default onboarding command wrapper.
run_wizard_cmd "$case_name" "$state_ref" "node \"$OPENCLAW_ENTRY\" onboard $ONBOARD_FLAGS" "$send_fn" true "$validate_fn"
}
assert_onboard_config() {
local scenario="$1"
shift
openclaw_e2e_assert_file "$OPENCLAW_CONFIG_PATH"
node scripts/e2e/lib/onboard/assert-config.mjs "$scenario" "$OPENCLAW_CONFIG_PATH" "$@"
}
set_isolated_openclaw_env() {
local state_ref="$1"
openclaw_test_state_create "$state_ref" empty
}
select_skip_hooks() {
# Hooks multiselect: pick "Skip for now".
wait_for_log "Enable hooks?" 60
send $' \r' 0.6
}
send_local_basic() {
# Risk acknowledgement (default is "No").
wait_for_log "Continue?" 60
send $'y\r' 0.6
# Non-interactive flow; no gateway-location prompt.
select_skip_hooks
}
send_reset_config_only() {
# Risk acknowledgement (default is "No").
wait_for_log "Continue?" 40
send $'y\r' 0.8
# Select reset flow for existing config.
wait_for_log "Config handling" 40
send $'\e[B' 0.3
send $'\e[B' 0.3
send $'\r' 0.4
# Reset scope -> Config only (default).
wait_for_log "Reset scope" 40
send $'\r' 0.4
select_skip_hooks
}
send_channels_flow() {
# Configure channels via configure wizard. Use the remove-config branch for
# a stable no-op smoke path when the config starts empty.
wait_for_log "Where will the Gateway run?" 120
send $'\r' 0.6
wait_for_log "Configure/link" 120
send $'\e[B\r' 0.8
# Keep stdin open until wizard exits.
send "" 2.0
}
send_skills_flow() {
# configure --section skills still runs the configure wizard.
wait_for_log "Where will the Gateway run?" 120
send $'\r' 0.6
wait_for_log "Configure skills now?" 120
send $'n\r' 0.8
send "" 2.0
}
run_case_local_basic() {
set_isolated_openclaw_env local-basic
openclaw_e2e_run_logged local-basic node "$OPENCLAW_ENTRY" onboard \
--non-interactive \
--accept-risk \
--flow quickstart \
--mode local \
--skip-channels \
--skip-skills \
--skip-daemon \
--skip-ui \
--skip-health
# Assert config + workspace scaffolding.
workspace_dir="$OPENCLAW_STATE_DIR/workspace"
sessions_dir="$OPENCLAW_STATE_DIR/agents/main/sessions"
openclaw_e2e_assert_dir "$sessions_dir"
for file in AGENTS.md BOOTSTRAP.md IDENTITY.md SOUL.md TOOLS.md USER.md; do
openclaw_e2e_assert_file "$workspace_dir/$file"
done
assert_onboard_config local-basic "$workspace_dir"
}
run_case_remote_non_interactive() {
set_isolated_openclaw_env remote-non-interactive
# Smoke test non-interactive remote config write.
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 \
--skip-skills \
--skip-health
assert_onboard_config remote-non-interactive
}
run_case_reset() {
set_isolated_openclaw_env reset-config
node scripts/e2e/lib/onboard/write-config.mjs reset "$OPENCLAW_CONFIG_PATH"
openclaw_e2e_run_logged reset-config node "$OPENCLAW_ENTRY" onboard \
--non-interactive \
--accept-risk \
--flow quickstart \
--mode local \
--reset \
--skip-channels \
--skip-skills \
--skip-daemon \
--skip-ui \
--skip-health
assert_onboard_config reset
}
run_case_channels() {
# Channels-only configure flow.
run_wizard_cmd channels channels "node \"$OPENCLAW_ENTRY\" configure --section channels" send_channels_flow
assert_onboard_config channels
}
run_case_skills() {
local home_dir
set_isolated_openclaw_env skills
home_dir="$HOME"
node scripts/e2e/lib/onboard/write-config.mjs skills "$OPENCLAW_CONFIG_PATH"
run_wizard_cmd skills "$home_dir" "node \"$OPENCLAW_ENTRY\" configure --section skills" send_skills_flow
assert_onboard_config skills
}
validate_local_basic_log() {
local log_path="$1"
openclaw_e2e_assert_log_not_contains "$log_path" "systemctl --user unavailable"
}
run_case_local_basic
run_case_remote_non_interactive
run_case_reset
run_case_channels
run_case_skills