Files
next-tjwater-frontend/tests/browser/agent-ui-phase-anchors.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

94 lines
7.4 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import type { AnswerViewStateRecord, AnswerViewStateWrite } from "../../src/features/agent/api/answer-view-state";
for (const width of [1440, 375, 320]) {
test(`final UI anchors isolate process content and restore final controls at ${width}px`, async ({ page, context }) => {
test.setTimeout(60000);
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page, undefined, undefined, {}, [], { runtimeMode: "backend" });
const sessionId = "anchored-answer", title = "最终界面定位";
await mockAgentApi(page, { sessions: [{ session_id: sessionId, title, created_at: Date.now(), updated_at: Date.now() }] });
const process = {
version: "answer-ui@1", block_id: "query-process", phase: "process", fallback_text: "中间查询资料,仅过程使用。",
spec: { root: "note", elements: { note: { type: "Text", props: { text: "中间查询资料,仅过程使用。", tone: "neutral" }, children: [] } } },
};
const table = {
version: "answer-ui@1", block_id: "report-table", phase: "final", fallback_text: "最终读数:北侧 0.30 MPa,南侧 0.28 MPa。",
spec: { root: "table", elements: { table: { type: "Table", props: { title: "最终读数", columns: ["位置", "压力"], rows: [["北侧", "0.30 MPa"], ["南侧", "0.28 MPa"]] }, children: [] } } },
};
const form = {
version: "answer-ui@1", block_id: "report-form", phase: "final", fallback_text: "可补充现场记录。",
spec: { root: "form", elements: { form: { type: "FollowUpForm", props: { title: "补充现场资料", description: "由用户主动补充。", fields: [{ name: "record", label: "现场记录", kind: "text", required: true }] }, children: [] } } },
};
const headers = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, PUT, POST, OPTIONS", "Access-Control-Allow-Headers": "Authorization, Content-Type, X-Project-Id" };
await page.route(`**/api/v1/session/${sessionId}?**`, (route) => route.fulfill({ headers, json: {
session_id: sessionId, title, purpose: "ordinary_chat", status: "active", is_streaming: false,
messages: [{ id: "anchored", role: "assistant", parts: [
{ type: "text", text: "正在核对查询记录。" },
...[process, table, form].map((block) => ({ type: "data-answer_ui", id: block.block_id, data: block })),
{ type: "data-final_answer", data: { content: "前置结论\n\n[[ui:report-table]]\n\n表后分析\n\n[[ui:report-form]]\n\n行动建议", ui_block_ids: ["report-table", "query-process", "report-form"] } },
] }],
} }));
await page.route(`**/api/v1/session/${sessionId}/interactions**`, (route) => route.fulfill({ headers, json: { items: [] } }));
const records = new Map<string, AnswerViewStateRecord>();
const reads: string[] = [];
await page.route(`**/api/v1/session/${sessionId}/ui-state**`, async (route) => {
if (route.request().method() === "OPTIONS") return route.fulfill({ status: 204, headers });
const input = route.request().method() === "PUT" ? route.request().postDataJSON() as AnswerViewStateWrite : null;
const blockId = input?.block_id ?? new URL(route.request().url()).searchParams.get("block_id")!;
reads.push(blockId);
const current = records.get(blockId) ?? { version: "answer-view-state@1" as const, message_id: "anchored", block_id: blockId, revision: 0, schema_digest: "fixture", nodes: {}, block: { evidenceFilter: "all" }, updated_at: null };
const next = input ? { ...current, nodes: input.nodes, block: input.block, revision: current.revision + 1, updated_at: new Date().toISOString() } : current;
records.set(blockId, next);
await route.fulfill({ headers, json: next });
});
const openHistory = async () => {
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 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: title }).click();
return panel.locator('[data-agent-message-id="anchored"]');
};
await page.goto("/", { waitUntil: "domcontentloaded" });
let message = await openHistory();
await expect(message.getByText("前置结论", { exact: true })).toBeVisible();
const finalTable = message.locator('[data-answer-ui="report-table"]');
const finalForm = message.locator('[data-answer-ui="report-form"]');
await expect(finalTable).toHaveAttribute("data-answer-phase", "final");
const ordered = await message.evaluate((element) => [...element.querySelectorAll('.agent-streaming-response, [data-answer-ui]')]
.filter((node) => !node.closest("details"))
.map((node) => node.getAttribute("data-answer-ui") ?? node.textContent?.trim()));
expect(ordered).toEqual(["前置结论", "report-table", "表后分析", "report-form", "行动建议"]);
await expect(message.locator('[data-answer-ui="query-process"]')).toBeHidden();
await finalTable.getByRole("textbox", { name: "搜索最终读数" }).fill("北侧");
await finalForm.getByLabel("现场记录(必填)").fill("未提交的现场草稿");
await expect.poll(() => records.get("report-form")?.nodes.form).toMatchObject({ type: "FollowUpForm", values: { record: "未提交的现场草稿" } });
await expect.poll(() => records.get("report-table")?.nodes.table).toMatchObject({ type: "Table", query: "北侧" });
await message.getByText("查看处理过程", { exact: true }).click();
await expect(message.locator('[data-answer-ui="query-process"]')).toBeVisible();
expect(reads).not.toContain("query-process");
expect(reads).not.toContain("anchored-process");
await message.getByText("查看处理过程", { exact: true }).click();
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await message.getByRole("button", { name: "复制回复", exact: true }).click();
const copied = await page.evaluate(() => navigator.clipboard.readText());
expect(copied).toContain("最终读数");
expect(copied).not.toContain("中间查询资料");
expect(copied).not.toContain("[[ui:");
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await finalTable.screenshot({ path: `/tmp/agent-ui-anchors-table-${width}.png` });
await finalForm.screenshot({ path: `/tmp/agent-ui-anchors-form-${width}.png` });
await message.getByText("前置结论", { exact: true }).scrollIntoViewIfNeeded();
await page.getByRole("complementary", { name: "Agent 命令面板" }).screenshot({ path: `/tmp/agent-ui-anchors-${width}.png` });
await page.reload({ waitUntil: "domcontentloaded" });
message = await openHistory();
await expect(message.getByLabel("现场记录(必填)")).toHaveValue("未提交的现场草稿");
await expect(message.getByRole("textbox", { name: "搜索最终读数" })).toHaveValue("北侧");
expect(reads).not.toContain("query-process");
expect(await message.evaluate((element) => element.scrollWidth > element.clientWidth)).toBe(false);
});
}