test: raise vitest coverage

This commit is contained in:
Peter Steinberger
2025-12-13 20:37:56 +00:00
parent 41dd3b11b7
commit cf28ea0d1c
10 changed files with 1433 additions and 1 deletions

199
src/browser/chrome.test.ts Normal file
View File

@@ -0,0 +1,199 @@
import fs from "node:fs";
import fsp from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
decorateClawdProfile,
findChromeExecutableMac,
isChromeReachable,
stopClawdChrome,
} from "./chrome.js";
import {
DEFAULT_CLAWD_BROWSER_COLOR,
DEFAULT_CLAWD_BROWSER_PROFILE_NAME,
} from "./constants.js";
async function readJson(filePath: string): Promise<Record<string, unknown>> {
const raw = await fsp.readFile(filePath, "utf-8");
return JSON.parse(raw) as Record<string, unknown>;
}
describe("browser chrome profile decoration", () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it("writes expected name + signed ARGB seed to Chrome prefs", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
);
try {
decorateClawdProfile(userDataDir, { color: DEFAULT_CLAWD_BROWSER_COLOR });
const expectedSignedArgb = ((0xff << 24) | 0xff4500) >> 0;
const localState = await readJson(path.join(userDataDir, "Local State"));
const profile = localState.profile as Record<string, unknown>;
const infoCache = profile.info_cache as Record<string, unknown>;
const def = infoCache.Default as Record<string, unknown>;
expect(def.name).toBe(DEFAULT_CLAWD_BROWSER_PROFILE_NAME);
expect(def.shortcut_name).toBe(DEFAULT_CLAWD_BROWSER_PROFILE_NAME);
expect(def.profile_color_seed).toBe(expectedSignedArgb);
expect(def.profile_highlight_color).toBe(expectedSignedArgb);
expect(def.default_avatar_fill_color).toBe(expectedSignedArgb);
expect(def.default_avatar_stroke_color).toBe(expectedSignedArgb);
const prefs = await readJson(
path.join(userDataDir, "Default", "Preferences"),
);
const browser = prefs.browser as Record<string, unknown>;
const theme = browser.theme as Record<string, unknown>;
const autogenerated = prefs.autogenerated as Record<string, unknown>;
const autogeneratedTheme = autogenerated.theme as Record<string, unknown>;
expect(theme.user_color2).toBe(expectedSignedArgb);
expect(autogeneratedTheme.color).toBe(expectedSignedArgb);
const marker = await fsp.readFile(
path.join(userDataDir, ".clawd-profile-decorated"),
"utf-8",
);
expect(marker.trim()).toMatch(/^\d+$/);
} finally {
await fsp.rm(userDataDir, { recursive: true, force: true });
}
});
it("best-effort writes name when color is invalid", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
);
try {
decorateClawdProfile(userDataDir, { color: "lobster-orange" });
const localState = await readJson(path.join(userDataDir, "Local State"));
const profile = localState.profile as Record<string, unknown>;
const infoCache = profile.info_cache as Record<string, unknown>;
const def = infoCache.Default as Record<string, unknown>;
expect(def.name).toBe(DEFAULT_CLAWD_BROWSER_PROFILE_NAME);
expect(def.profile_color_seed).toBeUndefined();
} finally {
await fsp.rm(userDataDir, { recursive: true, force: true });
}
});
it("recovers from missing/invalid preference files", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
);
try {
await fsp.mkdir(path.join(userDataDir, "Default"), { recursive: true });
await fsp.writeFile(path.join(userDataDir, "Local State"), "{", "utf-8"); // invalid JSON
await fsp.writeFile(
path.join(userDataDir, "Default", "Preferences"),
"[]", // valid JSON but wrong shape
"utf-8",
);
decorateClawdProfile(userDataDir, { color: DEFAULT_CLAWD_BROWSER_COLOR });
const localState = await readJson(path.join(userDataDir, "Local State"));
expect(typeof localState.profile).toBe("object");
const prefs = await readJson(
path.join(userDataDir, "Default", "Preferences"),
);
expect(typeof prefs.profile).toBe("object");
} finally {
await fsp.rm(userDataDir, { recursive: true, force: true });
}
});
it("is idempotent when rerun on an existing profile", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
);
try {
decorateClawdProfile(userDataDir, { color: DEFAULT_CLAWD_BROWSER_COLOR });
decorateClawdProfile(userDataDir, { color: DEFAULT_CLAWD_BROWSER_COLOR });
const prefs = await readJson(
path.join(userDataDir, "Default", "Preferences"),
);
const profile = prefs.profile as Record<string, unknown>;
expect(profile.name).toBe(DEFAULT_CLAWD_BROWSER_PROFILE_NAME);
} finally {
await fsp.rm(userDataDir, { recursive: true, force: true });
}
});
});
describe("browser chrome helpers", () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
});
it("picks the first existing Chrome candidate on macOS", () => {
const exists = vi
.spyOn(fs, "existsSync")
.mockImplementation((p) => String(p).includes("Google Chrome Canary"));
const exe = findChromeExecutableMac();
expect(exe?.kind).toBe("canary");
expect(exe?.path).toMatch(/Google Chrome Canary/);
exists.mockRestore();
});
it("returns null when no Chrome candidate exists", () => {
const exists = vi.spyOn(fs, "existsSync").mockReturnValue(false);
expect(findChromeExecutableMac()).toBeNull();
exists.mockRestore();
});
it("reports reachability based on /json/version", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({ ok: true } as unknown as Response),
);
await expect(isChromeReachable(12345, 50)).resolves.toBe(true);
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({ ok: false } as unknown as Response),
);
await expect(isChromeReachable(12345, 50)).resolves.toBe(false);
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("boom")));
await expect(isChromeReachable(12345, 50)).resolves.toBe(false);
});
it("stopClawdChrome no-ops when process is already killed", async () => {
const proc = { killed: true, exitCode: null, kill: vi.fn() };
await stopClawdChrome(
{
proc,
cdpPort: 12345,
} as unknown as Parameters<typeof stopClawdChrome>[0],
10,
);
expect(proc.kill).not.toHaveBeenCalled();
});
it("stopClawdChrome sends SIGTERM and returns once CDP is down", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("down")));
const proc = { killed: false, exitCode: null, kill: vi.fn() };
await stopClawdChrome(
{
proc,
cdpPort: 12345,
} as unknown as Parameters<typeof stopClawdChrome>[0],
10,
);
expect(proc.kill).toHaveBeenCalledWith("SIGTERM");
});
});

View File

@@ -1,6 +1,16 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { browserStatus } from "./client.js";
import {
browserClickRef,
browserDom,
browserEval,
browserOpenTab,
browserQuery,
browserScreenshot,
browserSnapshot,
browserStatus,
browserTabs,
} from "./client.js";
describe("browser client", () => {
afterEach(() => {
@@ -21,4 +31,157 @@ describe("browser client", () => {
/Start .*gateway/i,
);
});
it("adds useful timeout messaging for abort-like failures", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("aborted")));
await expect(browserStatus("http://127.0.0.1:18791")).rejects.toThrow(
/timed out/i,
);
});
it("surfaces non-2xx responses with body text", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: false,
status: 409,
text: async () => "conflict",
} as unknown as Response),
);
await expect(
browserEval("http://127.0.0.1:18791", { js: "1+1" }),
).rejects.toThrow(/409: conflict/i);
});
it("uses the expected endpoints + methods for common calls", async () => {
const calls: Array<{ url: string; init?: RequestInit }> = [];
vi.stubGlobal(
"fetch",
vi.fn(async (url: string, init?: RequestInit) => {
calls.push({ url, init });
if (url.endsWith("/tabs") && (!init || init.method === undefined)) {
return {
ok: true,
json: async () => ({
running: true,
tabs: [{ targetId: "t1", title: "T", url: "https://x" }],
}),
} as unknown as Response;
}
if (url.endsWith("/tabs/open")) {
return {
ok: true,
json: async () => ({
targetId: "t2",
title: "N",
url: "https://y",
}),
} as unknown as Response;
}
if (url.includes("/screenshot")) {
return {
ok: true,
json: async () => ({
ok: true,
path: "/tmp/a.png",
targetId: "t1",
url: "https://x",
}),
} as unknown as Response;
}
if (url.includes("/query?")) {
return {
ok: true,
json: async () => ({
ok: true,
targetId: "t1",
url: "https://x",
matches: [{ index: 0, tag: "a" }],
}),
} as unknown as Response;
}
if (url.includes("/dom?")) {
return {
ok: true,
json: async () => ({
ok: true,
targetId: "t1",
url: "https://x",
format: "text",
text: "hi",
}),
} as unknown as Response;
}
if (url.includes("/snapshot?")) {
return {
ok: true,
json: async () => ({
ok: true,
format: "aria",
targetId: "t1",
url: "https://x",
nodes: [],
}),
} as unknown as Response;
}
if (url.endsWith("/click")) {
return {
ok: true,
json: async () => ({ ok: true, targetId: "t1", url: "https://x" }),
} as unknown as Response;
}
return {
ok: true,
json: async () => ({
enabled: true,
controlUrl: "http://127.0.0.1:18791",
running: true,
pid: 1,
cdpPort: 18792,
chosenBrowser: "chrome",
userDataDir: "/tmp",
color: "#FF4500",
headless: false,
attachOnly: false,
}),
} as unknown as Response;
}),
);
await expect(
browserStatus("http://127.0.0.1:18791"),
).resolves.toMatchObject({
running: true,
cdpPort: 18792,
});
await expect(browserTabs("http://127.0.0.1:18791")).resolves.toHaveLength(
1,
);
await expect(
browserOpenTab("http://127.0.0.1:18791", "https://example.com"),
).resolves.toMatchObject({ targetId: "t2" });
await expect(
browserScreenshot("http://127.0.0.1:18791", { fullPage: true }),
).resolves.toMatchObject({ ok: true, path: "/tmp/a.png" });
await expect(
browserQuery("http://127.0.0.1:18791", { selector: "a", limit: 1 }),
).resolves.toMatchObject({ ok: true });
await expect(
browserDom("http://127.0.0.1:18791", { format: "text", maxChars: 10 }),
).resolves.toMatchObject({ ok: true });
await expect(
browserSnapshot("http://127.0.0.1:18791", { format: "aria", limit: 1 }),
).resolves.toMatchObject({ ok: true, format: "aria" });
await expect(
browserClickRef("http://127.0.0.1:18791", { ref: "1" }),
).resolves.toMatchObject({ ok: true });
expect(calls.some((c) => c.url.endsWith("/tabs"))).toBe(true);
const open = calls.find((c) => c.url.endsWith("/tabs/open"));
expect(open?.init?.method).toBe("POST");
});
});

431
src/browser/server.test.ts Normal file
View File

@@ -0,0 +1,431 @@
import { type AddressInfo, createServer } from "node:net";
import { fetch as realFetch } from "undici";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
let testPort = 0;
let reachable = false;
let cfgAttachOnly = false;
let createTargetId: string | null = null;
let screenshotThrowsOnce = false;
function makeProc(pid = 123) {
const handlers = new Map<string, Array<(...args: unknown[]) => void>>();
return {
pid,
killed: false,
exitCode: null as number | null,
on: (event: string, cb: (...args: unknown[]) => void) => {
handlers.set(event, [...(handlers.get(event) ?? []), cb]);
return undefined;
},
emitExit: () => {
for (const cb of handlers.get("exit") ?? []) cb(0);
},
kill: () => {
return true;
},
};
}
const proc = makeProc();
vi.mock("../config/config.js", () => ({
loadConfig: () => ({
browser: {
enabled: true,
controlUrl: `http://127.0.0.1:${testPort}`,
color: "#FF4500",
attachOnly: cfgAttachOnly,
headless: true,
},
}),
}));
const launchCalls = vi.hoisted(() => [] as Array<{ port: number }>);
vi.mock("./chrome.js", () => ({
isChromeReachable: vi.fn(async () => reachable),
launchClawdChrome: vi.fn(async (resolved: { cdpPort: number }) => {
launchCalls.push({ port: resolved.cdpPort });
reachable = true;
return {
pid: 123,
exe: { kind: "chrome", path: "/fake/chrome" },
userDataDir: "/tmp/clawd",
cdpPort: resolved.cdpPort,
startedAt: Date.now(),
proc,
};
}),
stopClawdChrome: vi.fn(async () => {
reachable = false;
}),
}));
const evalCalls = vi.hoisted(() => [] as Array<string>);
let evalThrows = false;
vi.mock("./cdp.js", () => ({
createTargetViaCdp: vi.fn(async () => {
if (createTargetId) return { targetId: createTargetId };
throw new Error("cdp disabled");
}),
evaluateJavaScript: vi.fn(async ({ expression }: { expression: string }) => {
evalCalls.push(expression);
if (evalThrows) {
return {
exceptionDetails: { text: "boom" },
};
}
return { result: { type: "string", value: "ok" } };
}),
getDomText: vi.fn(async () => ({ text: "<html/>" })),
querySelector: vi.fn(async () => ({ matches: [{ index: 0, tag: "a" }] })),
snapshotAria: vi.fn(async () => ({
nodes: [{ ref: "1", role: "link", name: "x", depth: 0 }],
})),
snapshotDom: vi.fn(async () => ({
nodes: [{ ref: "1", parentRef: null, depth: 0, tag: "html" }],
})),
captureScreenshot: vi.fn(async () => {
if (screenshotThrowsOnce) {
screenshotThrowsOnce = false;
throw new Error("jpeg failed");
}
return Buffer.from("jpg");
}),
captureScreenshotPng: vi.fn(async () => Buffer.from("png")),
}));
vi.mock("./pw-ai.js", () => ({
clickRefViaPlaywright: vi.fn(async () => {}),
closePlaywrightBrowserConnection: vi.fn(async () => {}),
snapshotAiViaPlaywright: vi.fn(async () => ({ snapshot: "ok" })),
}));
vi.mock("../media/store.js", () => ({
ensureMediaDir: vi.fn(async () => {}),
saveMediaBuffer: vi.fn(async () => ({ path: "/tmp/fake.png" })),
}));
vi.mock("./screenshot.js", () => ({
DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES: 128,
DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE: 64,
normalizeBrowserScreenshot: vi.fn(async (buf: Buffer) => ({
buffer: buf,
contentType: "image/png",
})),
}));
async function getFreePort(): Promise<number> {
return await new Promise((resolve, reject) => {
const s = createServer();
s.once("error", reject);
s.listen(0, "127.0.0.1", () => {
const port = (s.address() as AddressInfo).port;
s.close((err) => (err ? reject(err) : resolve(port)));
});
});
}
function makeResponse(
body: unknown,
init?: { ok?: boolean; status?: number; text?: string },
): Response {
const ok = init?.ok ?? true;
const status = init?.status ?? 200;
const text = init?.text ?? "";
return {
ok,
status,
json: async () => body,
text: async () => text,
} as unknown as Response;
}
describe("browser control server", () => {
beforeEach(async () => {
reachable = false;
cfgAttachOnly = false;
createTargetId = null;
screenshotThrowsOnce = false;
testPort = await getFreePort();
// Minimal CDP JSON endpoints used by the server.
let putNewCalls = 0;
vi.stubGlobal(
"fetch",
vi.fn(async (url: string, init?: RequestInit) => {
const u = String(url);
if (u.includes("/json/list")) {
if (!reachable) return makeResponse([]);
return makeResponse([
{
id: "abcd1234",
title: "Tab",
url: "https://example.com",
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/abcd1234",
type: "page",
},
{
id: "abce9999",
title: "Other",
url: "https://other",
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/abce9999",
type: "page",
},
]);
}
if (u.includes("/json/new?")) {
if (init?.method === "PUT") {
putNewCalls += 1;
if (putNewCalls === 1) {
return makeResponse({}, { ok: false, status: 405, text: "" });
}
}
return makeResponse({
id: "newtab1",
title: "",
url: "about:blank",
webSocketDebuggerUrl: "ws://127.0.0.1/devtools/page/newtab1",
type: "page",
});
}
if (u.includes("/json/activate/")) return makeResponse("ok");
if (u.includes("/json/close/")) return makeResponse("ok");
return makeResponse({}, { ok: false, status: 500, text: "unexpected" });
}),
);
});
afterEach(async () => {
vi.unstubAllGlobals();
vi.restoreAllMocks();
const { stopBrowserControlServer } = await import("./server.js");
await stopBrowserControlServer();
});
it("serves status + starts browser when requested", async () => {
const { startBrowserControlServerFromConfig } = await import("./server.js");
const started = await startBrowserControlServerFromConfig();
expect(started?.port).toBe(testPort);
const base = `http://127.0.0.1:${testPort}`;
const s1 = (await realFetch(`${base}/`).then((r) => r.json())) as {
running: boolean;
pid: number | null;
};
expect(s1.running).toBe(false);
expect(s1.pid).toBe(null);
await realFetch(`${base}/start`, { method: "POST" }).then((r) => r.json());
const s2 = (await realFetch(`${base}/`).then((r) => r.json())) as {
running: boolean;
pid: number | null;
chosenBrowser: string | null;
};
expect(s2.running).toBe(true);
expect(s2.pid).toBe(123);
expect(s2.chosenBrowser).toBe("chrome");
expect(launchCalls.length).toBeGreaterThan(0);
});
it("handles tabs: list, open, focus conflict on ambiguous prefix", async () => {
const { startBrowserControlServerFromConfig } = await import("./server.js");
await startBrowserControlServerFromConfig();
const base = `http://127.0.0.1:${testPort}`;
await realFetch(`${base}/start`, { method: "POST" }).then((r) => r.json());
const tabs = (await realFetch(`${base}/tabs`).then((r) => r.json())) as {
running: boolean;
tabs: Array<{ targetId: string }>;
};
expect(tabs.running).toBe(true);
expect(tabs.tabs.length).toBeGreaterThan(0);
const opened = await realFetch(`${base}/tabs/open`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://example.com" }),
}).then((r) => r.json());
expect(opened).toMatchObject({ targetId: "newtab1" });
const focus = await realFetch(`${base}/tabs/focus`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ targetId: "abc" }),
});
expect(focus.status).toBe(409);
});
it("maps JS exceptions to a 400 and returns results otherwise", async () => {
const { startBrowserControlServerFromConfig } = await import("./server.js");
await startBrowserControlServerFromConfig();
const base = `http://127.0.0.1:${testPort}`;
await realFetch(`${base}/start`, { method: "POST" }).then((r) => r.json());
evalThrows = true;
const bad = await realFetch(`${base}/eval`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ js: "throw 1" }),
});
expect(bad.status).toBe(400);
evalThrows = false;
const ok = (await realFetch(`${base}/eval`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ js: "1+1", await: true }),
}).then((r) => r.json())) as { ok: boolean; result?: unknown };
expect(ok.ok).toBe(true);
expect(evalCalls.length).toBeGreaterThan(0);
});
it("supports query/dom/snapshot/click/screenshot and stop", async () => {
const { startBrowserControlServerFromConfig } = await import("./server.js");
await startBrowserControlServerFromConfig();
const base = `http://127.0.0.1:${testPort}`;
await realFetch(`${base}/start`, { method: "POST" }).then((r) => r.json());
const query = (await realFetch(`${base}/query?selector=a&limit=1`).then(
(r) => r.json(),
)) as { ok: boolean; matches?: unknown[] };
expect(query.ok).toBe(true);
expect(Array.isArray(query.matches)).toBe(true);
const dom = (await realFetch(`${base}/dom?format=text&maxChars=10`).then(
(r) => r.json(),
)) as { ok: boolean; text?: string };
expect(dom.ok).toBe(true);
expect(typeof dom.text).toBe("string");
const snapAria = (await realFetch(
`${base}/snapshot?format=aria&limit=1`,
).then((r) => r.json())) as {
ok: boolean;
format?: string;
nodes?: unknown[];
};
expect(snapAria.ok).toBe(true);
expect(snapAria.format).toBe("aria");
const snapAi = (await realFetch(`${base}/snapshot?format=ai`).then((r) =>
r.json(),
)) as { ok: boolean; format?: string; snapshot?: string };
expect(snapAi.ok).toBe(true);
expect(snapAi.format).toBe("ai");
const click = (await realFetch(`${base}/click`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ref: "1" }),
}).then((r) => r.json())) as { ok: boolean };
expect(click.ok).toBe(true);
const shot = (await realFetch(`${base}/screenshot?fullPage=true`).then(
(r) => r.json(),
)) as { ok: boolean; path?: string };
expect(shot.ok).toBe(true);
expect(typeof shot.path).toBe("string");
const stopped = (await realFetch(`${base}/stop`, {
method: "POST",
}).then((r) => r.json())) as { ok: boolean; stopped?: boolean };
expect(stopped.ok).toBe(true);
expect(stopped.stopped).toBe(true);
});
it("covers common error branches", async () => {
cfgAttachOnly = true;
const { startBrowserControlServerFromConfig } = await import("./server.js");
await startBrowserControlServerFromConfig();
const base = `http://127.0.0.1:${testPort}`;
const missing = await realFetch(`${base}/tabs/open`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
expect(missing.status).toBe(400);
reachable = false;
const started = (await realFetch(`${base}/start`, {
method: "POST",
}).then((r) => r.json())) as { error?: string };
expect(started.error ?? "").toMatch(/attachOnly/i);
});
it("opens tabs via CDP createTarget path and falls back to PNG screenshots", async () => {
const { startBrowserControlServerFromConfig } = await import("./server.js");
await startBrowserControlServerFromConfig();
const base = `http://127.0.0.1:${testPort}`;
await realFetch(`${base}/start`, { method: "POST" }).then((r) => r.json());
createTargetId = "abcd1234";
const opened = (await realFetch(`${base}/tabs/open`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: "https://example.com" }),
}).then((r) => r.json())) as { targetId?: string };
expect(opened.targetId).toBe("abcd1234");
screenshotThrowsOnce = true;
const shot = (await realFetch(`${base}/screenshot`).then((r) =>
r.json(),
)) as { ok: boolean; path?: string };
expect(shot.ok).toBe(true);
expect(typeof shot.path).toBe("string");
});
it("covers additional endpoint branches", async () => {
const { startBrowserControlServerFromConfig } = await import("./server.js");
await startBrowserControlServerFromConfig();
const base = `http://127.0.0.1:${testPort}`;
const tabsWhenStopped = (await realFetch(`${base}/tabs`).then((r) =>
r.json(),
)) as { running: boolean; tabs: unknown[] };
expect(tabsWhenStopped.running).toBe(false);
expect(Array.isArray(tabsWhenStopped.tabs)).toBe(true);
const focusStopped = await realFetch(`${base}/tabs/focus`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ targetId: "abcd" }),
});
expect(focusStopped.status).toBe(409);
await realFetch(`${base}/start`, { method: "POST" }).then((r) => r.json());
const focusMissing = await realFetch(`${base}/tabs/focus`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ targetId: "zzz" }),
});
expect(focusMissing.status).toBe(404);
const delAmbiguous = await realFetch(`${base}/tabs/abc`, {
method: "DELETE",
});
expect(delAmbiguous.status).toBe(409);
const shotAmbiguous = await realFetch(`${base}/screenshot?targetId=abc`);
expect(shotAmbiguous.status).toBe(409);
const evalMissing = await realFetch(`${base}/eval`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
expect(evalMissing.status).toBe(400);
const queryMissing = await realFetch(`${base}/query`);
expect(queryMissing.status).toBe(400);
const snapDom = (await realFetch(
`${base}/snapshot?format=domSnapshot&limit=1`,
).then((r) => r.json())) as { ok: boolean; format?: string };
expect(snapDom.ok).toBe(true);
expect(snapDom.format).toBe("domSnapshot");
});
});