LazyBoy2/tests/handover_ui.mjs

61 lines
4.3 KiB
JavaScript

// Isolated browser fixture: no credentials or model requests leave this process.
import { chromium } from "../tools/playwright/node_modules/playwright/index.mjs";
import assert from "node:assert/strict";
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({serviceWorkers:"block", ignoreHTTPSErrors: true});
const page = await context.newPage();
page.setDefaultTimeout(5000);
page.on("pageerror",e=>console.error(e.message));
await page.addInitScript(() => localStorage.setItem("lazyboy.locale", "zh-Hant"));
let waiting = true;
const submitted = [];
const computerCalls = [];
const activity = () => ({
state: waiting ? "waiting_input" : "running", running: !waiting, queued: false,
active_task_ids: waiting ? [] : ["task"], observed_at_ms: Date.now(),
question: waiting ? { kind: "handoff", handover: true, task_id: "task", question_id: "q",
question: "請登入原網站", site_url: "https://example.com/login", options: ["完成"], surface: "box_browser" } : null,
});
await page.route("**/fixture-viewer**", (route) => route.fulfill({
contentType: "text/html", body: '<label>Password<input type="password" aria-label="Password"></label>',
}));
await page.route("**/api/**", async (route) => {
const path = new URL(route.request().url()).pathname;
if (path.endsWith("/activity")) return route.fulfill({ json: activity() });
if (path.endsWith("/handover")) {
submitted.push(route.request().postDataJSON());
waiting = false;
return route.fulfill({ json: { ok: true } });
}
if (path.endsWith("/messages")) throw new Error("handover must not post ordinary chat");
if (path.endsWith("/events")) return route.fulfill({ contentType:"text/event-stream",
body:'data: {"events":[{"id":1,"kind":"runtime","payload":{"type":"user_progress","message":"正在比對官方規則。"}},{"id":2,"kind":"runtime","payload":{"type":"progress","message":"JavaScript internal scratchpad"}},{"id":3,"kind":"runtime","payload":{"type":"user_progress","message":"正在整理攻略。"}}]}\n\n' });
if (path.endsWith("/computer")) {
computerCalls.push({ method: route.request().method(), path });
return route.fulfill({ json:{ready:true,state:"ready",viewer_url:"/fixture-viewer"} });
}
if (path === "/api/agents") return route.fulfill({ json:{agents:[{id:"owner",name:"測試"}]} });
if (path === "/api/agents/owner") return route.fulfill({ json:{id:"owner",name:"測試",expertise:"",preview:"",transcript:[],running:false,activity:activity()} });
return route.fulfill({ json:{} });
});
await page.goto(process.env.LAZYBOY_TEST_UI_URL || "http://127.0.0.1:5173");
await page.locator(".handover-card").waitFor();
await page.screenshot({path:"/tmp/lazyboy-handover.png",fullPage:true});
await page.frameLocator(".handover-viewer iframe").getByLabel("Password").fill("fixture-secret").catch(async e=>{console.error(await page.locator("body").innerText());console.error(page.frames().map(f=>f.url()));throw e;});
assert.equal(await page.locator(".thinking-row .avatar.thinking").count(),0);
assert(await page.locator(".composer textarea").isDisabled());
assert.equal(await page.locator("iframe.desktop-frame").count(),1);
assert(computerCalls.some((call) => call.method === "POST" && call.path === "/api/agents/owner/computer"), JSON.stringify(computerCalls));
assert((await page.locator("iframe.desktop-frame").getAttribute("src") || "").startsWith("/fixture-viewer"));
await page.getByRole("button",{name:"完成並繼續",exact:true}).click();
await page.locator(".handover-card").waitFor({state:"hidden"});
await page.locator(".thinking-row .avatar.thinking").waitFor();
assert.deepEqual(submitted,[{task_id:"task",question_id:"q",action:"done"}]);
assert(!(await page.locator("body").innerText()).includes("fixture-secret"));
assert(!(await page.locator("body").innerText()).includes("JavaScript internal scratchpad"));
assert.equal(await page.locator(".messages").getByText("正在整理攻略。",{exact:true}).count(),0);
assert(await page.locator(".composer textarea").isEnabled());
console.log("PASS automatic embedded handover, one viewer, paused animation, direct hand-back, and no secret/chat/log leakage");
} finally { await browser.close(); }