diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 588e8890ce4..7f86b2bf5a9 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -375,7 +375,7 @@ jobs: TARGET_ID: ${{ matrix.id }} run: | set -euo pipefail - npx vitest run --project e2e-live test/e2e/live/registry-targets.test.ts -t "^${TARGET_ID}$" --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/registry-targets.test.ts --selector "^${TARGET_ID}$" # The sanitizer reads raw traces only after checking the workflow-owned # runner-temp path, then writes the timing-only file into upload roots. @@ -569,9 +569,7 @@ jobs: export OPENSHELL_GATEWAY_BIN echo "Using OPENSHELL_GATEWAY_BIN=$OPENSHELL_GATEWAY_BIN" "$OPENSHELL_GATEWAY_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/openshell-gateway-auth-source-contract.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openshell-gateway-auth-source-contract.test.ts - name: Upload OpenShell gateway auth contract artifacts if: always() @@ -662,9 +660,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/mcp-bridge.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts - id: mcp_artifact_secret_scan name: Scan MCP artifacts for fixture credentials @@ -778,9 +774,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/mcp-bridge.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/mcp-bridge.test.ts - id: mcp_artifact_secret_scan name: Scan MCP artifacts for fixture credentials @@ -850,9 +844,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/skill-agent.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/skill-agent.test.ts - name: Upload skill-agent artifacts if: always() @@ -906,9 +898,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/openclaw-skill-cli.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-skill-cli.test.ts - name: Upload OpenClaw skill CLI artifacts if: always() @@ -948,9 +938,7 @@ jobs: # any future secret-bearing lane must run that file from trusted main. run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/inference-routing.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/inference-routing.test.ts - name: Upload inference routing artifacts if: always() @@ -995,9 +983,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/cloud-inference.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cloud-inference.test.ts - name: Upload cloud inference artifacts if: always() @@ -1055,7 +1041,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live test/e2e/live/gpu-e2e.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gpu-e2e.test.ts - name: Upload GPU E2E artifacts if: always() uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 @@ -1115,7 +1101,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live test/e2e/live/agent-turn-latency.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/agent-turn-latency.test.ts - name: Upload agent turn latency artifacts if: always() uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 @@ -1157,7 +1143,7 @@ jobs: set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" export OPENSHELL_BIN="$(command -v openshell || true)" - npx vitest run --project e2e-live test/e2e/live/kimi-inference-compat.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/kimi-inference-compat.test.ts - name: Upload Kimi compatibility artifacts if: always() uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 @@ -1226,7 +1212,7 @@ jobs: set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" export OPENSHELL_BIN="$(command -v openshell || true)" - npx vitest run --project e2e-live test/e2e/live/hermes-inference-switch.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-inference-switch.test.ts - name: Upload Hermes inference switch artifacts if: always() uses: NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@7768e15eb90d3ee2d33432f481dfe8747e4f6d57 @@ -1287,7 +1273,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live test/e2e/live/brave-search.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/brave-search.test.ts - name: Upload Brave search artifacts if: always() @@ -1326,7 +1312,7 @@ jobs: - name: Run Ollama auth proxy live Vitest test run: | set -euo pipefail - npx vitest run --project e2e-live test/e2e/live/ollama-auth-proxy.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/ollama-auth-proxy.test.ts - name: Upload Ollama auth proxy artifacts if: always() @@ -1384,7 +1370,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live test/e2e/live/cron-preflight-inference-local.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cron-preflight-inference-local.test.ts - name: Upload cron preflight inference.local artifacts if: always() @@ -1466,9 +1452,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/issue-4434-tui-unreachable-inference.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-4434-tui-unreachable-inference.test.ts - name: "Upload issue #4434 TUI unreachable inference artifacts" if: always() @@ -1513,9 +1497,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/credential-sanitization.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/credential-sanitization.test.ts - name: Upload credential sanitization artifacts if: always() @@ -1565,9 +1547,7 @@ jobs: NEMOCLAW_PREFERRED_API: openai-completions run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/credential-migration.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/credential-migration.test.ts - name: Upload credential migration artifacts if: always() @@ -1619,9 +1599,7 @@ jobs: run: | set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" - npx vitest run --project e2e-live \ - test/e2e/live/sessions-agents-cli.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sessions-agents-cli.test.ts - name: Upload sessions/agents CLI artifacts if: always() @@ -1672,9 +1650,7 @@ jobs: SLACK_APP_TOKEN: xapp-test-hermes-slack-app-token run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/hermes-slack-e2e.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-slack-e2e.test.ts - name: Upload Hermes Slack artifacts if: always() @@ -1722,9 +1698,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && inputs.checkout_sha == '' && (inputs.inference_mode || 'mock') != 'mock' && secrets.NVIDIA_INFERENCE_API_KEY || '' }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/hermes-e2e.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-e2e.test.ts - name: Upload Hermes live Vitest artifacts if: always() @@ -1923,9 +1897,7 @@ jobs: >"$E2E_ARTIFACT_DIR/docker-default-runtime-during.txt" fi - npx vitest run --project e2e-live \ - test/e2e/live/hermes-gpu-startup.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-gpu-startup.test.ts - name: Recover Docker daemon after Hermes GPU fallback fixture if: always() @@ -2030,9 +2002,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/hermes-e2e.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-e2e.test.ts - name: Upload Hermes dashboard live Vitest artifacts if: always() @@ -2082,9 +2052,7 @@ jobs: DISCORD_REQUIRE_MENTION: "0" run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/hermes-discord.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-discord.test.ts - name: Upload Hermes Discord artifacts if: always() @@ -2160,9 +2128,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/network-policy.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/network-policy.test.ts - name: Upload network-policy artifacts if: always() @@ -2231,9 +2197,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/common-egress-agent.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/common-egress-agent.test.ts - name: Upload common-egress agent artifacts if: always() @@ -2280,9 +2244,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/shields-config.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/shields-config.test.ts - name: Upload shields-config artifacts if: always() @@ -2328,9 +2290,7 @@ jobs: # proves two complete down/up cycles on a fresh non-root Hermes sandbox. run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/hermes-shields-config.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/hermes-shields-config.test.ts - name: Upload Hermes shields-config artifacts if: always() @@ -2390,9 +2350,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/rebuild-openclaw.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-openclaw.test.ts - name: Upload OpenClaw rebuild artifacts if: always() @@ -2445,9 +2403,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/rebuild-hermes.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts - name: Upload Hermes rebuild artifacts if: always() @@ -2500,9 +2456,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/rebuild-hermes.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/rebuild-hermes.test.ts - name: Upload Hermes stale-base rebuild artifacts if: always() @@ -2563,9 +2517,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/sandbox-rebuild.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-rebuild.test.ts - name: Upload sandbox rebuild artifacts if: always() @@ -2628,9 +2580,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/sandbox-rlimits-connect.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-rlimits-connect.test.ts - name: Upload sandbox rlimit connect artifacts if: always() @@ -2674,9 +2624,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/overlayfs-autofix.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/overlayfs-autofix.test.ts - name: Upload overlayfs autofix artifacts if: always() @@ -2738,9 +2686,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/state-backup-restore.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/state-backup-restore.test.ts - name: Upload state backup restore artifacts if: always() @@ -2801,9 +2747,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/upgrade-stale-sandbox.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/upgrade-stale-sandbox.test.ts - name: Upload upgrade stale sandbox artifacts if: always() @@ -2857,9 +2801,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/double-onboard.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/double-onboard.test.ts - name: Upload double-onboard Vitest artifacts if: always() @@ -2936,9 +2878,7 @@ jobs: # sandbox exec, /dev/nvmap, CUDA cuInit(0), and status-proof boundary. run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/jetson-nvmap-gpu.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/jetson-nvmap-gpu.test.ts - name: Upload Jetson nvmap GPU artifacts if: always() @@ -2992,9 +2932,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/concurrent-gateway-ports.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/concurrent-gateway-ports.test.ts - name: Upload concurrent gateway ports Vitest artifacts if: always() @@ -3047,9 +2985,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/onboard-resume.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/onboard-resume.test.ts - name: Upload onboard-resume artifacts if: always() @@ -3105,9 +3041,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/full-e2e.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/full-e2e.test.ts - name: Upload full-e2e artifacts if: always() @@ -3175,9 +3109,7 @@ jobs: run: | set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" - npx vitest run --project e2e-live \ - "${{ matrix.test_file }}" \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path "${{ matrix.test_file }}" - name: Upload security posture artifacts if: always() @@ -3271,9 +3203,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/cloud-onboard.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/cloud-onboard.test.ts - name: Build trusted cloud-onboard timing summary if: always() @@ -3361,9 +3291,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/gpu-double-onboard.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gpu-double-onboard.test.ts - name: Upload gpu-double-onboard artifacts if: always() @@ -3416,9 +3344,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/onboard-repair.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/onboard-repair.test.ts - name: Upload onboard-repair artifacts if: always() @@ -3474,9 +3400,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/issue-4462-scope-upgrade-approval.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-4462-scope-upgrade-approval.test.ts - name: Upload issue-4462-scope-upgrade-approval artifacts if: always() @@ -3525,9 +3449,7 @@ jobs: SLACK_APP_TOKEN_B: "xapp-fake-B-rotation-e2e" run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/token-rotation.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/token-rotation.test.ts - name: Upload token rotation artifacts if: always() @@ -3572,9 +3494,7 @@ jobs: TELEGRAM_BOT_TOKEN: "test-fake-telegram-token-e2e" run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/messaging-compatible-endpoint.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/messaging-compatible-endpoint.test.ts - name: Upload messaging compatible endpoint artifacts if: always() @@ -3645,9 +3565,7 @@ jobs: run: | set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" - npx vitest run --project e2e-live \ - test/e2e/live/openshell-gateway-upgrade.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openshell-gateway-upgrade.test.ts - name: Upload OpenShell gateway upgrade artifacts if: always() @@ -3704,9 +3622,7 @@ jobs: SLACK_CHANNEL_ID_E2E: ${{ secrets.SLACK_CHANNEL_ID_E2E }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/messaging-providers.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/messaging-providers.test.ts - name: Upload messaging providers artifacts if: always() @@ -3755,9 +3671,7 @@ jobs: COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/launchable-smoke.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/launchable-smoke.test.ts - name: Upload launchable smoke artifacts if: always() @@ -3801,9 +3715,7 @@ jobs: NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/model-router-provider-routed-inference.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/model-router-provider-routed-inference.test.ts - name: Upload Model Router provider-routed inference artifacts if: always() @@ -3846,9 +3758,7 @@ jobs: # supplies a sandbox-reachable fake OpenAI-compatible endpoint. run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/snapshot-commands.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/snapshot-commands.test.ts - name: Upload snapshot commands artifacts if: always() @@ -3912,9 +3822,7 @@ jobs: run: | set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" - npx vitest run --project e2e-live \ - test/e2e/live/sandbox-operations.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-operations.test.ts - name: Upload sandbox operations artifacts if: always() @@ -3960,9 +3868,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/sandbox-survival.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/sandbox-survival.test.ts - name: Upload sandbox survival artifacts if: always() @@ -4009,7 +3915,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live test/e2e/live/diagnostics.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/diagnostics.test.ts - name: Upload diagnostics artifacts if: always() @@ -4074,10 +3980,7 @@ jobs: test -z "${DOCKERHUB_USERNAME:-}" test -z "${DOCKERHUB_TOKEN:-}" env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN \ - npx vitest run --project e2e-live \ - test/e2e/live/openclaw-plugin-runtime-exdev.test.ts \ - --silent=false --reporter=default \ - --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-plugin-runtime-exdev.test.ts - name: Upload OpenClaw plugin runtime-deps EXDEV artifacts if: always() @@ -4155,9 +4058,7 @@ jobs: COMPATIBLE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/openclaw-tui-chat-correlation.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-tui-chat-correlation.test.ts - name: Upload OpenClaw TUI chat correlation artifacts if: always() @@ -4317,9 +4218,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ matrix.mode == 'hosted' && secrets.NVIDIA_INFERENCE_API_KEY || '' }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/openclaw-inference-switch.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-inference-switch.test.ts - name: Upload OpenClaw inference switch artifacts if: always() @@ -4375,9 +4274,7 @@ jobs: # leak-scan contract for both OpenClaw and Hermes. run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/bedrock-runtime-compatible-anthropic.test.ts - name: Upload Bedrock Runtime compatible Anthropic artifacts if: always() @@ -4423,9 +4320,7 @@ jobs: run: | set -euo pipefail export PATH="$HOME/.local/bin:$HOME/.npm-global/bin:$PATH" - npx vitest run --project e2e-live \ - test/e2e/live/gateway-health-honest.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/gateway-health-honest.test.ts - name: Upload gateway health honesty artifacts if: always() @@ -4476,7 +4371,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live test/e2e/live/device-auth-health.test.ts --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/device-auth-health.test.ts - name: Upload device auth health artifacts if: always() @@ -4544,9 +4439,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/channels-add-remove.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/channels-add-remove.test.ts - name: Upload channels add/remove artifacts if: always() @@ -4611,9 +4504,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/telegram-injection.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/telegram-injection.test.ts - name: Upload Telegram injection artifacts if: always() @@ -4690,9 +4581,7 @@ jobs: fi export OPENSHELL_BIN "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/channels-stop-start.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/channels-stop-start.test.ts - name: Upload channels stop/start artifacts if: always() @@ -4760,9 +4649,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/openclaw-slack-pairing.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-slack-pairing.test.ts - name: Upload OpenClaw Slack pairing artifacts if: always() @@ -4818,9 +4705,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/issue-2478-crash-loop-recovery.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/issue-2478-crash-loop-recovery.test.ts - name: "Upload issue #2478 crash-loop recovery artifacts" if: always() @@ -4884,9 +4769,7 @@ jobs: export OPENSHELL_BIN echo "Using OPENSHELL_BIN=$OPENSHELL_BIN" "$OPENSHELL_BIN" --version - npx vitest run --project e2e-live \ - test/e2e/live/openclaw-discord-pairing.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/openclaw-discord-pairing.test.ts - name: Upload OpenClaw Discord pairing artifacts if: always() @@ -4961,9 +4844,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/tunnel-lifecycle.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/tunnel-lifecycle.test.ts - name: Upload tunnel lifecycle artifacts if: always() @@ -5014,9 +4895,7 @@ jobs: NVIDIA_INFERENCE_API_KEY: ${{ secrets.NVIDIA_INFERENCE_API_KEY }} run: | set -euo pipefail - npx vitest run --project e2e-live \ - test/e2e/live/spark-install.test.ts \ - --silent=false --reporter=default --reporter=test/e2e/risk-signal-reporter.ts + npx tsx tools/e2e/live-vitest-invocation.mts run --test-path test/e2e/live/spark-install.test.ts - name: Upload Spark install artifacts if: always() diff --git a/test/e2e/support/e2e-operations-workflow-boundary.test.ts b/test/e2e/support/e2e-operations-workflow-boundary.test.ts index 013daea0cb9..3dab0d098ad 100644 --- a/test/e2e/support/e2e-operations-workflow-boundary.test.ts +++ b/test/e2e/support/e2e-operations-workflow-boundary.test.ts @@ -106,8 +106,13 @@ describe("E2E operations workflow boundary", () => { const workflow = readE2eOperationsWorkflow(); const job = workflow.jobs["cloud-onboard"]; job.env!.E2E_TARGET_ID = "different-job"; - const run = job.steps!.find((step) => String(step.run ?? "").includes("npx vitest"))!; - run.run = run.run!.replace("test/e2e/risk-signal-reporter.ts", "default"); + const run = job.steps!.find((step) => + String(step.run ?? "").includes("tools/e2e/live-vitest-invocation.mts run --test-path"), + )!; + run.run = run.run!.replace( + "tools/e2e/live-vitest-invocation.mts run --test-path", + "tools/e2e/live-vitest-invocation.mts runx --test-path", + ); const upload = job.steps!.find((step) => step.uses?.startsWith("NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@"), )!; diff --git a/test/e2e/support/live-vitest-invocation.test.ts b/test/e2e/support/live-vitest-invocation.test.ts new file mode 100644 index 00000000000..f8e9cb13794 --- /dev/null +++ b/test/e2e/support/live-vitest-invocation.test.ts @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { spawnSync } from "node:child_process"; + +import { describe, expect, it } from "vitest"; + +import { + buildLiveVitestArgs, + LIVE_VITEST_PROJECT, + type LiveVitestSpawner, + RISK_SIGNAL_REPORTER, + runLiveVitestCommand, + validateLiveProject, + validateLiveSelector, + validateLiveTestPath, +} from "../../../tools/e2e/live-vitest-invocation.mts"; + +describe("validateLiveProject (#6961)", () => { + it("accepts the live project and defaults to it", () => { + expect(validateLiveProject("e2e-live")).toBe(LIVE_VITEST_PROJECT); + expect(validateLiveProject(undefined)).toBe(LIVE_VITEST_PROJECT); + }); + + it("rejects any other project", () => { + for (const project of ["cli", "e2e-support", "e2e-live-extra", "integration"]) { + expect(() => validateLiveProject(project)).toThrow(/unsupported vitest project/); + } + }); +}); + +describe("validateLiveTestPath (#6961)", () => { + it("accepts a real live test path", () => { + expect(validateLiveTestPath("test/e2e/live/registry-targets.test.ts")).toBe( + "test/e2e/live/registry-targets.test.ts", + ); + }); + + it("rejects paths outside the live test root", () => { + expect(() => validateLiveTestPath("test/e2e/support/thing.test.ts")).toThrow( + /must be under test\/e2e\/live/, + ); + expect(() => validateLiveTestPath("src/lib/onboard.ts")).toThrow(/must be under/); + }); + + it("rejects '..' traversal", () => { + expect(() => validateLiveTestPath("test/e2e/live/../support/x.test.ts")).toThrow(/traverse/); + }); + + it("rejects absolute paths", () => { + expect(() => validateLiveTestPath("/etc/passwd")).toThrow(/unsupported character|absolute/); + }); + + it("rejects shell metacharacters", () => { + for (const bad of [ + "test/e2e/live/x.test.ts; rm -rf /", + "test/e2e/live/$(whoami).test.ts", + "test/e2e/live/x.test.ts && curl evil", + "test/e2e/live/`id`.test.ts", + "test/e2e/live/x.test.ts|cat", + ]) { + expect(() => validateLiveTestPath(bad)).toThrow(/unsupported character/); + } + }); + + it("requires a .test.ts file", () => { + expect(() => validateLiveTestPath("test/e2e/live/fixtures")).toThrow(/\.test\.ts/); + }); + + it("requires a non-empty path", () => { + expect(() => validateLiveTestPath("")).toThrow(/required/); + expect(() => validateLiveTestPath(undefined)).toThrow(/required/); + }); +}); + +describe("validateLiveSelector (#6961)", () => { + it("accepts anchored title patterns", () => { + expect(validateLiveSelector("^ubuntu-repo-cloud-openclaw$")).toBe( + "^ubuntu-repo-cloud-openclaw$", + ); + expect(validateLiveSelector("^skill-agent$")).toBe("^skill-agent$"); + }); + + it("treats an absent or empty selector as no selector", () => { + expect(validateLiveSelector(undefined)).toBeUndefined(); + expect(validateLiveSelector("")).toBeUndefined(); + expect(validateLiveSelector(" ")).toBeUndefined(); + }); + + it("rejects shell metacharacters in the expanded selector", () => { + for (const bad of [ + "^$(touch pwned)$", + "^x$; rm -rf /", + "^x$ && evil", + "^`id`$", + "^x|y$", + "^x>out$", + ]) { + expect(() => validateLiveSelector(bad)).toThrow(/unsupported character/); + } + }); +}); + +describe("buildLiveVitestArgs (#6961)", () => { + it("builds the standard invocation with a selector", () => { + expect( + buildLiveVitestArgs({ + testPath: "test/e2e/live/registry-targets.test.ts", + selector: "^ubuntu-repo-cloud-openclaw$", + }), + ).toEqual([ + "vitest", + "run", + "--project", + "e2e-live", + "test/e2e/live/registry-targets.test.ts", + "-t", + "^ubuntu-repo-cloud-openclaw$", + "--silent=false", + "--reporter=default", + `--reporter=${RISK_SIGNAL_REPORTER}`, + ]); + }); + + it("omits the selector arguments for a single-file target", () => { + expect( + buildLiveVitestArgs({ + testPath: "test/e2e/live/diagnostics.test.ts", + }), + ).toEqual([ + "vitest", + "run", + "--project", + "e2e-live", + "test/e2e/live/diagnostics.test.ts", + "--silent=false", + "--reporter=default", + `--reporter=${RISK_SIGNAL_REPORTER}`, + ]); + }); + + it("fails closed on an invalid input before producing any argv", () => { + expect(() => + buildLiveVitestArgs({ + testPath: "test/e2e/live/x.test.ts", + selector: "^x$; rm -rf /", + }), + ).toThrow(/unsupported character/); + expect(() => + buildLiveVitestArgs({ + testPath: "test/e2e/support/x.test.ts", + selector: "^x$", + project: "e2e-live", + }), + ).toThrow(/must be under/); + }); +}); + +describe("runLiveVitestCommand (#6961)", () => { + const validArgs = ["run", "--test-path", "test/e2e/live/diagnostics.test.ts"]; + + it.each([ + ["child status", { status: 7, signal: null }, 7], + ["child signal", { status: null, signal: "SIGTERM" as NodeJS.Signals }, 143], + ["missing status and signal", { status: null, signal: null }, 1], + ])("preserves %s", (_label, result, expected) => { + let spawned: Parameters | undefined; + const spawn: LiveVitestSpawner = (...args) => { + spawned = args; + return result; + }; + + expect(runLiveVitestCommand(validArgs, spawn)).toBe(expected); + expect(spawned).toEqual([ + "npx", + [ + "vitest", + "run", + "--project", + "e2e-live", + "test/e2e/live/diagnostics.test.ts", + "--silent=false", + "--reporter=default", + `--reporter=${RISK_SIGNAL_REPORTER}`, + ], + { stdio: "inherit" }, + ]); + }); + + it("surfaces child launch failures", () => { + const launchError = new Error("spawn npx ENOENT"); + const spawn: LiveVitestSpawner = () => ({ + status: null, + signal: null, + error: launchError, + }); + + expect(() => runLiveVitestCommand(validArgs, spawn)).toThrow(launchError); + }); + + it.each([ + [ + "unknown option", + ["run", "--test-path", "test/e2e/live/diagnostics.test.ts", "--selctor", "^x$"], + ], + ["bare selector", [...validArgs, "--selector"]], + ])("rejects an %s before spawning Vitest", (_label, args) => { + let spawned = false; + const spawn: LiveVitestSpawner = () => { + spawned = true; + return { status: 0 }; + }; + + expect(() => runLiveVitestCommand(args, spawn)).toThrow(/unsupported.*option|requires a value/); + expect(spawned).toBe(false); + }); + + it("rejects a repeated supported option before spawning Vitest", () => { + let spawned = false; + const spawn: LiveVitestSpawner = () => { + spawned = true; + return { status: 0 }; + }; + + expect(() => + runLiveVitestCommand( + [...validArgs, "--test-path", "test/e2e/live/registry-targets.test.ts"], + spawn, + ), + ).toThrow(/must not be repeated/); + expect(spawned).toBe(false); + }); + + it.each([ + ["missing", []], + ["unsupported", ["runx"]], + ])("fails the direct CLI for a %s subcommand", (_label, args) => { + const result = spawnSync( + process.execPath, + ["--experimental-strip-types", "tools/e2e/live-vitest-invocation.mts", ...args], + { encoding: "utf8" }, + ); + + expect(result.status).toBe(1); + expect(result.stderr).toContain('expected "run"'); + }); +}); diff --git a/test/e2e/support/mcp-workflow-boundary.test.ts b/test/e2e/support/mcp-workflow-boundary.test.ts index c99dfb7a86d..6797c38e227 100644 --- a/test/e2e/support/mcp-workflow-boundary.test.ts +++ b/test/e2e/support/mcp-workflow-boundary.test.ts @@ -26,10 +26,10 @@ describe("MCP workflow artifact boundary", () => { (step) => step.name === "Run MCP OpenShell provider live test", ); requireFixture(run?.run, `${jobName} MCP live-test fixture is missing`); - const reporter = "--reporter=test/e2e/risk-signal-reporter.ts"; - requireFixture(run.run.includes(reporter), `${jobName} reporter fixture is missing`); - const updatedRun = run.run.replace(` ${reporter}`, ""); - requireFixture(updatedRun !== run.run, `${jobName} reporter could not be removed`); + const helper = "tools/e2e/live-vitest-invocation.mts run --test-path"; + requireFixture(run.run.includes(helper), `${jobName} live-vitest helper fixture is missing`); + const updatedRun = run.run.replace(helper, "vitest run"); + requireFixture(updatedRun !== run.run, `${jobName} live-vitest helper could not be removed`); run.run = updatedRun; fs.writeFileSync(workflowPath, YAML.stringify(workflow)); diff --git a/test/e2e/support/security-posture-workflow-boundary.test.ts b/test/e2e/support/security-posture-workflow-boundary.test.ts index d8cb7222dd8..7ccf597ce1f 100644 --- a/test/e2e/support/security-posture-workflow-boundary.test.ts +++ b/test/e2e/support/security-posture-workflow-boundary.test.ts @@ -27,6 +27,26 @@ function validateCentralWorkflowMutation(mutate: (source: string) => string): st } describe("security posture workflow boundary", () => { + it("requires the validated helper for the templated live test path", () => { + const workflow = readSecurityPostureWorkflow(); + const job = (workflow.jobs as Record>)["security-posture"]; + const run = (job.steps as Array>).find( + (step) => step.name === "Run security posture live Vitest test", + )!; + run.run = [ + "set -euo pipefail", + 'npx vitest run --project e2e-live "${{ matrix.test_file }}"', + ].join("\n"); + + const errors = validateSecurityPostureWorkflow(workflow); + expect(errors).toContain( + "security-posture step 'Run security posture live Vitest test' must run: tools/e2e/live-vitest-invocation.mts run", + ); + expect(errors).toContain( + "security-posture step 'Run security posture live Vitest test' must run: --test-path \"${{ matrix.test_file }}\"", + ); + }); + it("rejects missing agent coverage, mode drift, and broadly scoped credentials", () => { const hermesMatrixEntry = [ " - agent: hermes", diff --git a/tools/e2e/hermes-dashboard-workflow-boundary.mts b/tools/e2e/hermes-dashboard-workflow-boundary.mts index c94536bb870..fb226d2d161 100644 --- a/tools/e2e/hermes-dashboard-workflow-boundary.mts +++ b/tools/e2e/hermes-dashboard-workflow-boundary.mts @@ -114,7 +114,7 @@ export function validateHermesDashboardWorkflow(workflow: HermesDashboardWorkflo } const run = findStep(job, "Run Hermes dashboard live Vitest test"); - if (!run.run?.includes("npx vitest run --project e2e-live")) { + if (!run.run?.includes("tools/e2e/live-vitest-invocation.mts run --test-path")) { errors.push(`${JOB_NAME} must run the live Vitest project`); } if (!run.run?.includes("test/e2e/live/hermes-e2e.test.ts")) { diff --git a/tools/e2e/hermes-gpu-startup-workflow-boundary.mts b/tools/e2e/hermes-gpu-startup-workflow-boundary.mts index c2e9dc82e1c..17849ef39c8 100644 --- a/tools/e2e/hermes-gpu-startup-workflow-boundary.mts +++ b/tools/e2e/hermes-gpu-startup-workflow-boundary.mts @@ -295,7 +295,7 @@ removalCondition:`, true, ) || /\b(?:install\s+-m|chmod)\s+0?644\b/u.test(run) || - !run.includes("npx vitest run --project e2e-live") || + !run.includes("tools/e2e/live-vitest-invocation.mts run --test-path") || !run.includes("test/e2e/live/hermes-gpu-startup.test.ts") ) { errors.push(`${JOB_NAME} trusted runtime boundary failed`); diff --git a/tools/e2e/live-vitest-invocation.mts b/tools/e2e/live-vitest-invocation.mts new file mode 100644 index 00000000000..8a513c007ec --- /dev/null +++ b/tools/e2e/live-vitest-invocation.mts @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { spawnSync } from "node:child_process"; +import { pathToFileURL } from "node:url"; + +import { spawnExitCode } from "../../src/lib/core/process-exit.ts"; + +export const LIVE_VITEST_PROJECT = "e2e-live"; +export const LIVE_TEST_ROOT = "test/e2e/live/"; +export const RISK_SIGNAL_REPORTER = "test/e2e/risk-signal-reporter.ts"; + +const SHELL_METACHARACTER = /[^A-Za-z0-9_./^$=:@+-]/u; +const TEST_PATH_PATTERN = /^[A-Za-z0-9_./-]+$/u; + +export interface LiveVitestInvocation { + testPath: string | undefined; + selector?: string | undefined; + project?: string | undefined; +} + +export interface LiveVitestSpawnResult { + status: number | null; + signal?: NodeJS.Signals | null; + error?: Error | undefined; +} + +export type LiveVitestSpawner = ( + command: string, + args: string[], + options: { stdio: "inherit" }, +) => LiveVitestSpawnResult; + +const LIVE_VITEST_OPTIONS = { + "--project": "project", + "--selector": "selector", + "--test-path": "testPath", +} as const; + +function parseLiveVitestArgs(cliArgs: string[]): LiveVitestInvocation { + const invocation: LiveVitestInvocation = { testPath: undefined }; + + for (let index = 0; index < cliArgs.length; index += 2) { + const option = cliArgs[index]; + const key = LIVE_VITEST_OPTIONS[option as keyof typeof LIVE_VITEST_OPTIONS]; + if (!key) { + throw new Error(`unsupported live Vitest option ${JSON.stringify(option)}`); + } + const value = cliArgs[index + 1]; + if (!value || value.startsWith("--")) { + throw new Error(`live Vitest option ${option} requires a value`); + } + if (invocation[key] !== undefined) { + throw new Error(`live Vitest option ${option} must not be repeated`); + } + invocation[key] = value; + } + + return invocation; +} + +function assertNoShellMetacharacters(value: string, field: string): void { + const match = SHELL_METACHARACTER.exec(value); + if (match) { + throw new Error(`${field} contains an unsupported character ${JSON.stringify(match[0])}`); + } +} + +export function validateLiveProject(project: string | undefined): string { + const resolved = (project ?? LIVE_VITEST_PROJECT).trim(); + if (resolved !== LIVE_VITEST_PROJECT) { + throw new Error( + `unsupported vitest project ${JSON.stringify(resolved)}; this helper only runs ${LIVE_VITEST_PROJECT}`, + ); + } + return resolved; +} + +export function validateLiveTestPath(testPath: string | undefined): string { + const value = (testPath ?? "").trim(); + if (!value) { + throw new Error("test path is required"); + } + if (!TEST_PATH_PATTERN.test(value)) { + assertNoShellMetacharacters(value, "test path"); + throw new Error(`test path ${JSON.stringify(value)} has an unsupported character`); + } + if (value.startsWith("/")) { + throw new Error("test path must be repository-relative, not absolute"); + } + if (value.split("/").includes("..")) { + throw new Error("test path must not traverse with '..'"); + } + if (!value.startsWith(LIVE_TEST_ROOT)) { + throw new Error(`test path must be under ${LIVE_TEST_ROOT}, got ${JSON.stringify(value)}`); + } + if (!value.endsWith(".test.ts")) { + throw new Error("test path must name a .test.ts file"); + } + return value; +} + +export function validateLiveSelector(selector: string | undefined): string | undefined { + const value = (selector ?? "").trim(); + if (!value) { + return undefined; + } + assertNoShellMetacharacters(value, "selector"); + return value; +} + +export function buildLiveVitestArgs(invocation: LiveVitestInvocation): string[] { + const project = validateLiveProject(invocation.project); + const testPath = validateLiveTestPath(invocation.testPath); + const selector = validateLiveSelector(invocation.selector); + const selectorArgs = selector ? ["-t", selector] : []; + return [ + "vitest", + "run", + "--project", + project, + testPath, + ...selectorArgs, + "--silent=false", + "--reporter=default", + `--reporter=${RISK_SIGNAL_REPORTER}`, + ]; +} + +export function runLiveVitestCli(cliArgs: string[], spawn: LiveVitestSpawner = spawnSync): number { + const argv = buildLiveVitestArgs(parseLiveVitestArgs(cliArgs)); + const result = spawn("npx", argv, { stdio: "inherit" }); + if (result.error) { + throw result.error; + } + return spawnExitCode(result); +} + +export function runLiveVitestCommand(argv: string[], spawn: LiveVitestSpawner = spawnSync): number { + const [command, ...cliArgs] = argv; + if (command !== "run") { + throw new Error( + `unsupported live Vitest command ${JSON.stringify(command ?? "")}; expected "run"`, + ); + } + return runLiveVitestCli(cliArgs, spawn); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + process.exit(runLiveVitestCommand(process.argv.slice(2))); +} diff --git a/tools/e2e/mcp-workflow-boundary.mts b/tools/e2e/mcp-workflow-boundary.mts index f099561fc63..beb76c24268 100644 --- a/tools/e2e/mcp-workflow-boundary.mts +++ b/tools/e2e/mcp-workflow-boundary.mts @@ -392,13 +392,16 @@ function validateJobExecution( errors.push("mcp-bridge stable lane must run its full MCP lifecycle unconditionally"); } } - for (const required of ["--project e2e-live", "test/e2e/live/mcp-bridge.test.ts"]) { + for (const required of [ + "tools/e2e/live-vitest-invocation.mts run --test-path", + "test/e2e/live/mcp-bridge.test.ts", + ]) { requireContains(errors, run.run, required, `${jobName} must run the unified MCP live test`); } requireContains( errors, run.run, - "--reporter=test/e2e/risk-signal-reporter.ts", + "tools/e2e/live-vitest-invocation.mts run --test-path", `${jobName} must publish canonical risk-signal evidence`, ); requireEqual( diff --git a/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts b/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts index a54eddf2204..676b8f6bbce 100644 --- a/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts +++ b/tools/e2e/openclaw-plugin-runtime-exdev-workflow-boundary.mts @@ -167,7 +167,7 @@ export function validateOpenClawPluginRuntimeExdevWorkflow( 'test -z "${DOCKERHUB_USERNAME:-}"', 'test -z "${DOCKERHUB_TOKEN:-}"', "env -u DOCKER_CONFIG -u DOCKERHUB_USERNAME -u DOCKERHUB_TOKEN", - "npx vitest run --project e2e-live", + "tools/e2e/live-vitest-invocation.mts run --test-path", "test/e2e/live/openclaw-plugin-runtime-exdev.test.ts", ]) { requireRunContains(errors, run, fragment, runName); diff --git a/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts b/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts index 31cc2bbd783..72c5359573a 100644 --- a/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts +++ b/tools/e2e/openshell-gateway-auth-contract-workflow-boundary.mts @@ -149,7 +149,7 @@ export function validateOpenShellGatewayAuthContractWorkflow( const runName = "Run OpenShell gateway auth contract live test"; const run = findStep(job, runName); - requireRunContains(errors, run, "npx vitest run --project e2e-live"); + requireRunContains(errors, run, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, run, "test/e2e/live/openshell-gateway-auth-source-contract.test.ts"); if (Object.keys(run.env ?? {}).length > 0 || JSON.stringify(run).includes("secrets.")) { errors.push(`${JOB_NAME} live test must not receive workflow credentials`); diff --git a/tools/e2e/operations-workflow-boundary.mts b/tools/e2e/operations-workflow-boundary.mts index 4af3483cc7c..0a409c8c6a7 100644 --- a/tools/e2e/operations-workflow-boundary.mts +++ b/tools/e2e/operations-workflow-boundary.mts @@ -16,6 +16,7 @@ const FULL_SHA_ACTION = /^[^\s@]+@[0-9a-f]{40}$/u; const GITHUB_SCRIPT_NODE24_ACTION = "actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3"; const PR_GATE_REPORTER = "test/e2e/risk-signal-reporter.ts"; +const LIVE_VITEST_HELPER = "tools/e2e/live-vitest-invocation.mts run --test-path"; const E2E_ARTIFACT_ACTION = "NVIDIA/NemoClaw/.github/actions/upload-e2e-artifacts@"; const ISSUE_API_REFERENCE = /\bgithub\.rest\.issues\b/u; const ISSUE_MUTATION_BEYOND_COMMENT = @@ -231,12 +232,16 @@ function validatePrGateEvidenceProducers(errors: string[], workflow: OperationsW if (typeof job.env?.E2E_ARTIFACT_DIR !== "string" || !job.env.E2E_ARTIFACT_DIR) { errors.push(`${jobId} must expose an evidence artifact directory`); } - const vitestSteps = (job.steps ?? []).filter((step) => - String(step.run ?? "").includes("npx vitest"), - ); + const vitestSteps = (job.steps ?? []).filter((step) => { + const run = String(step.run ?? ""); + return run.includes("npx vitest") || run.includes(LIVE_VITEST_HELPER); + }); if ( vitestSteps.length === 0 || - vitestSteps.some((step) => !String(step.run).includes(PR_GATE_REPORTER)) + vitestSteps.some((step) => { + const run = String(step.run); + return !run.includes(LIVE_VITEST_HELPER) && !run.includes(PR_GATE_REPORTER); + }) ) { errors.push(`${jobId} must attach the risk-signal reporter to every Vitest invocation`); } diff --git a/tools/e2e/sandbox-operations-workflow-boundary.mts b/tools/e2e/sandbox-operations-workflow-boundary.mts index 6ffdb3bd8d5..76dabc3b0b4 100644 --- a/tools/e2e/sandbox-operations-workflow-boundary.mts +++ b/tools/e2e/sandbox-operations-workflow-boundary.mts @@ -167,7 +167,7 @@ export function validateSandboxOperationsWorkflow(workflow: { errors.push(`${JOB_NAME} exposes the inference key outside the live test step`); } } - requireRunContains(errors, run, "npx vitest run --project e2e-live"); + requireRunContains(errors, run, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, run, "test/e2e/live/sandbox-operations.test.ts"); const cleanup = findStep(job, "Clean up Docker auth"); diff --git a/tools/e2e/security-posture-workflow-boundary.mts b/tools/e2e/security-posture-workflow-boundary.mts index 72eabafd6b2..9a648d5966b 100644 --- a/tools/e2e/security-posture-workflow-boundary.mts +++ b/tools/e2e/security-posture-workflow-boundary.mts @@ -149,8 +149,8 @@ export function validateSecurityPostureWorkflow(workflow: WorkflowRecord): strin errors.push(`${JOB_NAME} exposes the inference key outside the live test step`); } } - requireRunContains(errors, run, "npx vitest run --project e2e-live"); - requireRunContains(errors, run, '"${{ matrix.test_file }}"'); + requireRunContains(errors, run, "tools/e2e/live-vitest-invocation.mts run"); + requireRunContains(errors, run, '--test-path "${{ matrix.test_file }}"'); return errors; } diff --git a/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts b/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts index 417539931c3..cfa592b9d0f 100644 --- a/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts +++ b/tools/e2e/upload-e2e-artifacts-workflow-boundary.mts @@ -280,7 +280,10 @@ export function validateUploadE2eArtifactsInvocations(workflow: WorkflowRecord): env.NEMOCLAW_RUN_LIVE_E2E === "1" || SHARED_E2E_JOBS.has(jobName) || jobSteps.some( - (step) => typeof step.run === "string" && step.run.includes("--project e2e-live"), + (step) => + typeof step.run === "string" && + (step.run.includes("--project e2e-live") || + step.run.includes("tools/e2e/live-vitest-invocation.mts run --test-path")), ) ); }) diff --git a/tools/e2e/workflow-boundary.mts b/tools/e2e/workflow-boundary.mts index 71b22a9996f..8b5aa78c73a 100644 --- a/tools/e2e/workflow-boundary.mts +++ b/tools/e2e/workflow-boundary.mts @@ -926,7 +926,7 @@ function validateSkillAgentJob(errors: string[], jobs: WorkflowRecord): void { ); requireRunContains(errors, runVitest, 'OPENSHELL_BIN="$(command -v openshell)"'); requireRunContains(errors, runVitest, "export OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/skill-agent.test.ts"); } @@ -1032,7 +1032,7 @@ function validateNetworkPolicyJob(errors: string[], jobs: WorkflowRecord): void if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push("network-policy live E2E step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/network-policy.test.ts"); } @@ -1176,7 +1176,7 @@ function validateCommonEgressAgentJob(errors: string[], jobs: WorkflowRecord): v errors.push("common-egress-agent step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/common-egress-agent.test.ts"); } @@ -1259,7 +1259,7 @@ function validateShieldsConfigJob(errors: string[], jobs: WorkflowRecord): void if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push("shields-config step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/shields-config.test.ts"); } @@ -1333,7 +1333,7 @@ function validateRebuildOpenClawJob(errors: string[], jobs: WorkflowRecord): voi errors.push("rebuild-openclaw step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/rebuild-openclaw.test.ts"); } @@ -1438,7 +1438,7 @@ function validateRebuildHermesJob( if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push(`${jobName} step must receive NVIDIA_INFERENCE_API_KEY from secrets`); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/rebuild-hermes.test.ts"); } @@ -1519,7 +1519,7 @@ function validateSandboxRebuildJob(errors: string[], jobs: WorkflowRecord): void errors.push("sandbox-rebuild step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/sandbox-rebuild.test.ts"); } @@ -1611,7 +1611,7 @@ function validateStateBackupRestoreJob(errors: string[], jobs: WorkflowRecord): errors.push("state-backup-restore step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/state-backup-restore.test.ts"); } @@ -1702,7 +1702,7 @@ function validateUpgradeStaleSandboxJob(errors: string[], jobs: WorkflowRecord): errors.push("upgrade-stale-sandbox step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/upgrade-stale-sandbox.test.ts"); } @@ -1783,7 +1783,7 @@ function validateTokenRotationJob(errors: string[], jobs: WorkflowRecord): void errors.push(`token-rotation step must set ${tokenName}`); } } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/token-rotation.test.ts"); } @@ -1904,7 +1904,7 @@ function validateMessagingCompatibleEndpointJob(errors: string[], jobs: Workflow if (runVitestEnv.TELEGRAM_ALLOWED_IDS !== "123456789") { errors.push("messaging-compatible-endpoint step must set fake Telegram allowed ids"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/messaging-compatible-endpoint.test.ts"); } @@ -1969,7 +1969,7 @@ function validateCloudInferenceJob(errors: string[], jobs: WorkflowRecord): void if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push("cloud-inference run step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/cloud-inference.test.ts"); } @@ -2308,7 +2308,7 @@ function validateDoubleOnboardJob(errors: string[], jobs: WorkflowRecord): void const runVitest = requireJobStep(errors, jobName, steps, "Run double-onboard live Vitest test"); requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/double-onboard.test.ts"); } function validateHermesE2EJob(errors: string[], jobs: WorkflowRecord): void { @@ -2386,7 +2386,7 @@ function validateHermesE2EJob(errors: string[], jobs: WorkflowRecord): void { "hermes-e2e run step must guard NVIDIA_INFERENCE_API_KEY behind a trusted main-branch dispatch without a PR checkout and the inference mode condition", ); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/hermes-e2e.test.ts"); requireRunDoesNotContain(errors, runVitest, "${{ inputs."); } @@ -2470,7 +2470,7 @@ function validateDiagnosticsJob(errors: string[], jobs: WorkflowRecord): void { if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push("diagnostics live E2E step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/diagnostics.test.ts"); requireRunDoesNotContain(errors, runVitest, "${{ inputs."); } @@ -2554,7 +2554,7 @@ function validateSparkInstallJob(errors: string[], jobs: WorkflowRecord): void { errors.push("spark-install live E2E step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } requireRunContains(errors, runVitest, "set -euo pipefail"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/spark-install.test.ts"); } @@ -2638,7 +2638,7 @@ function validateSnapshotCommandsJob(errors: string[], jobs: WorkflowRecord): vo } const runVitest = requireJobStep(errors, jobName, steps, "Run snapshot commands live test"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/snapshot-commands.test.ts"); } @@ -2739,7 +2739,7 @@ function validateModelRouterProviderRoutedInferenceJob( "model-router-provider-routed-inference live E2E step must receive NVIDIA_API_KEY from secrets", ); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains( errors, runVitest, @@ -2880,7 +2880,7 @@ function validateTunnelLifecycleJob(errors: string[], jobs: WorkflowRecord): voi "tunnel-lifecycle live E2E step must not run cloudflared APT installation with NVIDIA_INFERENCE_API_KEY in scope", ); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/tunnel-lifecycle.test.ts"); } @@ -2965,7 +2965,7 @@ function validateIssue2478CrashLoopRecoveryJob(errors: string[], jobs: WorkflowR runVitestEnv, "NVIDIA_INFERENCE_API_KEY", ); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/issue-2478-crash-loop-recovery.test.ts"); } @@ -3077,7 +3077,7 @@ function validateChannelsAddRemoveJob(errors: string[], jobs: WorkflowRecord): v errors.push("channels-add-remove step must set TELEGRAM_REQUIRE_MENTION"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/channels-add-remove.test.ts"); } @@ -3150,7 +3150,7 @@ function validateOpenClawDiscordPairingJob(errors: string[], jobs: WorkflowRecor if (runVitestEnv.DISCORD_BOT_TOKEN !== "test-fake-discord-pairing-e2e") { errors.push("openclaw-discord-pairing step must use fake Discord token"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/openclaw-discord-pairing.test.ts"); } @@ -3221,7 +3221,7 @@ function validateOpenClawSlackPairingJob(errors: string[], jobs: WorkflowRecord) if (runVitestEnv.SLACK_APP_TOKEN !== "xapp-fake-slack-pairing-e2e") { errors.push("openclaw-slack-pairing step must use fake Slack app token"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/openclaw-slack-pairing.test.ts"); } @@ -3350,7 +3350,7 @@ function validateChannelsStopStartJob(errors: string[], jobs: WorkflowRecord): v errors.push("channels-stop-start step must set the fake WeChat token"); } requireRunContains(errors, runVitest, "OPENSHELL_BIN"); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/channels-stop-start.test.ts"); } @@ -3409,7 +3409,7 @@ function validateTelegramInjectionJob(errors: string[], jobs: WorkflowRecord): v if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push("telegram-injection step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/telegram-injection.test.ts"); } @@ -3536,7 +3536,7 @@ function validateBedrockRuntimeCompatibleAnthropicJob( steps, "Run Bedrock Runtime compatible Anthropic live test", ); - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains( errors, runVitest, @@ -4005,7 +4005,7 @@ export function validateE2eWorkflow(workflowValue: unknown): string[] { if (runVitestEnv.NVIDIA_INFERENCE_API_KEY !== "${{ secrets.NVIDIA_INFERENCE_API_KEY }}") { errors.push("live E2E step must receive NVIDIA_INFERENCE_API_KEY from secrets"); } - requireRunContains(errors, runVitest, "npx vitest run --project e2e-live"); + requireRunContains(errors, runVitest, "tools/e2e/live-vitest-invocation.mts run --test-path"); requireRunContains(errors, runVitest, "test/e2e/live/registry-targets.test.ts"); requireRunContains(errors, runVitest, '"^${TARGET_ID}$"');