Files
next-tjwater-frontend/tests/browser/agent-answer-components-motion.e2e.ts
T

114 lines
8.4 KiB
TypeScript
Raw 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 { expect, test, type Locator } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { responsesFixture } from "./support/responses-fixture";
const block = {
version: "answer-ui@1", block_id: "components-motion", fallback_text: "组件交互演练,不代表现场事实。",
spec: { root: "root", elements: {
root: { type: "Stack", props: { columns: "one", gap: "normal", intent: "report" }, children: ["result", "note", "metric", "table", "evidence", "form", "location"] },
result: { type: "Result", props: { title: "演练结果", summary: "待现场核验。", status: "unconfirmed", source: "浏览器演练" }, children: [] },
note: { type: "Text", props: { title: "数据边界", tone: "info", text: "仅供交互验收。" }, children: [] },
metric: { type: "Metric", props: { label: "演练入口压力", value: "0.2130", unit: "MPa", source: "浏览器演练" }, children: [] },
table: { type: "Table", props: { title: "演练记录", columns: ["对象", "压力(MPa)"], rows: [["入口一", "0.2130"], ["入口二", "0.2870"]] }, children: [] },
evidence: { type: "Evidence", props: { title: "演练依据", summary: "尚未核验现场事实。", source: "浏览器演练", kind: "demo", evidence_id: "DEMO-MOTION", observed_at: null }, children: [] },
form: { type: "FollowUpForm", props: { title: "现场核验", description: "由你补充后带入对话。", fields: [{ name: "record", label: "现场记录", kind: "text", required: true }] }, children: [] },
location: { type: "MapLocation", props: { label: "定位演练入口", layer: "junctions", feature_ids: ["DEMO-ENTRY"], source: "浏览器演练" }, children: [] },
} },
};
async function sampleToggle(button: Locator, selector: string) {
return button.evaluate(async (button, selector) => {
const reveal = button.closest("[data-answer-ui]")!.querySelector<HTMLElement>(selector)!;
button.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true }));
button.dispatchEvent(new MouseEvent("click", { bubbles: true, detail: 1 }));
const frames = [];
for (let index = 0; index < 18; index++) {
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
frames.push({ opacity: Number(getComputedStyle(reveal).opacity), hidden: reveal.hidden,
inert: reveal.inert, ariaHidden: reveal.getAttribute("aria-hidden") });
}
return frames;
}, selector);
}
for (const width of [1440, 320]) for (const reduce of [false, true]) {
test(`answer component disclosures ${reduce ? "reduced" : "normal"} motion at ${width}px`, async ({ page }, testInfo) => {
test.setTimeout(60000);
await page.setViewportSize({ width, height: 1000 });
await page.emulateMedia({ reducedMotion: reduce ? "reduce" : "no-preference" });
await mockWorkbenchContext(page, undefined, undefined, {}, [], { runtimeMode: "backend" });
await mockAgentApi(page);
await page.route("**/api/v1/responses", async (route) => {
if (route.request().method() === "OPTIONS") return route.fallback();
await route.fulfill({ headers: { "Access-Control-Allow-Origin": "*", "Content-Type": "text/event-stream" }, body: responsesFixture([
{ type: "start", messageId: "components-answer" },
{ type: "data-answer_ui", id: block.block_id, data: block },
{ type: "data-final_answer", data: { content: "组件演练,待现场核验。", ui_block_ids: [block.block_id] } },
{ type: "finish", finishReason: "stop" },
]) });
});
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(".agent-panel-prompt-textarea").fill("检查演练组件");
await panel.getByRole("button", { name: "发送 Agent 指令" }).click();
await expect(panel.locator('[data-answer-ui="components-motion"]')).toBeVisible();
await panel.getByRole("button", { name: "展开查看", exact: true }).click();
const workspace = page.getByRole("region", { name: "展开报告工作区", exact: true });
const ui = workspace.locator('[data-answer-ui="components-motion"]');
const evidence = ui.locator(".answer-ui-evidence");
const opened = await sampleToggle(evidence.getByRole("button").first(), ".answer-ui-evidence [data-answer-reveal]");
expect(opened.some((frame) => frame.opacity > 0 && frame.opacity < 1)).toBe(!reduce);
await expect(evidence.getByText("尚未核验现场事实。", { exact: true })).toBeVisible();
const closed = await sampleToggle(evidence.getByRole("button").first(), ".answer-ui-evidence [data-answer-reveal]");
expect(closed[0].inert).toBe(true);
expect(closed[0].ariaHidden).toBe("true");
expect(closed.at(-1)!.hidden).toBe(true);
expect(closed.some((frame) => frame.opacity > 0 && frame.opacity < 1)).toBe(!reduce);
// Keyboard actions and restoration never replay the pointer transition.
await evidence.getByRole("button").first().focus();
await page.keyboard.press("Enter");
await expect(evidence.locator("[data-answer-reveal]")).toHaveCSS("opacity", "1");
const editor = ui.getByLabel("现场记录(必填)");
await editor.fill("保留同一个编辑器的现场草稿");
await editor.evaluate((element) => element.setAttribute("data-original-editor", "yes"));
await ui.getByRole("button", { name: "收起补充资料", exact: true }).click();
await expect(editor).toBeHidden();
await ui.getByRole("button", { name: "展开补充资料", exact: true }).click();
await expect(editor).toHaveValue("保留同一个编辑器的现场草稿");
await expect(editor).toHaveAttribute("data-original-editor", "yes");
await expect(ui.getByText("需要你补充", { exact: true })).toHaveCount(0);
const location = ui.locator(".answer-ui-location");
await location.getByRole("button", { name: "查看关联要素", exact: true }).click();
await expect(location.getByText("DEMO-ENTRY", { exact: true })).toBeVisible();
await ui.getByRole("checkbox", { name: "选择第 1 行", exact: true }).check();
await ui.getByRole("button", { name: "将所选行加入分析", exact: true }).click();
const tray = ui.getByRole("region", { name: "分析内容篮" });
await expect(tray).toContainText("已选 1 项分析内容");
await tray.getByRole("button", { name: "演练记录", exact: true }).click();
await expect(tray.getByText(/对象:入口一;压力(MPa):0.2130/)).toBeVisible();
await tray.getByRole("textbox").fill("核查演练资料");
await ui.getByRole("button", { name: "将演练入口压力加入分析", exact: true }).click();
await tray.getByRole("button", { name: "移除演练入口压力", exact: true }).click();
await expect(tray.getByRole("textbox")).toHaveValue("核查演练资料");
await expect(editor).toHaveValue("保留同一个编辑器的现场草稿");
await ui.getByRole("textbox", { name: "搜索演练记录" }).fill("不存在的入口");
await expect(ui.getByRole("cell", { name: "当前筛选没有匹配的数据。" })).toBeVisible();
await expect(ui.getByText("勾选数据行可加入分析或继续提问。", { exact: true })).toHaveCount(0);
await ui.getByRole("textbox", { name: "搜索演练记录" }).fill("");
await ui.getByRole("button", { name: "按压力(MPa)排序" }).click();
await expect(ui.getByRole("columnheader", { name: "压力(MPa)" })).toHaveAttribute("aria-sort", "ascending");
expect(await workspace.evaluate((element) => element.scrollWidth > element.clientWidth)).toBe(false);
await ui.getByRole("button", { name: "收起补充资料", exact: true }).click();
await ui.getByRole("button", { name: "展开补充资料", exact: true }).click();
// A reverse action while the exit is running must not hide a reopened editor.
await expect(editor).toBeVisible();
await expect(editor).toHaveValue("保留同一个编辑器的现场草稿");
await page.evaluate(() => document.fonts.ready);
await ui.locator(".answer-ui-form").screenshot({ path: testInfo.outputPath(`form-${width}-${reduce}.png`) });
await evidence.screenshot({ path: testInfo.outputPath(`evidence-${width}-${reduce}.png`) });
await tray.screenshot({ path: testInfo.outputPath(`basket-${width}-${reduce}.png`) });
});
}