Files
next-tjwater-frontend/tests/browser/agent-connection-recovery.e2e.ts
T
jiang c0e4231b83
Generic Container CI/CD / test-build-publish (push) Successful in 1m52s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m52s
feat: expand intelligent investigation workspace
2026-10-30 17:30:19 +08:00

104 lines
6.3 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { responsesFixture } from "./support/responses-fixture";
test.setTimeout(60_000);
test("service detection recovers without a reload and retains the draft", async ({ page }) => {
await page.clock.install();
await mockWorkbenchContext(page);
await mockAgentApi(page);
let available = false;
let healthReads = 0;
const headers = { "Access-Control-Allow-Origin": "*" };
await page.route("**/health", (route) => {
healthReads++;
return route.fulfill({ headers, status: available ? 200 : 503, json: { ok: available, ready: available } });
});
await page.route("**/api/v1/models", (route) => available ? route.fallback() : route.fulfill({ headers, status: 503, json: { message: "temporarily unavailable" } }));
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
const state = panel.locator("header").getByRole("status");
await expect(state).toHaveText("离线");
const prompt = panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" });
await prompt.fill("恢复后继续核查压力");
available = true;
await page.clock.runFor(5_000);
await expect(state).toHaveText("在线");
await expect(prompt).toHaveValue("恢复后继续核查压力");
available = false;
await page.clock.runFor(15_000);
await expect(state).toHaveText("离线");
available = true;
await page.evaluate(() => window.dispatchEvent(new Event("focus")));
await expect(state).toHaveText("在线");
expect(healthReads).toBeGreaterThanOrEqual(4);
});
test("a completed business failure does not mark a healthy Agent offline", async ({ page }) => {
await mockWorkbenchContext(page);
await mockAgentApi(page);
await page.route("**/health", (route) => route.fulfill({ headers: { "Access-Control-Allow-Origin": "*" }, json: { ok: true, ready: true } }));
await page.route("**/api/v1/responses", (route) => route.fulfill({
headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" },
body: responsesFixture([{ type: "start", messageId: "business-failure" }, { type: "error", errorText: "本次数据查询失败" }, { type: "finish", finishReason: "error" }])
}));
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
const state = panel.locator("header").getByRole("status");
await expect(state).toHaveText("在线");
await panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" }).fill("核对压力");
await panel.getByRole("button", { name: "发送 Agent 指令", exact: true }).click();
await expect(page.getByText("本次数据查询失败", { exact: true }).first()).toBeVisible();
await expect(state).toHaveText("在线");
});
test("a disconnected session resubscribes without replaying input or moving an older reader", async ({ page }) => {
await page.clock.install();
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page);
const sessionId = "recover-stream";
await mockAgentApi(page, { sessions: [{ session_id: sessionId, title: "断线中的压力分析", created_at: Date.now(), updated_at: Date.now() }] });
const headers = { "Access-Control-Allow-Origin": "*" };
const history = Array.from({ length: 18 }, (_, i) => ({ id: `older-${i}`, role: "assistant", parts: [{ type: "text", text: `历史核查记录 ${i}:需要保留这段已读内容和阅读位置。` }] }));
const messages = (text: string) => [...history, { id: "stream-result", role: "assistant", parts: [{ type: "text", text }] }];
let subscriptions = 0;
let generationRequests = 0;
await page.route("**/health", (route) => route.fulfill({ headers, json: { ok: true, ready: true } }));
await page.route(`**/api/v1/session/${sessionId}?**`, (route) => route.fulfill({ headers, json: {
session_id: sessionId, title: "断线中的压力分析", purpose: "ordinary_chat", status: "active", is_streaming: true, run_status: "running", messages: messages("已收到第一段结果。")
} }));
await page.route(`**/api/v1/session/${sessionId}/stream**`, (route) => {
subscriptions++;
const finished = subscriptions > 1;
return route.fulfill({ headers: { ...headers, "Content-Type": "text/event-stream" }, body:
`event: state\ndata: ${JSON.stringify({ session_id: sessionId, messages: messages(finished ? "断线期间的结果已恢复。" : "已收到第一段结果。"), is_streaming: !finished, run_status: finished ? "completed" : "running" })}\n\n`
});
});
await page.route("**/api/v1/responses", (route) => { generationRequests++; return route.abort(); });
await page.goto("/", { waitUntil: "domcontentloaded" });
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
await panel.getByRole("button", { name: /历史/ }).click();
await page.locator("button.agent-history-session-trigger").filter({ hasText: "断线中的压力分析" }).click();
await expect.poll(() => subscriptions).toBe(1);
const prompt = panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" });
await prompt.fill("保留未发送的补充问题");
const scroll = panel.locator(".agent-conversation-scroll");
await page.clock.runFor(100);
await scroll.hover();
await page.mouse.wheel(0, -2_400);
await page.clock.runFor(100);
await expect.poll(() => scroll.evaluate((element) => element.scrollTop)).toBeLessThanOrEqual(2);
await page.clock.runFor(2_000);
await expect.poll(() => subscriptions).toBe(2);
await expect(panel.locator('[data-agent-message-id="stream-result"]')).toContainText("断线期间的结果已恢复。");
await expect(panel.locator('[data-agent-message-id="older-0"]')).toHaveCount(1);
await expect(prompt).toHaveValue("保留未发送的补充问题");
expect(await scroll.evaluate((element) => element.scrollTop)).toBeLessThanOrEqual(2);
expect(generationRequests).toBe(0);
});