


















@@ -5,7 +5,20 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
55import { runMantisVisualDriver, runMantisVisualTask } from "./visual-task.runtime.js";
6677async function expectPathMissing(targetPath: string): Promise<void> {
8-await expect(fs.stat(targetPath)).rejects.toMatchObject({ code: "ENOENT" });
8+try {
9+await fs.stat(targetPath);
10+} catch (error) {
11+expect((error as { code?: unknown }).code).toBe("ENOENT");
12+return;
13+}
14+throw new Error(`Expected path to be missing: ${targetPath}`);
15+}
16+17+function expectArgsContainSequence(args: readonly string[], expected: readonly string[]): void {
18+const startIndex = args.findIndex((_, index) => {
19+return expected.every((value, offset) => args[index + offset] === value);
20+});
21+expect(startIndex).toBeGreaterThanOrEqual(0);
922}
10231124describe("mantis visual task runtime", () => {
@@ -88,23 +101,19 @@ describe("mantis visual task runtime", () => {
88101".artifacts/qa-e2e/mantis/visual-task-test/visual-task.mp4",
89102);
90103const stagedVideoPath = recordArgs[recordArgs.indexOf("--output") + 1];
91-expect(recordArgs).toEqual(
92-expect.arrayContaining([
93-"--duration",
94-"12s",
95-"--output",
96-stagedVideoPath,
97-"--while",
98-"--",
99-"pnpm",
100-"--dir",
101-repoRoot,
102-"openclaw",
103-"qa",
104-"mantis",
105-"visual-driver",
106-]),
107-);
104+expectArgsContainSequence(recordArgs, ["--duration", "12s"]);
105+expectArgsContainSequence(recordArgs, ["--output", stagedVideoPath ?? ""]);
106+expectArgsContainSequence(recordArgs, [
107+"--while",
108+"--",
109+"pnpm",
110+"--dir",
111+repoRoot,
112+"openclaw",
113+"qa",
114+"mantis",
115+"visual-driver",
116+]);
108117expect(stagedVideoPath).not.toBe(finalVideoPath);
109118expect(path.basename(stagedVideoPath ?? "")).toContain(path.basename(finalVideoPath));
110119expect(path.basename(stagedVideoPath ?? "")).toMatch(/\.part$/);
@@ -116,14 +125,12 @@ describe("mantis visual task runtime", () => {
116125status: string;
117126visionMode: string;
118127};
119-expect(summary).toMatchObject({
120-crabbox: {
121-id: "cbx_abc123",
122-vncCommand: "/tmp/crabbox vnc --provider hetzner --id cbx_abc123 --open",
123-},
124-status: "pass",
125-visionMode: "metadata",
126-});
128+expect(summary.crabbox.id).toBe("cbx_abc123");
129+expect(summary.crabbox.vncCommand).toBe(
130+"/tmp/crabbox vnc --provider hetzner --id cbx_abc123 --open",
131+);
132+expect(summary.status).toBe("pass");
133+expect(summary.visionMode).toBe("metadata");
127134});
128135129136it("fails when recording breaks after the visual driver passes", async () => {
@@ -180,10 +187,8 @@ describe("mantis visual task runtime", () => {
180187visionMode: "metadata",
181188});
182189183-expect(result).toMatchObject({
184-status: "fail",
185-videoPath: undefined,
186-});
190+expect(result.status).toBe("fail");
191+expect(result.videoPath).toBeUndefined();
187192expect(commands.map((entry) => [entry.command, entry.args[0]])).toEqual([
188193["/tmp/crabbox", "warmup"],
189194["/tmp/crabbox", "inspect"],
@@ -194,14 +199,10 @@ describe("mantis visual task runtime", () => {
194199recording?: { error?: string; required: boolean };
195200status: string;
196201};
197-expect(summary).toMatchObject({
198-error: "crabbox record failed after driver exit",
199-recording: {
200-error: "crabbox record failed after driver exit",
201-required: true,
202-},
203-status: "fail",
204-});
202+expect(summary.error).toBe("crabbox record failed after driver exit");
203+expect(summary.recording?.error).toBe("crabbox record failed after driver exit");
204+expect(summary.recording?.required).toBe(true);
205+expect(summary.status).toBe("fail");
205206});
206207207208it("preserves the video artifact when recording fails after writing output", async () => {
@@ -278,17 +279,11 @@ describe("mantis visual task runtime", () => {
278279recording?: { error?: string; required: boolean };
279280status: string;
280281};
281-expect(summary).toMatchObject({
282-artifacts: {
283-videoPath: result.videoPath,
284-},
285-error: "crabbox record failed after writing video",
286-recording: {
287-error: "crabbox record failed after writing video",
288-required: true,
289-},
290-status: "fail",
291-});
282+expect(summary.artifacts?.videoPath).toBe(result.videoPath);
283+expect(summary.error).toBe("crabbox record failed after writing video");
284+expect(summary.recording?.error).toBe("crabbox record failed after writing video");
285+expect(summary.recording?.required).toBe(true);
286+expect(summary.status).toBe("fail");
292287});
293288294289it("drives a lease, screenshots it, and verifies image-describe text", async () => {
@@ -345,29 +340,27 @@ describe("mantis visual task runtime", () => {
345340["pnpm", "--dir", repoRoot],
346341]);
347342const launchArgs = commands.find((entry) => entry.args[0] === "desktop")?.args ?? [];
348-expect(launchArgs).toEqual(
349- expect.arrayContaining(["--", "sh", "-lc", expect.stringContaining("--no-first-run")]),
350-);
343+const launchShellIndex = launchArgs.findIndex((arg) => arg === "--");
344+expect(launchArgs.slice(launchShellIndex, launchShellIndex + 3)).toEqual(["--", "sh", "-lc"]);
345+expect(launchArgs[launchShellIndex + 3]).toContain("--no-first-run");
351346const visionArgs = commands.find((entry) => entry.command === "pnpm")?.args ?? [];
352-expect(visionArgs).toEqual(
353-expect.arrayContaining([
354-"infer",
355-"image",
356-"describe",
357-"--file",
358-path.join(repoRoot, ".artifacts/qa-e2e/mantis/visual-driver-test/visual-task.png"),
359-"--model",
360-"openai/gpt-5.4",
361-]),
362-);
363-expect(visionArgs).toEqual(
364-expect.arrayContaining(["--prompt", expect.stringContaining("return only valid JSON")]),
365-);
366-expect(result.vision.assertion).toMatchObject({
367-evidence: 'The page heading reads "Example Domain".',
368-matched: true,
369-visible: true,
370-});
347+expectArgsContainSequence(visionArgs, [
348+"openclaw",
349+"infer",
350+"image",
351+"describe",
352+"--file",
353+path.join(repoRoot, ".artifacts/qa-e2e/mantis/visual-driver-test/visual-task.png"),
354+]);
355+const promptIndex = visionArgs.indexOf("--prompt");
356+expect(promptIndex).toBeGreaterThanOrEqual(0);
357+expect(visionArgs[promptIndex + 1]).toContain("return only valid JSON");
358+const modelIndex = visionArgs.indexOf("--model");
359+expect(modelIndex).toBeGreaterThanOrEqual(0);
360+expect(visionArgs[modelIndex + 1]).toBe("openai/gpt-5.4");
361+expect(result.vision.assertion?.evidence).toBe('The page heading reads "Example Domain".');
362+expect(result.vision.assertion?.matched).toBe(true);
363+expect(result.vision.assertion?.visible).toBe(true);
371364});
372365373366it("fails image-describe text checks when the model gives negative evidence that quotes the target", async () => {
@@ -405,16 +398,12 @@ describe("mantis visual task runtime", () => {
405398visionMode: "image-describe",
406399});
407400408-expect(result).toMatchObject({
409-matched: false,
410-status: "fail",
411-vision: {
412-assertion: {
413-matched: false,
414-reason: "Image describe did not return a structured visual assertion.",
415-},
416-},
417-});
401+expect(result.matched).toBe(false);
402+expect(result.status).toBe("fail");
403+expect(result.vision.assertion?.matched).toBe(false);
404+expect(result.vision.assertion?.reason).toBe(
405+"Image describe did not return a structured visual assertion.",
406+);
418407});
419408420409it("fails metadata mode when text evidence is requested", async () => {
@@ -438,12 +427,8 @@ describe("mantis visual task runtime", () => {
438427visionMode: "metadata",
439428});
440429441-expect(result).toMatchObject({
442-matched: false,
443-status: "fail",
444-vision: {
445-mode: "metadata",
446-},
447-});
430+expect(result.matched).toBe(false);
431+expect(result.status).toBe("fail");
432+expect(result.vision.mode).toBe("metadata");
448433});
449434});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。