Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
163 lines
6.0 KiB
TypeScript
163 lines
6.0 KiB
TypeScript
import type { AdapterContext, ComputerRef } from "@rakazo/adapter-kit";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
browserActFromTool,
|
|
browserNavigateFromTool,
|
|
browserSnapshotFromTool,
|
|
parseBrowserActions,
|
|
} from "./browser-tools.js";
|
|
import { filterPageBrowserTools, PAGE_BROWSER_TOOL_NAMES } from "./executor.js";
|
|
import { FakeBrowserProvider } from "./fake-browser.js";
|
|
|
|
const context: AdapterContext = {
|
|
operationId: "1",
|
|
traceId: "1",
|
|
spaceId: "w",
|
|
userId: "u",
|
|
signal: new AbortController().signal,
|
|
};
|
|
|
|
const computer: ComputerRef = {
|
|
id: "c1",
|
|
botId: "b1",
|
|
kind: "fake",
|
|
providerRef: "c1",
|
|
};
|
|
|
|
describe("browser tools", () => {
|
|
it("parses click/fill/type actions and rejects bad batches", () => {
|
|
expect(
|
|
parseBrowserActions([
|
|
{ kind: "fill", ref: "e1", text: "hi" },
|
|
{ kind: "click", ref: "e2" },
|
|
]),
|
|
).toEqual([
|
|
{ kind: "fill", ref: "e1", text: "hi" },
|
|
{ kind: "click", ref: "e2" },
|
|
]);
|
|
expect(() => parseBrowserActions([])).toThrow(/at least one/i);
|
|
expect(() => parseBrowserActions([{ kind: "click" }])).toThrow(/ref/i);
|
|
expect(() => parseBrowserActions([{ kind: "fill", ref: "e1" }])).toThrow(/text/i);
|
|
});
|
|
|
|
it("rejects URL credentials before invoking the browser", async () => {
|
|
const browser = new FakeBrowserProvider();
|
|
browser.navigateError = new Error("must not invoke browser");
|
|
for (const scheme of ["http", "https"]) {
|
|
expect(
|
|
await browserNavigateFromTool(browser, computer, context, {
|
|
url: `${scheme}://example:fake-password@example.test`,
|
|
}),
|
|
).toMatchObject({ error: expect.stringContaining("credentials") });
|
|
}
|
|
});
|
|
|
|
it("navigates, snapshots, and acts through the tool helpers", async () => {
|
|
const browser = new FakeBrowserProvider({
|
|
pages: {
|
|
"https://example.test/x": {
|
|
title: "Demo",
|
|
html: `<!doctype html><html><head><title>Demo</title></head><body>
|
|
<input aria-label="Name" />
|
|
<button>Save</button>
|
|
</body></html>`,
|
|
},
|
|
},
|
|
});
|
|
const navigated = await browserNavigateFromTool(browser, computer, context, {
|
|
url: "https://example.test/x",
|
|
});
|
|
expect(navigated).toMatchObject({ title: "Demo" });
|
|
expect("fallback" in navigated && navigated.fallback).toBeFalsy();
|
|
|
|
const snap = await browserSnapshotFromTool(browser, computer, context, {});
|
|
expect(snap).toMatchObject({ title: "Demo" });
|
|
expect(String((snap as { tree?: string }).tree)).toMatch(/\[e\d+\]/);
|
|
|
|
const nameRef = (snap as { elements: Array<{ ref: string; name: string }> }).elements.find(
|
|
(el) => el.name.includes("Name"),
|
|
)!.ref;
|
|
const acted = await browserActFromTool(browser, computer, context, {
|
|
actions: [{ kind: "fill", ref: nameRef, text: "Ada" }],
|
|
});
|
|
expect(acted).toMatchObject({ ok: true, completed: 1 });
|
|
});
|
|
|
|
it("toggles checkboxes via click without undoing activation", async () => {
|
|
const browser = new FakeBrowserProvider({
|
|
pages: {
|
|
"https://example.test/form": {
|
|
title: "Form",
|
|
html: `<!doctype html><html><head><title>Form</title></head><body>
|
|
<label><input type="checkbox" aria-label="Agree" /> Agree</label>
|
|
</body></html>`,
|
|
},
|
|
},
|
|
});
|
|
await browserNavigateFromTool(browser, computer, context, { url: "https://example.test/form" });
|
|
const before = await browserSnapshotFromTool(browser, computer, context, {});
|
|
const agree = (
|
|
before as { elements: Array<{ ref: string; name: string; value?: string }> }
|
|
).elements.find((el) => el.name.includes("Agree"))!;
|
|
expect(agree.value).toMatch(/unchecked|false|^$/i);
|
|
const acted = await browserActFromTool(browser, computer, context, {
|
|
actions: [{ kind: "click", ref: agree.ref }],
|
|
});
|
|
expect(acted).toMatchObject({ ok: true });
|
|
const after = await browserSnapshotFromTool(browser, computer, context, {});
|
|
const toggled = (after as { elements: Array<{ name: string; value?: string }> }).elements.find(
|
|
(el) => el.name.includes("Agree"),
|
|
)!;
|
|
expect(toggled.value).toMatch(/checked|true/i);
|
|
});
|
|
|
|
it("falls back to computer_act for link navigation the in-process page cannot load", async () => {
|
|
const browser = new FakeBrowserProvider({
|
|
pages: {
|
|
"https://example.test/start": {
|
|
title: "Start",
|
|
html: `<!doctype html><html><head><title>Start</title></head><body>
|
|
<a href="https://example.test/next">Next</a>
|
|
</body></html>`,
|
|
},
|
|
},
|
|
});
|
|
await browserNavigateFromTool(browser, computer, context, {
|
|
url: "https://example.test/start",
|
|
});
|
|
const snap = await browserSnapshotFromTool(browser, computer, context, {});
|
|
const link = (snap as { elements: Array<{ ref: string; name: string }> }).elements.find((el) =>
|
|
el.name.includes("Next"),
|
|
)!;
|
|
const acted = await browserActFromTool(browser, computer, context, {
|
|
actions: [{ kind: "click", ref: link.ref }],
|
|
});
|
|
expect(acted).toMatchObject({
|
|
ok: false,
|
|
fallback: "computer_act",
|
|
error: expect.stringMatching(/cannot navigate links|computer_act/i),
|
|
});
|
|
});
|
|
|
|
it("surfaces computer_act fallback from the tool layer", async () => {
|
|
const browser = new FakeBrowserProvider();
|
|
browser.forceFallback = "cannot attach to page";
|
|
const result = await browserNavigateFromTool(browser, computer, context, {
|
|
url: "https://example.test",
|
|
});
|
|
expect(result).toMatchObject({
|
|
fallback: "computer_act",
|
|
note: expect.stringMatching(/computer_act/i),
|
|
});
|
|
});
|
|
|
|
it("hides page browser tools when the computer is not graphical", () => {
|
|
const tools = [...PAGE_BROWSER_TOOL_NAMES].map((name) => ({ name }));
|
|
expect(filterPageBrowserTools(tools, true)).toHaveLength(3);
|
|
expect(filterPageBrowserTools(tools, false)).toEqual([]);
|
|
expect(filterPageBrowserTools([{ name: "computer_act" }, ...tools], false)).toEqual([
|
|
{ name: "computer_act" },
|
|
]);
|
|
});
|
|
});
|