Files
next-tjwater-frontend/tests/browser/agent-chart-motion.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

87 lines
5.2 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 { 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";
// Synthetic readings exercise the same chart contract as the production answer.
const chart = {
version: "answer-ui@1", block_id: "chart-motion", fallback_text: "演练压力对比",
spec: { root: "chart", elements: { chart: { type: "Chart", children: [], props: {
title: "节点17038压力:事件日前后同时段对比(m)", chart_type: "line",
x_data: ["07:00", "07:15", "07:30", "07:45", "08:00", "08:15", "08:30", "08:45", "09:00"],
series: [{ name: "6月1日(m)", data: [57.8, 63, 64, 66, 67, 68, 71, 73.8, 73.93] },
{ name: "6月2日(m)", data: [58.91, 62, 63, 65, 65, 66, 69, 71, 71.34] }],
source: "浏览器演练数据,待现场核验",
} } } },
};
for (const width of [1440, 375]) for (const reduce of [false, true]) {
test(`chart switches with ${reduce ? "reduced" : "normal"} motion at ${width}px`, async ({ page }, testInfo) => {
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: "chart-answer" },
{ type: "data-answer_ui", id: chart.block_id, data: chart },
{ type: "data-final_answer", data: { content: "演练资料,待现场核验。", ui_block_ids: [chart.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("textarea").fill("查看演练压力对比");
await panel.getByRole("button", { name: "发送 Agent 指令" }).click();
const ui = panel.locator(".answer-ui-chart");
await expect(ui).toBeVisible();
await expect(ui.getByRole("button", { name: "查看数据点", exact: true })).toHaveCount(0);
await expect(ui.getByRole("region", { name: "数据点详情" })).toHaveCount(0);
const content = ui.locator("[data-chart-view]");
const samples = await ui.getByRole("tab", { name: "柱状", exact: true }).evaluate(async (tab) => {
const content = tab.closest(".answer-ui-chart")!.querySelector("[data-chart-view]")!;
const values: number[] = [];
// Dispatch a real pointer modality before click, sampling the actual render over frames.
tab.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true }));
tab.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, button: 0 }));
(tab as HTMLElement).click();
for (let i = 0; i < 16; i++) {
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));
values.push(Number(getComputedStyle(content).opacity));
}
return values;
});
await expect(content).toHaveAttribute("data-chart-view", "bar");
if (reduce) expect(samples.every((value) => value === 1)).toBe(true);
else expect(samples.some((value) => value > 0 && value < 1)).toBe(true);
await expect(content).toHaveCSS("opacity", "1");
await ui.getByRole("tab", { name: "数据", exact: true }).click();
await expect(ui.getByRole("group", { name: "当前区间摘要" })).toHaveCount(0);
await ui.getByRole("textbox", { name: /搜索节点17038/ }).fill("08:30");
const line = ui.getByRole("tab", { name: "折线", exact: true });
await line.focus();
await page.keyboard.press("Enter");
await expect(content).toHaveCSS("opacity", "1");
// Exercise the real SVG hit target as well as the keyboard alternative.
await ui.locator('svg path[stroke="#2563eb"]').last().click();
await expect(ui.getByRole("region", { name: "数据点详情" })).toContainText("73.93");
await ui.getByRole("button", { name: "关闭数据点详情" }).click();
const plot = ui.getByRole("group", { name: /按回车查看数据点/ });
await plot.focus();
await page.keyboard.press("End");
await expect(ui.getByRole("region", { name: "数据点详情" })).toContainText("73.93");
await ui.getByRole("button", { name: "关闭数据点详情" }).click();
await expect(plot).toBeFocused();
await ui.getByRole("tab", { name: "数据", exact: true }).click();
await expect(ui.getByRole("textbox", { name: /搜索节点17038/ })).toHaveValue("08:30");
await line.click();
await expect(content).toHaveCSS("opacity", "1");
expect(await ui.evaluate((el) => el.scrollWidth > el.clientWidth)).toBe(false);
await page.evaluate(() => document.fonts.ready);
await ui.screenshot({ path: testInfo.outputPath(`chart-${width}-${reduce}.png`) });
});
}