// Mocked SSE and API snapshots: partial text, replay, retry reset and persistence. 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("lazyboy.locale", "zh-Hant"); window.sources = []; window.EventSource = class { constructor() { window.sources.push(this); } close() {} }; }); const errors = []; page.on("pageerror", error => errors.push(error.message)); let transcript = [{ role: "user", content: "請直接回答" }]; const activity = { state: "running", running: true, queued: false, active_task_ids: [], observed_at_ms: Date.now() }; await page.route("**/api/**", route => { const path = new URL(route.request().url()).pathname; if (path === "/api/agents") return route.fulfill({ json: { agents: [{ id: "owner", name: "測試" }] } }); if (path.endsWith("/activity")) return route.fulfill({ json: activity }); if (path.endsWith("/computer")) return route.fulfill({ json: { ready: false, state: "error", error: "fixture" } }); if (path === "/api/agents/owner") return route.fulfill({ json: { id: "owner", name: "測試", transcript, activity } }); return route.fulfill({ json: {} }); }); await page.goto(process.env.LAZYBOY_TEST_UI_URL || "http://127.0.0.1:5173"); await page.locator(".messages").getByText("請直接回答", { exact: true }).waitFor(); await page.waitForFunction(() => window.sources.at(-1)?.onmessage); const emit = events => page.evaluate(events => window.sources.at(-1).onmessage({ data: JSON.stringify({ events }) }), events); const event = (id, type, fields = {}) => ({ id, kind: "runtime", payload: { type, id: "message-1", ...fields } }); await emit([event(1, "message_delta", { delta: "正在" })]); await page.locator(".messages").getByText("正在", { exact: true }).waitFor(); await emit([event(1, "message_delta", { delta: "正在" }), event(2, "message_delta", { delta: "回答" })]); await page.locator(".messages").getByText("正在回答", { exact: true }).waitFor(); assert.equal(await page.locator(".messages").getByText("正在正在回答", { exact: true }).count(), 0); const reconnect = page.waitForResponse(response => new URL(response.url()).pathname === "/api/agents/owner"); await page.evaluate(() => window.sources.at(-1).onopen()); await reconnect; await page.locator(".messages").getByText("正在回答", { exact: true }).waitFor(); await emit([event(3, "message_reset")]); await page.locator(".messages").getByText("正在回答", { exact: true }).waitFor({ state: "hidden" }); await emit([event(4, "message_delta", { delta: "完成" })]); await page.locator(".messages").getByText("完成", { exact: true }).waitFor(); await emit([event(5, "message_end", { content: "完成回答" })]); await page.locator(".messages").getByText("完成回答", { exact: true }).waitFor(); transcript = [...transcript, { role: "assistant", id: "message-1", content: "完成回答" }]; const persisted = page.waitForResponse(response => new URL(response.url()).pathname === "/api/agents/owner"); await emit([{ id: 6, kind: "reply", payload: { verdict: "answer", message: "完成回答" } }]); await persisted; assert.equal(await page.locator(".messages").getByText("完成回答", { exact: true }).count(), 1); await page.reload(); await page.locator(".messages").getByText("完成回答", { exact: true }).waitFor(); await page.waitForFunction(() => window.sources.at(-1)?.onmessage); await emit([event(1, "message_delta", { delta: "舊內容" }), event(2, "message_reset")]); assert.equal(await page.locator(".messages").getByText("完成回答", { exact: true }).count(), 1); assert.deepEqual(errors, []); console.log("PASS incremental text appears before completion; replay/reset/reconnect/reload stay consistent"); } finally { await browser.close(); }