Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

249 lines
8.2 KiB
JavaScript

/**
* Unit coverage for the device-e2e bundle assembler.
*
* The real runners need phones/simulators, so this test pins the pure filesystem
* contract: output directory selection, inline-ready artifact collection,
* summary writing, and JUnit generation on both passing and failed steps.
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import {
captureFailureForensics,
collectBundleArtifacts,
createDeviceE2eBundle,
defaultDeviceE2eOutputDir,
finalizeDeviceE2eBundle,
finishBundleStep,
formatFailureForensicsBlock,
parseOutputDirArg,
recordBundleArtifact,
runBundledCommand,
startBundleStep,
} from "./lib/device-e2e-bundle.mjs";
const tempDirs = [];
const ONE_BY_ONE_PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=",
"base64",
);
function tempRoot() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "device-e2e-bundle-"));
tempDirs.push(dir);
return dir;
}
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
describe("device-e2e bundle assembly", () => {
it("parses --output and builds the default per-lane directory", () => {
expect(parseOutputDirArg(["node", "runner", "--output", "/tmp/out"])).toBe(
"/tmp/out",
);
expect(parseOutputDirArg(["node", "runner"])).toBeUndefined();
expect(
defaultDeviceE2eOutputDir({
appDir: "/repo/packages/app",
lane: "android",
date: new Date("2026-07-05T01:02:03.004Z"),
}),
).toBe(
"/repo/packages/app/device-e2e-output/android-2026-07-05T01-02-03-004Z",
);
});
it("writes summary, junit, and inline copies for existing JPG/MP4 artifacts", () => {
const root = tempRoot();
const bundle = createDeviceE2eBundle({
appDir: root,
lane: "android",
outputDir: path.join(root, "bundle"),
device: { serial: "device-1" },
build: { buildId: "build-1", commit: "abc123" },
});
const sourceDir = path.join(root, "source");
fs.mkdirSync(sourceDir, { recursive: true });
const jpg = path.join(sourceDir, "screen.jpg");
const mp4 = path.join(sourceDir, "walkthrough.mp4");
fs.writeFileSync(jpg, "jpg");
fs.writeFileSync(mp4, "mp4");
const step = startBundleStep(bundle, "route coverage");
recordBundleArtifact(bundle, jpg, "screenshot", step);
recordBundleArtifact(bundle, mp4, "video", step);
finishBundleStep(bundle, step, "passed");
const bundleRoot = finalizeDeviceE2eBundle(bundle, "passed");
const summary = JSON.parse(
fs.readFileSync(path.join(bundleRoot, "summary.json"), "utf8"),
);
expect(summary.result).toBe("passed");
expect(summary.device.serial).toBe("device-1");
expect(summary.build.buildId).toBe("build-1");
expect(summary.steps).toHaveLength(1);
expect(fs.existsSync(path.join(bundleRoot, "junit.xml"))).toBe(true);
expect(fs.existsSync(path.join(bundleRoot, "inline", "screen.jpg"))).toBe(
true,
);
expect(
fs.existsSync(path.join(bundleRoot, "inline", "walkthrough.mp4")),
).toBe(true);
});
it("collects logs from source directories and records failed steps in junit", () => {
const root = tempRoot();
const bundle = createDeviceE2eBundle({
appDir: root,
lane: "ios-sim",
outputDir: path.join(root, "bundle"),
});
const logDir = path.join(root, "logs");
fs.mkdirSync(logDir, { recursive: true });
fs.writeFileSync(path.join(logDir, "runner.log"), "failed\n");
const step = startBundleStep(bundle, "local chat");
finishBundleStep(bundle, step, "failed", new Error("chat failed"));
collectBundleArtifacts(bundle, [logDir]);
finalizeDeviceE2eBundle(bundle, "failed");
const junit = fs.readFileSync(path.join(bundle.root, "junit.xml"), "utf8");
const summary = JSON.parse(
fs.readFileSync(path.join(bundle.root, "summary.json"), "utf8"),
);
expect(junit).toContain('failures="1"');
expect(junit).toContain("chat failed");
expect(summary.result).toBe("failed");
expect(summary.artifacts.some((a) => a.path.endsWith("runner.log"))).toBe(
true,
);
});
it("converts PNG screenshots into inline JPG artifacts", () => {
const root = tempRoot();
const bundle = createDeviceE2eBundle({
appDir: root,
lane: "android",
outputDir: path.join(root, "bundle"),
});
const png = path.join(bundle.rawDir, "screen.png");
fs.writeFileSync(png, ONE_BY_ONE_PNG);
recordBundleArtifact(bundle, png, "screenshot");
finalizeDeviceE2eBundle(bundle, "passed");
const summary = JSON.parse(
fs.readFileSync(path.join(bundle.root, "summary.json"), "utf8"),
);
expect(fs.existsSync(path.join(bundle.inlineDir, "screen.jpg"))).toBe(true);
expect(summary.artifacts.some((a) => a.path === "inline/screen.jpg")).toBe(
true,
);
});
it("writes a failed summary and runner log when a bundled command fails", () => {
const root = tempRoot();
const bundle = createDeviceE2eBundle({
appDir: root,
lane: "android",
outputDir: path.join(root, "bundle"),
});
expect(() =>
runBundledCommand(
bundle,
"failing command",
process.execPath,
["-e", "console.error('nope'); process.exit(7)"],
{ cwd: root },
),
).toThrow(/exited with code 7/);
finalizeDeviceE2eBundle(bundle, "failed");
const summary = JSON.parse(
fs.readFileSync(path.join(bundle.root, "summary.json"), "utf8"),
);
expect(summary.result).toBe("failed");
expect(summary.steps[0]).toMatchObject({
name: "failing command",
status: "failed",
});
expect(
fs.readFileSync(path.join(bundle.logsDir, "runner.log"), "utf8"),
).toContain("nope");
});
it("records step failure forensics and formats a compact stderr block", () => {
const root = tempRoot();
const bundle = createDeviceE2eBundle({
appDir: root,
lane: "android",
outputDir: path.join(root, "bundle"),
});
const step = startBundleStep(bundle, "Android route coverage");
const error = new Error("route failed");
captureFailureForensics(
bundle,
step,
({ failureDir }) => {
const cause = path.join(failureDir, "failure-cause.txt");
const log = path.join(failureDir, "logcat.txt");
const screen = path.join(failureDir, "screen.png");
fs.writeFileSync(cause, "route failed\n");
fs.writeFileSync(log, "log tail\n");
fs.writeFileSync(screen, ONE_BY_ONE_PNG);
return [cause, log, screen];
},
error,
);
finishBundleStep(bundle, step, "failed", error);
finalizeDeviceE2eBundle(bundle, "failed");
const summary = JSON.parse(
fs.readFileSync(path.join(bundle.root, "summary.json"), "utf8"),
);
const block = formatFailureForensicsBlock(bundle, error);
expect(summary.steps[0].failureDir).toBe("failure/android-route-coverage");
expect(summary.steps[0].artifacts).toEqual([
"failure/android-route-coverage/failure-cause.txt",
"failure/android-route-coverage/logcat.txt",
"failure/android-route-coverage/screen.png",
]);
expect(block).toContain("DEVICE E2E FAILURE FORENSICS");
expect(block).toContain("step: Android route coverage");
expect(block).toContain("screen.png");
});
it("keeps the original failed step when forensic capture fails", () => {
const root = tempRoot();
const bundle = createDeviceE2eBundle({
appDir: root,
lane: "ios-sim",
outputDir: path.join(root, "bundle"),
});
const step = startBundleStep(bundle, "boot iOS Simulator");
captureFailureForensics(bundle, step, () => {
throw new Error("simulator disconnected");
});
finishBundleStep(bundle, step, "failed", new Error("boot failed"));
finalizeDeviceE2eBundle(bundle, "failed");
const summary = JSON.parse(
fs.readFileSync(path.join(bundle.root, "summary.json"), "utf8"),
);
expect(summary.result).toBe("failed");
expect(summary.steps[0].error).toBe("boot failed");
expect(summary.warnings[0]).toContain("simulator disconnected");
});
});