// 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: '', })); 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(); }