test/domains/form-errors-test.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
import { test, describe } from "node:test";
import assert from "node:assert/strict";
import { createFakeClient, type FakeClient } from "./fake-client";
import { fillTool, focusTool, selectOptionTool } from "../../src/domains/form";
import type { HandlerContext } from "../../src/util/tool";
import { ok } from "../../src/util/result";
const RESOLVED_NODE = ok({ object: { type: "object", objectId: "obj-1" } });
const THREW = ok({
result: { type: "undefined" },
exceptionDetails: { text: "Uncaught TypeError: el.dispatchEvent is not a function" },
});
const returned = (value: unknown) => ok({ result: { type: "object", value } });
const ctxFor = (fake: FakeClient): HandlerContext => ({
client: fake.client,
signal: undefined,
onUpdate: () => {},
extensionCtx: undefined as never,
});
const withRef = async (callResult: ReturnType<typeof ok>): Promise<FakeClient> =>
createFakeClient({
refs: [{ ref: "e1", backendId: 101 }],
canned: { "DOM.resolveNode": RESOLVED_NODE, "Runtime.callFunctionOn": callResult },
});
describe("a page-side throw is reported as a throw, not as a missing element", () => {
test("browser_fill surfaces the page exception", async () => {
const fake = await withRef(THREW);
const r = await fillTool.handler({ ref: "e1", value: "x" }, ctxFor(fake));
assert.equal(r.success, false);
if (r.success) return;
assert.equal(r.error.kind, "cdp_error");
assert.doesNotMatch(r.error.message, /No element matched/);
assert.match(r.error.message, /dispatchEvent is not a function/);
});
test("browser_focus surfaces the page exception", async () => {
const fake = await withRef(THREW);
const r = await focusTool.handler({ ref: "e1" }, ctxFor(fake));
assert.equal(r.success, false);
if (r.success) return;
assert.equal(r.error.kind, "cdp_error");
assert.doesNotMatch(r.error.message, /No element matched/);
});
test("browser_select_option surfaces the page exception", async () => {
const fake = await withRef(THREW);
const r = await selectOptionTool.handler({ ref: "e1", label: "India" }, ctxFor(fake));
assert.equal(r.success, false);
if (r.success) return;
assert.equal(r.error.kind, "cdp_error");
assert.doesNotMatch(r.error.message, /No element matched/);
});
});
describe("a genuinely missing element still reports no match", () => {
test("the selector path reports no match when querySelector finds nothing", async () => {
const fake = await createFakeClient({
evaluate: () => ok({ ok: false, reason: "not_found" }),
});
const r = await fillTool.handler({ selector: "#nope", value: "x" }, ctxFor(fake));
assert.equal(r.success, false);
if (r.success) return;
assert.equal(r.error.kind, "invalid_state");
assert.match(r.error.message, /No element matched: #nope/);
});
test("a stale ref still reports a stale ref", async () => {
const fake = await createFakeClient({ refs: [{ ref: "e1", backendId: 101 }] });
const r = await fillTool.handler({ ref: "e9", value: "x" }, ctxFor(fake));
assert.equal(r.success, false);
if (r.success) return;
assert.match(r.error.message, /unknown or stale/);
});
});
describe("a well-formed page result still succeeds", () => {
test("browser_fill reports the value the page read back", async () => {
const fake = await withRef(returned({ ok: true, kind: "text", value: "typed", tag: "INPUT" }));
const r = await fillTool.handler({ ref: "e1", value: "typed" }, ctxFor(fake));
assert.equal(r.success, true);
if (!r.success) return;
assert.match(r.data.text, /Filled e1 = "typed"/);
});
test("browser_fill refuses a disabled field with the page's reason", async () => {
const fake = await withRef(returned({ ok: false, reason: "element is disabled", tag: "INPUT" }));
const r = await fillTool.handler({ ref: "e1", value: "typed" }, ctxFor(fake));
assert.equal(r.success, false);
if (r.success) return;
assert.match(r.error.message, /element is disabled/);
});
});
|