LazyBoy2/tests/streaming_ui.mjs

64 lines
4.0 KiB
JavaScript

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