// Uses an isolated browser and mocked API; no model requests or existing profile. 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 page = await browser.newPage(); page.setDefaultTimeout(8000); await page.addInitScript(() => localStorage.setItem("grokboy.locale", "zh-Hant")); page.on("pageerror", (e) => console.error(e.message)); let state = "running"; let disconnected = false; const activity = () => ({ state, running: state === "running", queued: state === "queued", active_task_ids: state === "running" ? ["task"] : [], observed_at_ms: Date.now(), question: state === "waiting_input" ? { question: "請確認資料", options: ["完成"] } : null, }); await page.route("**/api/**", async (route) => { const path = new URL(route.request().url()).pathname; if (path.endsWith("/activity")) { if (disconnected) return route.abort("connectionfailed"); return route.fulfill({ json: activity() }); } if (path.endsWith("/events")) { // Stale "working" events must not override an authoritative idle snapshot. return route.fulfill({ contentType: "text/event-stream", body: 'data: {"events":[{"id":1,"kind":"runtime","payload":{"type":"waiting"}}]}\n\n' }); } if (path === "/api/agents") return route.fulfill({ json: { agents: [{ id: "owner", name: "測試", running: true }], } }); if (path === "/api/agents/owner") return route.fulfill({ json: { id: "owner", name: "測試", expertise: "", preview: "", transcript: [], running: true, activity: activity(), } }); return route.fulfill({ json: {} }); }); await page.goto(process.env.GROKBOY_TEST_UI_URL || "http://127.0.0.1:5173"); await page.locator(".thinking-row .avatar.thinking").waitFor().catch(async (e) => { console.error(await page.locator("body").innerText()); throw e; }); state = "idle"; await page.waitForFunction(() => !document.querySelector(".thinking-row .avatar.thinking"), { timeout: 6000 }); assert.equal(await page.locator(".working-label").count(), 0); state = "queued"; await page.locator(".working-label").waitFor(); assert.equal(await page.locator(".thinking-row .avatar.thinking").count(), 0); state = "waiting_input"; await page.getByText("請確認資料", { exact: true }).waitFor(); assert.equal(await page.locator(".working-label").count(), 0); state = "running"; await page.locator(".thinking-row .avatar.thinking").waitFor(); disconnected = true; await page.waitForFunction(() => !document.querySelector(".thinking-row .avatar.thinking"), { timeout: 6000 }); await page.getByText("暫時無法確認工作狀態,正在重新連線。", { exact: true }).waitFor(); disconnected = false; state = "idle"; await page.getByText("暫時無法確認工作狀態,正在重新連線。", { exact: true }).waitFor({ state: "hidden" }); console.log("PASS running/idle/queued/question/disconnect/reconnect; stale events and roster do not keep animation alive"); } finally { await browser.close(); }