refactor: extract e2e scenario fixtures

This commit is contained in:
Peter Steinberger
2026-04-29 10:25:27 +01:00
parent 3b10b8cf74
commit a98a4e6ca5
6 changed files with 136 additions and 145 deletions

View File

@@ -24,34 +24,7 @@ trap cleanup EXIT
run_with_timeout() {
local timeout_ms="$1"
shift
node - "$timeout_ms" "$@" <<'NODE'
const { spawnSync } = require("node:child_process");
const timeout = Number(process.argv[2]);
const command = process.argv[3];
const args = process.argv.slice(4);
const result = spawnSync(command, args, {
encoding: "utf8",
env: process.env,
timeout,
});
if (result.stdout) {
process.stdout.write(result.stdout);
}
if (result.stderr) {
process.stderr.write(result.stderr);
}
if (result.error) {
console.error(`command failed: ${command}: ${result.error.message}`);
process.exit(1);
}
if (result.signal) {
console.error(`command terminated: ${command}: ${result.signal}`);
process.exit(1);
}
process.exit(result.status ?? 0);
NODE
node scripts/e2e/lib/bun-global-install/assertions.mjs run-with-timeout "$timeout_ms" "$@"
}
restore_dist_from_image() {
@@ -163,29 +136,7 @@ main() {
echo "==> OpenClaw image providers through Bun global install"
local providers_json
providers_json="$(run_with_timeout "$COMMAND_TIMEOUT_MS" "$openclaw_bin" infer image providers --json)"
OPENCLAW_IMAGE_PROVIDERS_JSON="$providers_json" node - <<'NODE'
const raw = process.env.OPENCLAW_IMAGE_PROVIDERS_JSON ?? "";
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
console.error(raw);
throw new Error(`image providers output is not JSON: ${error.message}`);
}
if (!Array.isArray(parsed)) {
throw new Error("image providers output must be a JSON array");
}
if (parsed.length === 0) {
throw new Error("image providers output is empty");
}
const ids = new Set(parsed.map((entry) => entry && typeof entry.id === "string" ? entry.id : ""));
for (const expected of ["google", "openai", "xai"]) {
if (!ids.has(expected)) {
throw new Error(`image providers output is missing bundled provider '${expected}'`);
}
}
console.log(`bun-global-install-smoke: image providers OK (${parsed.length} providers)`);
NODE
OPENCLAW_IMAGE_PROVIDERS_JSON="$providers_json" node scripts/e2e/lib/bun-global-install/assertions.mjs assert-image-providers
}
main "$@"

View File

@@ -0,0 +1,57 @@
import { spawnSync } from "node:child_process";
const usage = () => {
console.error("Usage: assertions.mjs <run-with-timeout|assert-image-providers> [...]");
process.exit(2);
};
const [mode, ...args] = process.argv.slice(2);
if (mode === "run-with-timeout") {
const [timeoutMs, command, ...commandArgs] = args;
const timeout = Number(timeoutMs);
if (!Number.isFinite(timeout) || timeout <= 0 || !command) {
usage();
}
const result = spawnSync(command, commandArgs, { encoding: "utf8", env: process.env, timeout });
process.stdout.write(result.stdout ?? "");
process.stderr.write(result.stderr ?? "");
if (result.error) {
console.error(`command failed: ${command}: ${result.error.message}`);
process.exit(1);
}
if (result.signal) {
console.error(`command terminated: ${command}: ${result.signal}`);
process.exit(1);
}
process.exit(result.status ?? 0);
}
if (mode === "assert-image-providers") {
const raw = process.env.OPENCLAW_IMAGE_PROVIDERS_JSON ?? "";
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
console.error(raw);
const message = error instanceof Error ? error.message : String(error);
throw new Error(`image providers output is not JSON: ${message}`, { cause: error });
}
if (!Array.isArray(parsed)) {
throw new Error("image providers output must be a JSON array");
}
if (parsed.length === 0) {
throw new Error("image providers output is empty");
}
const ids = new Set(parsed.map((entry) => (typeof entry?.id === "string" ? entry.id : "")));
for (const expected of ["google", "openai", "xai"]) {
if (!ids.has(expected)) {
throw new Error(`image providers output is missing bundled provider '${expected}'`);
}
}
console.log(`bun-global-install-smoke: image providers OK (${parsed.length} providers)`);
process.exit(0);
}
usage();

View File

@@ -24,60 +24,7 @@ bundled_channel_install_package /tmp/openclaw-load-failure-install.log
root="$(bundled_channel_package_root)"
plugin_dir="$root/dist/extensions/load-failure-alpha"
mkdir -p "$plugin_dir"
cat >"$plugin_dir/package.json" <<'JSON'
{
"name": "@openclaw/load-failure-alpha",
"version": "2026.4.21",
"private": true,
"type": "module",
"openclaw": {
"extensions": ["./index.js"],
"setupEntry": "./setup-entry.js"
}
}
JSON
cat >"$plugin_dir/openclaw.plugin.json" <<'JSON'
{
"id": "load-failure-alpha",
"channels": ["load-failure-alpha"],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}
JSON
cat >"$plugin_dir/index.js" <<'JS'
export default {
kind: "bundled-channel-entry",
id: "load-failure-alpha",
name: "Load Failure Alpha",
description: "Load Failure Alpha",
register() {},
loadChannelSecrets() {
globalThis.__loadFailureSecrets = (globalThis.__loadFailureSecrets ?? 0) + 1;
throw new Error("synthetic channel secrets failure");
},
loadChannelPlugin() {
globalThis.__loadFailurePlugin = (globalThis.__loadFailurePlugin ?? 0) + 1;
throw new Error("synthetic channel plugin failure");
}
};
JS
cat >"$plugin_dir/setup-entry.js" <<'JS'
export default {
kind: "bundled-channel-setup-entry",
loadSetupSecrets() {
globalThis.__loadFailureSetupSecrets = (globalThis.__loadFailureSetupSecrets ?? 0) + 1;
throw new Error("synthetic setup secrets failure");
},
loadSetupPlugin() {
globalThis.__loadFailureSetup = (globalThis.__loadFailureSetup ?? 0) + 1;
throw new Error("synthetic setup plugin failure");
}
};
JS
node scripts/e2e/lib/bundled-channel/write-load-failure-fixture.mjs "$plugin_dir"
echo "Loading synthetic failing bundled channel through packaged loader..."
node scripts/e2e/lib/bundled-channel/loader-probe.mjs load-failure "$root" load-failure-alpha

View File

@@ -0,0 +1,42 @@
import fs from "node:fs";
import path from "node:path";
const [pluginDir] = process.argv.slice(2);
if (!pluginDir) {
throw new Error("usage: write-load-failure-fixture.mjs <plugin-dir>");
}
const writeJson = (filename, contents) =>
fs.writeFileSync(path.join(pluginDir, filename), `${JSON.stringify(contents, null, 2)}\n`);
fs.mkdirSync(pluginDir, { recursive: true });
writeJson("package.json", {
name: "@openclaw/load-failure-alpha",
version: "2026.4.21",
private: true,
type: "module",
openclaw: { extensions: ["./index.js"], setupEntry: "./setup-entry.js" },
});
writeJson("openclaw.plugin.json", {
id: "load-failure-alpha",
channels: ["load-failure-alpha"],
configSchema: { type: "object", additionalProperties: false, properties: {} },
});
fs.writeFileSync(
path.join(pluginDir, "index.js"),
`export default {
kind: "bundled-channel-entry", id: "load-failure-alpha", name: "Load Failure Alpha", description: "Load Failure Alpha", register() {},
loadChannelSecrets() { globalThis.__loadFailureSecrets = (globalThis.__loadFailureSecrets ?? 0) + 1; throw new Error("synthetic channel secrets failure"); },
loadChannelPlugin() { globalThis.__loadFailurePlugin = (globalThis.__loadFailurePlugin ?? 0) + 1; throw new Error("synthetic channel plugin failure"); }
};
`,
);
fs.writeFileSync(
path.join(pluginDir, "setup-entry.js"),
`export default {
kind: "bundled-channel-setup-entry",
loadSetupSecrets() { globalThis.__loadFailureSetupSecrets = (globalThis.__loadFailureSetupSecrets ?? 0) + 1; throw new Error("synthetic setup secrets failure"); },
loadSetupPlugin() { globalThis.__loadFailureSetup = (globalThis.__loadFailureSetup ?? 0) + 1; throw new Error("synthetic setup plugin failure"); }
};
`,
);

View File

@@ -161,6 +161,13 @@ run_wizard() {
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
@@ -230,16 +237,14 @@ run_case_local_basic() {
# Assert config + workspace scaffolding.
workspace_dir="$OPENCLAW_STATE_DIR/workspace"
config_path="$OPENCLAW_CONFIG_PATH"
sessions_dir="$OPENCLAW_STATE_DIR/agents/main/sessions"
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
openclaw_e2e_assert_file "$workspace_dir/$file"
done
node scripts/e2e/lib/onboard/assert-config.mjs local-basic "$config_path" "$workspace_dir"
assert_onboard_config local-basic "$workspace_dir"
}
@@ -253,25 +258,12 @@ run_case_remote_non_interactive() {
--skip-skills \
--skip-health
config_path="$OPENCLAW_CONFIG_PATH"
openclaw_e2e_assert_file "$config_path"
node scripts/e2e/lib/onboard/assert-config.mjs remote-non-interactive "$config_path"
assert_onboard_config remote-non-interactive
}
run_case_reset() {
set_isolated_openclaw_env reset-config
# Seed a remote config to exercise reset path.
cat >"$OPENCLAW_CONFIG_PATH" <<'JSON'
{
"meta": {},
"agents": { "defaults": { "workspace": "/root/old" } },
"gateway": {
"mode": "remote",
"remote": { "url": "ws://old.example:18789", "token": "old-token" }
}
}
JSON
node scripts/e2e/lib/onboard/write-config.mjs reset "$OPENCLAW_CONFIG_PATH"
openclaw_e2e_run_logged reset-config node "$OPENCLAW_ENTRY" onboard \
--non-interactive \
@@ -285,43 +277,25 @@ JSON
--skip-ui \
--skip-health
config_path="$OPENCLAW_CONFIG_PATH"
openclaw_e2e_assert_file "$config_path"
node scripts/e2e/lib/onboard/assert-config.mjs reset "$config_path"
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
config_path="$OPENCLAW_CONFIG_PATH"
openclaw_e2e_assert_file "$config_path"
node scripts/e2e/lib/onboard/assert-config.mjs channels "$config_path"
assert_onboard_config channels
}
run_case_skills() {
local home_dir
set_isolated_openclaw_env skills
home_dir="$HOME"
# Seed skills config to ensure it survives the wizard.
cat >"$OPENCLAW_CONFIG_PATH" <<'JSON'
{
"meta": {},
"skills": {
"allowBundled": ["__none__"],
"install": { "nodeManager": "bun" }
}
}
JSON
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
config_path="$OPENCLAW_CONFIG_PATH"
openclaw_e2e_assert_file "$config_path"
node scripts/e2e/lib/onboard/assert-config.mjs skills "$config_path"
assert_onboard_config skills
}
validate_local_basic_log() {

View File

@@ -0,0 +1,20 @@
import fs from "node:fs";
const [scenario, configPath] = process.argv.slice(2);
if (!scenario || !configPath) {
throw new Error("usage: write-config.mjs <reset|skills> <config-path>");
}
const config = {
reset: {
meta: {},
agents: { defaults: { workspace: "/root/old" } },
gateway: { mode: "remote", remote: { url: "ws://old.example:18789", token: "old-token" } },
},
skills: { meta: {}, skills: { allowBundled: ["__none__"], install: { nodeManager: "bun" } } },
}[scenario];
if (!config) {
throw new Error(`unknown config scenario: ${scenario}`);
}
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);