Files
next-tjwater-frontend/tests/browser/agent-answer-ui.e2e.ts
T
jiang 2d8506fea0
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s
feat: add project-aware agent workspace
2026-10-22 18:52:18 +08:00

230 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { responsesFixture } from "./support/responses-fixture";
import { expect, test } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { answerUiDemoBlock as block } from "./support/answer-ui-demo";
import { captureReadingGeometry } from "./support/reading-geometry";
for (const width of [1440, 375, 320]) {
test(`local rich answers stream, restore and submit at ${width}px`, async ({ page, context }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ reducedMotion: "reduce" });
await mockWorkbenchContext(page, undefined, undefined, {}, [], { runtimeMode: "backend" });
await mockAgentApi(page, {
sessions: [
{
session_id: "browser-history-1",
title: "演练结构化回答",
created_at: Date.now(),
updated_at: Date.now()
}
]
});
const parts = [
{ type: "text", text: "以下资料均为演练数据。" },
{ type: "data-answer_ui", id: "pressure", data: block },
{ type: "text", text: "仍需用户补充现场核验资料。" },
{
type: "data-final_answer",
data: { content: "仍需用户补充现场核验资料。", ui_block_ids: ["pressure"] }
}
];
await page.route("**/api/v1/session/browser-history-1?**", async (route) => {
if (route.request().method() !== "GET") return route.fallback();
await route.fulfill({
headers: { "Access-Control-Allow-Origin": "*" },
json: {
session_id: "browser-history-1",
title: "演练结构化回答",
purpose: "ordinary_chat",
status: "active",
is_streaming: false,
messages: [
{
id: "user-fixture",
role: "user",
parts: [{ type: "text", text: "查看演练监测资料" }]
},
{ id: "rich-answer", role: "assistant", parts }
]
}
});
});
const prompts: string[] = [];
await page.route("**/api/v1/responses", async (route) => {
if (route.request().method() === "OPTIONS") return route.fallback();
const body = route.request().postDataJSON() as {
input: Array<{ role: string; content: Array<{ type: string; text?: string }> }>;
};
const text =
body.input
.filter((m) => m.role === "user")
.at(-1)
?.content.filter((p) => p.type === "input_text")
.map((p) => p.text)
.join("") ?? "";
prompts.push(text);
const chunks =
prompts.length === 1
? [
{ type: "start", messageId: "rich-answer" },
{ type: "text-start", id: "answer" },
{ type: "text-delta", id: "answer", delta: parts[0].text },
{ type: "text-end", id: "answer" },
{ ...parts[1], data: { ...block, fallback_text: "演练初始数据" } },
parts[1],
{ type: "text-start", id: "answer-1" },
{ type: "text-delta", id: "answer-1", delta: parts[2].text },
{ type: "text-end", id: "answer-1" },
parts[3],
{ type: "finish", finishReason: "stop" }
]
: [
{ type: "start", messageId: "form-answer" },
{ type: "text-start", id: "answer" },
{ type: "text-delta", id: "answer", delta: "已收到补充资料,尚未保存业务证据。" },
{ type: "text-end", id: "answer" },
{ type: "finish", finishReason: "stop" }
];
await route.fulfill({
headers: {
"Access-Control-Allow-Origin": "*",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache"
},
body: responsesFixture(chunks)
});
});
await page.goto("/", { waitUntil: "domcontentloaded" });
await page
.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ })
.click();
const panel = page.getByRole("complementary", { name: "Agent 命令面板" });
await panel.locator("textarea").fill("查看演练监测资料");
await panel.getByRole("button", { name: "发送 Agent 指令" }).click();
const ui = panel.locator('[data-answer-ui="pressure"]');
await expect(ui).toHaveCount(1);
const process = panel
.locator("details")
.filter({ has: page.locator("summary", { hasText: "查看处理过程" }) });
await expect(process).not.toHaveAttribute("open");
await expect(panel.getByText("以下资料均为演练数据。", { exact: true })).toBeHidden();
await process.locator("summary").click();
await expect(panel.getByText("以下资料均为演练数据。", { exact: true })).toBeVisible();
await process.locator("summary").click();
if (width >= 1024) {
const handle = page.getByRole("separator", { name: "调整 Agent 面板宽度" });
for (const panelWidth of [400, 640]) {
const box = (await handle.boundingBox())!;
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(panelWidth, box.y + box.height / 2);
await page.mouse.up();
await expect.poll(async () => Math.round((await panel.boundingBox())!.width)).toBe(panelWidth);
const columns = await ui
.locator(".answer-ui-stack-comparison")
.evaluate((element) => getComputedStyle(element).gridTemplateColumns.split(" ").length);
expect(columns).toBe(2);
}
}
await expect(ui.getByText("0.30", { exact: false }).first()).toBeVisible();
await expect(panel.getByText("交互回答", { exact: true })).toBeVisible();
await panel.locator(".agent-conversation-scroll").evaluate((element) => { element.scrollTop = 0; });
await expect(panel.getByText("查看演练监测资料", { exact: true })).toBeVisible();
await panel.screenshot({ path: `test-results/agent-chat-redesign-${width}.png` });
const lineTab = ui.getByRole("tab", { name: "折线", exact: true });
await lineTab.focus();
await page.keyboard.press("ArrowRight");
await expect(ui.getByRole("tab", { name: "柱状", exact: true })).toBeFocused();
await page.keyboard.press("ArrowRight");
await expect(ui.getByRole("tab", { name: "数据", exact: true })).toHaveAttribute("aria-selected", "true");
await expect(ui.getByRole("table")).toContainText("0.28");
await ui.getByRole("button", { name: "参考压力(MPa)", exact: true }).click();
await expect(ui.getByRole("columnheader", { name: /参考压力/ })).toHaveCount(0);
await ui.getByRole("button", { name: "参考压力(MPa)", exact: true }).click();
await expect(ui.getByRole("columnheader", { name: /参考压力/ })).toBeVisible();
await expect(ui.getByRole("combobox", { name: "区间起点" })).toBeHidden();
await ui.getByRole("button", { name: "筛选数据范围" }).click();
await ui.getByRole("combobox", { name: "区间起点" }).click();
await page.getByRole("option", { name: "09:00", exact: true }).click();
await expect(ui.getByRole("table")).not.toContainText("08:00");
await ui.getByRole("button", { name: "筛选数据范围" }).click();
await expect(ui.getByText("09:00 至 09:00", { exact: true })).toBeVisible();
await expect(ui.getByRole("table")).not.toContainText("08:00");
await ui.getByRole("button", { name: "筛选数据范围" }).click();
await ui.getByRole("button", { name: "重置范围" }).click();
await expect(ui.getByRole("table")).toContainText("08:00");
await ui.getByRole("button", { name: "筛选数据范围" }).click();
expect(prompts).toHaveLength(1);
const evidence = ui.getByRole("button", { name: /查看演练证据/ });
await evidence.focus();
await page.keyboard.press("Enter");
await expect(ui.getByText("仅用于组件验收,不代表已核验现场事实。")).toBeVisible();
await expect(panel.getByText("仍需用户补充现场核验资料。", { exact: true })).toHaveCount(1);
const bounds = await ui.evaluate((element) => {
const rect = element.getBoundingClientRect();
const panel = element.closest("aside")!.getBoundingClientRect();
return rect.left >= panel.left && rect.right <= panel.right;
});
expect(bounds).toBe(true);
await ui.getByRole("table").scrollIntoViewIfNeeded();
await panel.screenshot({ path: `test-results/answer-ui-${width}.png` });
const copy = panel.getByRole("button", { name: "复制回复", exact: true }).first();
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await copy.click();
await expect(panel.getByRole("button", { name: "回复已复制", exact: true }).first()).toBeVisible();
expect(await page.evaluate(() => navigator.clipboard.readText())).toContain("仍需用户补充现场核验资料。");
// The same editor and data selection move into the workspace and back.
await ui.getByLabel("现场位置(必填)").fill("尚未提交的现场位置");
await ui.getByRole("tab", { name: "数据", exact: true }).click();
await ui.getByRole("textbox", { name: "搜索演练监测趋势" }).fill("09:00");
await ui.getByRole("checkbox", { name: "选择第 2 行" }).check();
await panel.getByRole("button", { name: "展开查看", exact: true }).click();
const workspace = page.getByRole("region", { name: "展开报告工作区", exact: true });
await expect(workspace).toBeVisible();
await expect(page.getByLabel("现场位置(必填)")).toHaveCount(1);
await expect(workspace.getByLabel("现场位置(必填)")).toHaveValue("尚未提交的现场位置");
await expect(workspace.getByRole("textbox", { name: "搜索演练监测趋势" })).toHaveValue("09:00");
await expect(workspace.getByRole("checkbox", { name: "选择第 2 行" })).toBeChecked();
await workspace.getByRole("tab", { name: "折线", exact: true }).click();
for (const close of await page.getByRole("button", { name: "关闭通知", exact: true }).all()) await close.click();
await page.evaluate(() => document.fonts.ready);
const geometry = await captureReadingGeometry(workspace, testInfo);
expect(geometry.metricsHeight).toBeLessThan(145);
expect(geometry.chartOffset).toBeLessThan(430);
expect(geometry.horizontalOverflow).toBe(false);
await page.screenshot({ path: `test-results/answer-workspace-${width}.png` });
await workspace.getByRole("tab", { name: "数据", exact: true }).click();
await page.keyboard.press("Escape");
await expect(workspace).toHaveCount(0);
await expect(ui.getByLabel("现场位置(必填)")).toHaveValue("尚未提交的现场位置");
await expect(panel.getByRole("button", { name: "展开查看", exact: true })).toBeFocused();
await panel.getByRole("button", { name: "展开查看", exact: true }).click();
await workspace.locator("[data-interactive-table]").getByRole("button", { name: "针对这部分提问" }).click();
await expect(workspace).toHaveCount(0);
await panel.getByText("查看引用内容", { exact: true }).click();
await expect(panel.getByText("引用内容块:pressure", { exact: false })).toBeVisible();
expect(prompts).toHaveLength(1);
await panel.getByRole("button", { name: "移除内容引用" }).click();
// Leave the current editor before restoring canonical persisted history.
await panel.getByRole("button", { name: "新建 Agent 对话" }).click();
await expect(ui).toHaveCount(0);
await panel.getByRole("button", { name: /历史/ }).click();
await page
.locator("button.agent-history-session-trigger")
.filter({ hasText: "演练结构化回答" })
.click();
await expect(ui).toHaveCount(1);
await expect(ui.getByLabel("现场位置(必填)")).toHaveValue("");
expect(prompts).toHaveLength(1);
await ui.getByLabel("现场位置(必填)").fill("演练管段东侧");
await ui.getByRole("button", { name: "发送补充资料" }).click();
await expect.poll(() => prompts.length).toBe(2);
expect(prompts[1]).toContain("现场位置:演练管段东侧");
await expect(
panel.getByText("已收到补充资料,尚未保存业务证据。", { exact: true })
).toBeVisible();
});
}