ci: let telegram e2e use package artifacts

This commit is contained in:
Peter Steinberger
2026-04-27 05:09:10 +01:00
parent 720ab99307
commit 09107e0b7f
6 changed files with 151 additions and 39 deletions

View File

@@ -4,10 +4,20 @@ on:
workflow_dispatch:
inputs:
package_spec:
description: Published OpenClaw package spec to test
description: Published OpenClaw package spec to test when no artifact is supplied
required: true
default: openclaw@beta
type: string
package_label:
description: Optional display label for an artifact-backed package candidate
required: false
default: ""
type: string
package_artifact_name:
description: Advanced package-under-test artifact name; leave blank for registry install
required: false
default: ""
type: string
provider_mode:
description: QA provider mode
required: true
@@ -23,9 +33,19 @@ on:
workflow_call:
inputs:
package_spec:
description: Published OpenClaw package spec to test
description: Published OpenClaw package spec to test when no artifact is supplied
required: true
type: string
package_artifact_name:
description: Optional package-under-test artifact from the current workflow run
required: false
default: ""
type: string
package_label:
description: Optional display label for an artifact-backed package candidate
required: false
default: ""
type: string
provider_mode:
description: QA provider mode
required: false
@@ -58,7 +78,7 @@ env:
jobs:
run_npm_telegram_beta_e2e:
name: Run published npm Telegram E2E
name: Run package Telegram E2E
runs-on: blacksmith-32vcpu-ubuntu-2404
timeout-minutes: 60
environment: qa-live-shared
@@ -101,6 +121,7 @@ jobs:
- name: Validate inputs and secrets
env:
PACKAGE_SPEC: ${{ inputs.package_spec }}
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
PROVIDER_MODE: ${{ inputs.provider_mode }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
@@ -109,9 +130,11 @@ jobs:
run: |
set -euo pipefail
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
exit 1
if [[ -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
if [[ ! "${PACKAGE_SPEC}" =~ ^openclaw@(beta|latest|[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*|-beta\.[1-9][0-9]*)?)$ ]]; then
echo "package_spec must be openclaw@beta, openclaw@latest, or an exact OpenClaw release version; got: ${PACKAGE_SPEC}" >&2
exit 1
fi
fi
case "${PROVIDER_MODE}" in
mock-openai | live-frontier) ;;
@@ -135,7 +158,14 @@ jobs:
require_var OPENAI_API_KEY
fi
- name: Run npm Telegram beta E2E
- name: Download package-under-test artifact
if: inputs.package_artifact_name != ''
uses: actions/download-artifact@v8
with:
name: ${{ inputs.package_artifact_name }}
path: .artifacts/telegram-package-under-test
- name: Run package Telegram E2E
id: run_lane
shell: bash
env:
@@ -143,6 +173,7 @@ jobs:
OPENCLAW_SKIP_DOCKER_BUILD: "1"
OPENCLAW_DOCKER_E2E_IMAGE: openclaw-docker-e2e:local
OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.package_spec }}
OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL: ${{ inputs.package_label }}
OPENCLAW_NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.provider_mode }}
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_SOURCE: convex
OPENCLAW_NPM_TELEGRAM_CREDENTIAL_ROLE: ci
@@ -151,6 +182,7 @@ jobs:
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_TELEGRAM_CAPTURE_CONTENT: "1"
INPUT_SCENARIO: ${{ inputs.scenario }}
PACKAGE_ARTIFACT_NAME: ${{ inputs.package_artifact_name || '' }}
run: |
set -euo pipefail
@@ -158,6 +190,20 @@ jobs:
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
export OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR="${output_dir}"
if [[ -n "${PACKAGE_ARTIFACT_NAME// }" ]]; then
mapfile -t package_tgzs < <(find .artifacts/telegram-package-under-test -type f -name "*.tgz" | sort)
if [[ "${#package_tgzs[@]}" -ne 1 ]]; then
echo "package artifact ${PACKAGE_ARTIFACT_NAME} must contain exactly one .tgz; found ${#package_tgzs[@]}" >&2
exit 1
fi
export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgzs[0]}"
if [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$(basename "${package_tgzs[0]}")"
fi
elif [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}"
fi
if [[ -n "${INPUT_SCENARIO// }" ]]; then
export OPENCLAW_NPM_TELEGRAM_SCENARIOS="${INPUT_SCENARIO}"
fi

View File

@@ -65,7 +65,7 @@ on:
default: ""
type: string
telegram_mode:
description: Optional published-npm Telegram QA lane
description: Optional Telegram QA lane for the resolved package candidate
required: true
default: none
type: choice
@@ -125,7 +125,7 @@ on:
default: ""
type: string
telegram_mode:
description: Optional published-npm Telegram QA lane
description: Optional Telegram QA lane for the resolved package candidate
required: false
default: none
type: string
@@ -366,10 +366,6 @@ jobs:
telegram_enabled=false
if [[ "$TELEGRAM_MODE" != "none" ]]; then
if [[ "$SOURCE" != "npm" ]]; then
echo "telegram_mode requires source=npm because the Telegram workflow installs a published package spec." >&2
exit 1
fi
telegram_enabled=true
fi
@@ -476,12 +472,14 @@ jobs:
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
npm_telegram:
name: Published npm Telegram acceptance
name: Telegram package acceptance
needs: resolve_package
if: needs.resolve_package.outputs.telegram_enabled == 'true'
uses: ./.github/workflows/npm-telegram-beta-e2e.yml
with:
package_spec: ${{ inputs.package_spec }}
package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}
package_label: openclaw@${{ needs.resolve_package.outputs.package_version }}
provider_mode: ${{ needs.resolve_package.outputs.telegram_mode }}
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Installs a published OpenClaw npm package in Docker, performs Telegram
# Installs an OpenClaw package candidate in Docker, performs Telegram
# onboarding/doctor recovery, then runs the Telegram QA live harness.
set -euo pipefail
@@ -9,6 +9,8 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-npm-telegram-live-e2e" OPENCLAW_NPM_TELEGRAM_LIVE_E2E_IMAGE)"
DOCKER_TARGET="${OPENCLAW_NPM_TELEGRAM_DOCKER_TARGET:-build}"
PACKAGE_SPEC="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC:-openclaw@beta}"
PACKAGE_TGZ="${OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ:-${OPENCLAW_CURRENT_PACKAGE_TGZ:-}}"
PACKAGE_LABEL="${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL:-}"
OUTPUT_DIR="${OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR:-.artifacts/qa-e2e/npm-telegram-live}"
resolve_credential_source() {
@@ -46,7 +48,45 @@ validate_openclaw_package_spec() {
exit 1
}
validate_openclaw_package_spec "$PACKAGE_SPEC"
resolve_package_tgz() {
local candidate="$1"
if [ -z "$candidate" ]; then
return 0
fi
if [ ! -f "$candidate" ]; then
echo "OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ must point to an existing .tgz file; got: $candidate" >&2
exit 1
fi
case "$candidate" in
*.tgz) ;;
*)
echo "OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ must point to a .tgz file; got: $candidate" >&2
exit 1
;;
esac
local dir
local base
dir="$(cd "$(dirname "$candidate")" && pwd)"
base="$(basename "$candidate")"
printf "%s/%s" "$dir" "$base"
}
package_mount_args=()
package_install_source="$PACKAGE_SPEC"
resolved_package_tgz="$(resolve_package_tgz "$PACKAGE_TGZ")"
if [ -n "$resolved_package_tgz" ]; then
package_install_source="/package-under-test/$(basename "$resolved_package_tgz")"
package_mount_args=(-v "$resolved_package_tgz:$package_install_source:ro")
else
validate_openclaw_package_spec "$PACKAGE_SPEC"
fi
if [ -z "$PACKAGE_LABEL" ]; then
if [ -n "$resolved_package_tgz" ]; then
PACKAGE_LABEL="$(basename "$resolved_package_tgz")"
else
PACKAGE_LABEL="$PACKAGE_SPEC"
fi
fi
docker_e2e_build_or_reuse "$IMAGE_NAME" npm-telegram-live "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "$DOCKER_TARGET"
docker_e2e_harness_mount_args
@@ -64,6 +104,7 @@ fi
docker_env=(
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0
-e OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC="$PACKAGE_SPEC"
-e OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$PACKAGE_LABEL"
-e OPENCLAW_NPM_TELEGRAM_OUTPUT_DIR="$OUTPUT_DIR"
-e OPENCLAW_NPM_TELEGRAM_FAST="${OPENCLAW_NPM_TELEGRAM_FAST:-1}"
)
@@ -124,10 +165,12 @@ run_logged() {
>"$run_log"
}
echo "Running published npm Telegram live Docker E2E ($PACKAGE_SPEC)..."
echo "Running package Telegram live Docker E2E ($PACKAGE_LABEL)..."
run_logged docker run --rm \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC="$PACKAGE_SPEC" \
-e OPENCLAW_NPM_TELEGRAM_INSTALL_SOURCE="$package_install_source" \
-e OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$PACKAGE_LABEL" \
"${package_mount_args[@]}" \
-v "$npm_prefix_host:/npm-global" \
-i "$IMAGE_NAME" bash -s <<'EOF'
set -euo pipefail
@@ -136,15 +179,16 @@ export HOME="$(mktemp -d "/tmp/openclaw-npm-telegram-install.XXXXXX")"
export NPM_CONFIG_PREFIX="/npm-global"
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
package_spec="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC:?missing OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}"
echo "Installing ${package_spec}..."
npm install -g "$package_spec" --no-fund --no-audit
install_source="${OPENCLAW_NPM_TELEGRAM_INSTALL_SOURCE:?missing OPENCLAW_NPM_TELEGRAM_INSTALL_SOURCE}"
package_label="${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL:-$install_source}"
echo "Installing ${package_label} from ${install_source}..."
npm install -g "$install_source" --no-fund --no-audit
command -v openclaw
openclaw --version
EOF
# Mount only test harness/plugin QA sources; the SUT itself is the npm install.
# Mount only test harness/plugin QA sources; the SUT itself is the installed package candidate.
run_logged docker run --rm \
"${docker_env[@]}" \
-v "$ROOT_DIR/.artifacts:/app/.artifacts" \
@@ -161,7 +205,7 @@ export OPENCLAW_NPM_TELEGRAM_REPO_ROOT="/app"
dump_hotpath_logs() {
local status="$1"
echo "installed npm onboarding recovery hot path failed with exit code $status" >&2
echo "installed-package onboarding recovery hot path failed with exit code $status" >&2
for file in \
/tmp/openclaw-npm-telegram-onboard.json \
/tmp/openclaw-npm-telegram-channel-add.log \
@@ -178,11 +222,11 @@ trap 'status=$?; dump_hotpath_logs "$status"; exit "$status"' ERR
command -v openclaw
openclaw --version
# The mounted QA harness imports openclaw/plugin-sdk; point that package import
# at the installed npm package without copying source into the test image.
# at the installed package without copying source into the test image.
mkdir -p /app/node_modules
ln -sfn /npm-global/lib/node_modules/openclaw /app/node_modules/openclaw
echo "Running installed npm onboarding recovery hot path..."
echo "Running installed-package onboarding recovery hot path..."
OPENAI_API_KEY="${OPENAI_API_KEY:-sk-openclaw-npm-telegram-hotpath}" openclaw onboard --non-interactive --accept-risk \
--mode local \
--auth-choice openai-api-key \
@@ -210,4 +254,4 @@ trap - ERR
tsx scripts/e2e/npm-telegram-live-runner.ts
EOF
echo "published npm Telegram live Docker E2E passed ($PACKAGE_SPEC)"
echo "package Telegram live Docker E2E passed ($PACKAGE_LABEL)"

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env -S node --import tsx
// Telegram npm-live Docker harness.
// Runs QA live transport code against the published package installed in Docker.
// Telegram package Docker harness.
// Runs QA live transport code against the package candidate installed in Docker.
import fs from "node:fs/promises";
import path from "node:path";
@@ -78,9 +78,9 @@ async function main() {
credentialRole: resolveCredentialRole(process.env),
});
process.stdout.write(`NPM Telegram QA report: ${result.reportPath}\n`);
process.stdout.write(`NPM Telegram QA summary: ${result.summaryPath}\n`);
process.stdout.write(`NPM Telegram QA observed messages: ${result.observedMessagesPath}\n`);
process.stdout.write(`Package Telegram QA report: ${result.reportPath}\n`);
process.stdout.write(`Package Telegram QA summary: ${result.summaryPath}\n`);
process.stdout.write(`Package Telegram QA observed messages: ${result.observedMessagesPath}\n`);
if (
!parseBoolean(process.env.OPENCLAW_NPM_TELEGRAM_ALLOW_FAILURES) &&
result.scenarios.some((scenario) => scenario.status === "fail")
@@ -101,7 +101,7 @@ async function formatRunnerErrorMessage(error: unknown) {
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(async (error) => {
process.stderr.write(
`npm telegram live e2e failed: ${await formatRunnerErrorMessage(error)}\n`,
`package telegram live e2e failed: ${await formatRunnerErrorMessage(error)}\n`,
);
process.exitCode = 1;
});

View File

@@ -7,7 +7,7 @@ import { __testing } from "../../scripts/e2e/npm-telegram-live-runner.ts";
const TEST_DIR = path.dirname(fileURLToPath(import.meta.url));
const DOCKER_SCRIPT_PATH = path.resolve(TEST_DIR, "../../scripts/e2e/npm-telegram-live-docker.sh");
describe("npm Telegram live Docker E2E", () => {
describe("package Telegram live Docker E2E", () => {
it("supports npm-specific Convex credential aliases", () => {
const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8");
@@ -28,18 +28,33 @@ describe("npm Telegram live Docker E2E", () => {
expect(script).toContain('printf "convex"');
});
it("installs the npm package before forwarding runtime secrets", () => {
it("installs the package candidate before forwarding runtime secrets", () => {
const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8");
const installRunStart = script.indexOf('echo "Running published npm Telegram live Docker E2E');
const installRunStart = script.indexOf('echo "Running package Telegram live Docker E2E');
const installRunEnd = script.indexOf('run_logged docker run --rm \\\n "${docker_env[@]}"');
const installRun = script.slice(installRunStart, installRunEnd);
expect(installRun).toContain('npm install -g "$package_spec" --no-fund --no-audit');
expect(installRun).toContain('npm install -g "$install_source" --no-fund --no-audit');
expect(installRun).toContain('"${package_mount_args[@]}"');
expect(installRun).not.toContain('"${docker_env[@]}"');
expect(script).toContain('if [ -z "$credential_role" ] && [ -n "${CI:-}" ]');
expect(script).toContain('credential_role="ci"');
});
it("can install a resolved package tarball instead of a registry spec", () => {
const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8");
expect(script).toContain("OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ");
expect(script).toContain("OPENCLAW_CURRENT_PACKAGE_TGZ");
expect(script).toContain(
'package_mount_args=(-v "$resolved_package_tgz:$package_install_source:ro")',
);
expect(script).toContain('validate_openclaw_package_spec "$PACKAGE_SPEC"');
expect(script.indexOf('if [ -n "$resolved_package_tgz" ]; then')).toBeLessThan(
script.indexOf('validate_openclaw_package_spec "$PACKAGE_SPEC"'),
);
});
it("lets npm-specific credential aliases override shared QA env", () => {
expect(
__testing.resolveCredentialSource({

View File

@@ -34,15 +34,21 @@ describe("package acceptance workflow", () => {
);
});
it("offers bounded product profiles and keeps Telegram published-npm only", () => {
it("offers bounded product profiles and can run Telegram against the resolved artifact", () => {
const workflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");
expect(workflow).toContain("suite_profile:");
expect(workflow).toContain("npm-onboard-channel-agent gateway-network config-reload");
expect(workflow).toContain("install-e2e npm-onboard-channel-agent doctor-switch");
expect(workflow).toContain("include_release_path_suites=true");
expect(workflow).toContain("telegram_mode requires source=npm");
expect(workflow).not.toContain("telegram_mode requires source=npm");
expect(workflow).toContain("uses: ./.github/workflows/npm-telegram-beta-e2e.yml");
expect(workflow).toContain(
"package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }}",
);
expect(workflow).toContain(
"package_label: openclaw@${{ needs.resolve_package.outputs.package_version }}",
);
});
});
@@ -62,10 +68,13 @@ describe("package artifact reuse", () => {
expect(action).toContain("name: ${{ inputs.package-artifact-name }}");
});
it("allows the npm Telegram lane to run from reusable package acceptance", () => {
it("allows the Telegram lane to run from reusable package acceptance artifacts", () => {
const workflow = readFileSync(NPM_TELEGRAM_WORKFLOW, "utf8");
expect(workflow).toContain("workflow_call:");
expect(workflow).toContain("package_artifact_name:");
expect(workflow).toContain("Download package-under-test artifact");
expect(workflow).toContain("OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ");
expect(workflow).toContain("provider_mode:");
expect(workflow).toContain("provider_mode must be mock-openai or live-frontier");
});