The earlier placement fix exposed chart headings but retained react-rnd state updates on every mousemove, so dragging still stalled with several charts open. Move the active window through pointer capture and a frame-scheduled DOM translation, then commit its bounds once on release. Cover cancellation, resize and restore controls, and eight-window dragging. The regression records 40 React transform writes on the old code and one on the fix while other windows and chart SVGs stay unchanged.
378 lines
25 KiB
TypeScript
378 lines
25 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
||
import { investigationFixture, mockInvestigationPresentation, selectInvestigation } from "./support/mock-investigation-presentation";
|
||
|
||
const chart = (index: number) => ({
|
||
action: "open_chart", chart_id: "pressure", evidence_refs: ["synthetic:pressure"],
|
||
chart: { title: `合成压力分析 ${index}`, chart_type: "line", x_data: ["10:00", "10:15"],
|
||
series: [{ name: "压力(m)", data: [30, 30 - index] }] }
|
||
});
|
||
|
||
test.setTimeout(60_000);
|
||
// Keep unrelated avatar animation out of chart geometry and drag measurements.
|
||
test.use({ reducedMotion: "reduce" });
|
||
|
||
test("cancelled drags release capture and preserve restore, resizing and keyboard controls", async ({ page }) => {
|
||
const task = investigationFixture(1, "图窗拖动取消核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
await mock.emit(task, chart(1));
|
||
const popup = page.getByRole("dialog", { name: "合成压力分析 1", exact: true });
|
||
await expect(popup).toBeVisible();
|
||
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
|
||
const heading = popup.locator(".floating-window-heading");
|
||
await heading.click({ trial: true, position: { x: 200, y: 24 } });
|
||
const original = (await popup.boundingBox())!;
|
||
await heading.evaluate((node) => node.addEventListener("pointerdown", (event) => {
|
||
(node as HTMLElement).dataset.testPointerId = String((event as PointerEvent).pointerId);
|
||
}, { once: true }));
|
||
await page.mouse.move(original.x + 200, original.y + 24);
|
||
await page.mouse.down();
|
||
await page.mouse.move(original.x + 100, original.y + 64, { steps: 4 });
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.x)).toBe(original.x - 100);
|
||
await heading.evaluate((node) => node.dispatchEvent(new PointerEvent("pointercancel", {
|
||
pointerId: Number((node as HTMLElement).dataset.testPointerId)
|
||
})));
|
||
await page.mouse.up();
|
||
expect(await popup.boundingBox()).toEqual(original);
|
||
expect(await popup.evaluate((node) => (node as HTMLElement).style.willChange)).toBe("");
|
||
await popup.getByRole("button", { name: "最大化窗口", exact: true }).click();
|
||
expect((await popup.boundingBox())!.x).toBe(8);
|
||
await popup.getByRole("button", { name: "还原窗口", exact: true }).click();
|
||
expect(await popup.boundingBox()).toEqual(original);
|
||
await heading.focus();
|
||
await page.keyboard.press("ArrowRight");
|
||
expect((await popup.boundingBox())!.x).toBe(original.x + 16);
|
||
await page.mouse.move(original.x + 16 + original.width - 3, original.y + original.height - 3);
|
||
await page.mouse.down();
|
||
await page.mouse.move(original.x + 16 + original.width + 37, original.y + original.height + 27, { steps: 4 });
|
||
await page.mouse.up();
|
||
expect((await popup.boundingBox())!.width).toBe(original.width + 40);
|
||
expect((await popup.boundingBox())!.height).toBe(original.height + 30);
|
||
await heading.focus();
|
||
await page.keyboard.press("Escape");
|
||
await expect(popup).toHaveCount(0);
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("profiles a single drag with eight comparison chart windows", async ({ page }, testInfo) => {
|
||
await page.setViewportSize({ width: 1600, height: 1000 });
|
||
const task = investigationFixture(1, "多图拖动性能核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
const slots = Array.from({ length: 13 }, (_, index) => `${String(5 + Math.floor(index / 4)).padStart(2, "0")}:${String(index % 4 * 15).padStart(2, "0")}`);
|
||
for (let index = 1; index <= 8; index++) {
|
||
await mock.emit(task, { action: "open_chart", chart_id: `comparison-${index}`, evidence_refs: ["synthetic:pressure"], chart: {
|
||
title: `合成七日同期曲线 ${index}`, chart_type: "time_comparison", x_data: slots,
|
||
series: [{ name: "当前", type: "line", data: slots.map((_, point) => 30 - index + point / 10),
|
||
sample_times: slots.map((time) => `2026-07-16T${time}:00+08:00`) },
|
||
...Array.from({ length: 7 }, (_, day) => ({ name: `前${day + 1}天`, type: "scatter",
|
||
data: slots.map((_, point) => point < 10 ? null : 30 + day / 10),
|
||
sample_times: slots.map((time, point) => point < 10 ? null : `2026-07-${String(15 - day).padStart(2, "0")}T${time}:00+08:00`) }))]
|
||
} });
|
||
await expect(page.getByRole("dialog", { name: `合成七日同期曲线 ${index}`, exact: true })).toBeVisible();
|
||
}
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(8);
|
||
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
|
||
await page.evaluate(() => document.fonts.ready);
|
||
const popup = page.getByRole("dialog", { name: "合成七日同期曲线 8", exact: true });
|
||
const otherWindows = page.locator('[data-floating-window]').filter({ hasNot: page.getByRole("heading", { name: "合成七日同期曲线 8", exact: true }) });
|
||
const otherPositions = await otherWindows.evaluateAll((nodes) => nodes.map((node) => {
|
||
const { x, y } = node.getBoundingClientRect();
|
||
return { x, y };
|
||
}));
|
||
const heading = (await popup.locator(".floating-window-heading").boundingBox())!;
|
||
const before = (await popup.boundingBox())!;
|
||
const cdp = await page.context().newCDPSession(page);
|
||
await cdp.send("Emulation.setCPUThrottlingRate", { rate: 4 });
|
||
await cdp.send("Performance.enable");
|
||
const first = await cdp.send("Performance.getMetrics");
|
||
const events: Array<{ name: string; dur?: number }> = [];
|
||
cdp.on("Tracing.dataCollected", ({ value }) => events.push(...value));
|
||
await cdp.send("Tracing.start", { categories: "devtools.timeline", transferMode: "ReportEvents" });
|
||
await page.evaluate(() => {
|
||
Object.assign(window, { dragSvgMutations: 0, dragTransformWrites: 0 });
|
||
const observer = new MutationObserver((records) => {
|
||
(window as unknown as { dragSvgMutations: number }).dragSvgMutations += records.length;
|
||
});
|
||
document.querySelectorAll("[data-floating-window] svg").forEach((node) => observer.observe(node, { subtree: true, attributes: true, childList: true }));
|
||
const windowObserver = new MutationObserver((records) => {
|
||
for (const record of records) {
|
||
const previous = document.createElement("div");
|
||
previous.setAttribute("style", record.oldValue ?? "");
|
||
if (previous.style.transform !== (record.target as HTMLElement).style.transform)
|
||
(window as unknown as { dragTransformWrites: number }).dragTransformWrites++;
|
||
}
|
||
});
|
||
document.querySelectorAll("[data-floating-window]").forEach((node) => windowObserver.observe(node, { attributes: true, attributeFilter: ["style"], attributeOldValue: true }));
|
||
});
|
||
const x = heading.x + heading.width - 110;
|
||
const y = heading.y + heading.height / 2;
|
||
await page.mouse.move(x, y);
|
||
await page.mouse.down();
|
||
await page.mouse.move(x - 100, y - 60, { steps: 20 });
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.x - before.x)).toBe(-100);
|
||
await page.mouse.move(x - 200, y - 120, { steps: 20 });
|
||
await page.mouse.up();
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.x - before.x)).toBe(-200);
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.y - before.y)).toBe(-120);
|
||
const last = await cdp.send("Performance.getMetrics");
|
||
const finished = new Promise<void>((resolve) => cdp.once("Tracing.tracingComplete", () => resolve()));
|
||
await cdp.send("Tracing.end");
|
||
await finished;
|
||
const metric = (name: string) => (last.metrics.find((item) => item.name === name)?.value ?? 0) - (first.metrics.find((item) => item.name === name)?.value ?? 0);
|
||
const summary = { metrics: Object.fromEntries(["ScriptDuration", "LayoutDuration", "RecalcStyleDuration", "TaskDuration", "LayoutCount", "RecalcStyleCount"].map((name) => [name, metric(name)])),
|
||
trace: Object.fromEntries(["Paint", "Layout", "UpdateLayoutTree", "FunctionCall"].map((name) => [name, { count: events.filter((event) => event.name === name).length,
|
||
ms: events.filter((event) => event.name === name).reduce((total, event) => total + (event.dur ?? 0), 0) / 1000 }])),
|
||
svgMutations: await page.evaluate(() => (window as unknown as { dragSvgMutations: number }).dragSvgMutations),
|
||
transformWrites: await page.evaluate(() => (window as unknown as { dragTransformWrites: number }).dragTransformWrites) };
|
||
await testInfo.attach("drag-profile", { body: JSON.stringify(summary, null, 2), contentType: "application/json" });
|
||
console.info("Window drag metrics", JSON.stringify(summary));
|
||
expect(summary.transformWrites).toBeLessThanOrEqual(2);
|
||
expect(summary.svgMutations).toBe(0);
|
||
expect(await otherWindows.evaluateAll((nodes) => nodes.map((node) => {
|
||
const { x, y } = node.getBoundingClientRect();
|
||
return { x, y };
|
||
}))).toEqual(otherPositions);
|
||
await testInfo.attach("multiple-charts-after-drag", { body: await page.screenshot({ path: testInfo.outputPath("multiple-charts-after-drag.png") }), contentType: "image/png" });
|
||
});
|
||
|
||
for (const height of [900, 720]) {
|
||
test(`multiple chart headings stay accessible and drag on first press at ${height}px height`, async ({ page }, testInfo) => {
|
||
await page.setViewportSize({ width: 1440, height });
|
||
const task = investigationFixture(1, "多图窗口拖动核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
for (let index = 1; index <= 4; index++) {
|
||
await mock.emit(task, { ...chart(index), chart_id: `pressure-${index}` });
|
||
await expect(page.getByRole("dialog", { name: `合成压力分析 ${index}`, exact: true })).toBeVisible();
|
||
}
|
||
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
|
||
if (height === 900) await page.setViewportSize({ width: 1440, height: 720 });
|
||
for (let index = 1; index <= 4; index++) {
|
||
const popup = page.getByRole("dialog", { name: `合成压力分析 ${index}`, exact: true });
|
||
const heading = popup.locator(".floating-window-heading");
|
||
const box = (await heading.boundingBox())!;
|
||
expect((await popup.boundingBox())!.y).toBe(64 + (index - 1) * (box.height + 8));
|
||
await expect.poll(async () => {
|
||
const current = (await heading.boundingBox())!;
|
||
return page.evaluate(({ x, y }) => document.elementFromPoint(x, y)?.closest("[data-floating-window]")?.getAttribute("aria-labelledby"),
|
||
{ x: current.x + current.width / 2, y: current.y + current.height / 2 });
|
||
}).toBe(await popup.getAttribute("aria-labelledby"));
|
||
}
|
||
await testInfo.attach(`multiple-charts-before-drag-${height}`, {
|
||
body: await page.screenshot({ path: testInfo.outputPath(`multiple-charts-before-drag-${height}.png`) }), contentType: "image/png"
|
||
});
|
||
for (const index of [1, 4, 2]) {
|
||
const popup = page.getByRole("dialog", { name: `合成压力分析 ${index}`, exact: true });
|
||
const before = (await popup.boundingBox())!;
|
||
const heading = (await popup.locator(".floating-window-heading").boundingBox())!;
|
||
const start = { x: heading.x + heading.width - 110, y: heading.y + heading.height / 2 };
|
||
await page.mouse.move(start.x, start.y);
|
||
await page.mouse.down();
|
||
await page.mouse.move(start.x - 120, start.y - 32, { steps: 12 });
|
||
await page.mouse.up();
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.x - before.x)).toBe(-120);
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.y - before.y)).toBe(-32);
|
||
expect((await popup.boundingBox())!.width).toBe(before.width);
|
||
expect((await popup.boundingBox())!.height).toBe(before.height);
|
||
}
|
||
await testInfo.attach(`multiple-charts-after-drag-${height}`, {
|
||
body: await page.screenshot({ path: testInfo.outputPath(`multiple-charts-after-drag-${height}.png`) }), contentType: "image/png"
|
||
});
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
}
|
||
|
||
test("live analysis opens a window, preserves its position and respects closing and task switches", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "图表自动展示核查");
|
||
const other = investigationFixture(2, "另一项调查");
|
||
const mock = await mockInvestigationPresentation(page, [task, other]);
|
||
await selectInvestigation(page, task.title);
|
||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
|
||
const collapse = panel.getByRole("button", { name: "收起展示", exact: true });
|
||
await collapse.focus();
|
||
await mock.emit(task, chart(1));
|
||
let popup = page.getByRole("dialog", { name: "合成压力分析 1", exact: true });
|
||
await expect(popup).toBeVisible();
|
||
await expect(popup.getByRole("img", { name: "合成压力分析 1", exact: true })).toBeVisible();
|
||
await expect(collapse).toBeFocused();
|
||
const heading = popup.getByLabel("窗口标题栏,可用方向键移动,Shift 加方向键调整大小");
|
||
await heading.focus();
|
||
await page.keyboard.press("ArrowRight");
|
||
const moved = (await popup.boundingBox())!;
|
||
await mock.emit(task, chart(2));
|
||
popup = page.getByRole("dialog", { name: "合成压力分析 2", exact: true });
|
||
await expect(popup).toBeVisible();
|
||
expect((await popup.boundingBox())!.x).toBe(moved.x);
|
||
await page.keyboard.press("Escape");
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await mock.emit(task, { action: "zoom_to_map", center: [121.48, 31.1], zoom: 13 });
|
||
await expect(panel.getByRole("combobox", { name: "切换展示动作" })).toContainText("3 个动作");
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await panel.getByRole("button", { name: "放大查看 合成压力分析 2", exact: true }).click();
|
||
await expect(popup).toBeVisible();
|
||
await testInfo.attach("live-chart-window", { body: await page.screenshot({ path: testInfo.outputPath("live-chart-window.png") }), contentType: "image/png" });
|
||
await popup.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||
await mock.emit(task, chart(3));
|
||
await expect(page.getByRole("dialog", { name: "合成压力分析 3", exact: true })).toBeVisible();
|
||
await mock.emit(task, { action: "close_chart", chart_id: "pressure" });
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await mock.emit(task, chart(4));
|
||
await expect(page.getByRole("dialog", { name: "合成压力分析 4", exact: true })).toBeVisible();
|
||
await page.getByRole("region", { name: "工况任务", exact: true }).getByText(other.title, { exact: true }).first().click();
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("playback windows follow saved snapshots while live results remain separate", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "图表回放核查");
|
||
const commands = [chart(1), { action: "zoom_to_map", center: [121.48, 31.1], zoom: 13 }, chart(2), { action: "close_chart", chart_id: "pressure" }];
|
||
const saved = commands.map((command, index) => ({ task_id: task.task_id, event_seq: index + 1,
|
||
run_id: task.active_run!.run_id, execution_generation: 1, occurred_at: task.created_at, command }));
|
||
const mock = await mockInvestigationPresentation(page, [task], saved);
|
||
await selectInvestigation(page, task.title);
|
||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||
await expect(panel.getByRole("button", { name: "播放历史", exact: true })).toBeVisible();
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await panel.getByRole("button", { name: "播放历史", exact: true }).click();
|
||
await panel.getByRole("button", { name: "暂停历史播放", exact: true }).press("Enter");
|
||
const popup = page.getByRole("dialog", { name: "合成压力分析 1", exact: true });
|
||
await expect(popup).toBeVisible();
|
||
await mock.emit(task, chart(3));
|
||
await expect(popup).toBeVisible();
|
||
await expect(page.getByRole("dialog", { name: "合成压力分析 3", exact: true })).toHaveCount(0);
|
||
await popup.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||
const progress = panel.getByRole("slider", { name: "历史播放进度" });
|
||
await progress.focus();
|
||
await page.keyboard.press("ArrowRight");
|
||
await expect(panel.getByRole("combobox", { name: "切换展示动作" })).toContainText("2.");
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await page.keyboard.press("ArrowRight");
|
||
await expect(page.getByRole("dialog", { name: "合成压力分析 2", exact: true })).toBeVisible();
|
||
await expect(progress).toBeFocused();
|
||
await testInfo.attach("playback-chart-window", { body: await page.screenshot({ path: testInfo.outputPath("playback-chart-window.png") }), contentType: "image/png" });
|
||
await page.keyboard.press("ArrowRight");
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
|
||
await expect(page.getByRole("dialog", { name: "合成压力分析 3", exact: true })).toBeVisible();
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("structured analysis charts open responsive windows in local preview and history", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "结构化分析展示核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
|
||
const tools = page.getByTestId("investigation-dev-tools");
|
||
await tools.getByLabel("调查展示 JSONL", { exact: true }).fill(JSON.stringify({ record_type: "ui_block", block: {
|
||
version: "answer-ui@1", block_id: "chart-analysis", phase: "final", fallback_text: "合成压力数据", spec: {
|
||
root: "analysis", elements: { analysis: { type: "Chart", children: [],
|
||
props: { ...chart(1).chart, source: "仅供浏览器测试的合成数据" } } }
|
||
}
|
||
} }));
|
||
await tools.getByRole("button", { name: "执行本地预览", exact: true }).click();
|
||
const popup = page.getByRole("dialog", { name: "合成压力分析 1", exact: true });
|
||
await expect(popup).toBeVisible();
|
||
await expect(popup.getByRole("img", { name: "合成压力分析 1", exact: true })).toBeVisible();
|
||
for (const width of [375, 320]) {
|
||
await page.setViewportSize({ width, height: 720 });
|
||
const box = (await popup.boundingBox())!;
|
||
expect(box.x).toBeGreaterThanOrEqual(0);
|
||
expect(box.x + box.width).toBeLessThanOrEqual(width);
|
||
expect(await popup.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1);
|
||
await testInfo.attach(`structured-chart-${width}`, { body: await page.screenshot({ path: testInfo.outputPath(`structured-chart-${width}.png`) }), contentType: "image/png" });
|
||
}
|
||
await popup.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||
await page.setViewportSize({ width: 1440, height: 900 });
|
||
const preview = page.getByLabel("本地预览展示", { exact: true });
|
||
await preview.getByRole("button", { name: "播放历史", exact: true }).click();
|
||
await expect(popup).toBeVisible();
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("live charts fit desktop and narrow windows without outer scrollbars", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "实时图表紧凑布局核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
await mock.emit(task, chart(1));
|
||
const popup = page.getByRole("dialog", { name: "合成压力分析 1", exact: true });
|
||
const content = popup.locator("[data-floating-window-content]");
|
||
await expect(popup.getByRole("img", { name: "合成压力分析 1", exact: true })).toBeVisible();
|
||
for (const [width, height] of [[1440, 900], [1280, 720], [800, 600], [375, 540], [320, 540]]) {
|
||
await page.setViewportSize({ width, height });
|
||
await expect.poll(() => content.evaluate((node) => ({
|
||
x: node.scrollWidth - node.clientWidth,
|
||
y: node.scrollHeight - node.clientHeight
|
||
}))).toEqual({ x: 0, y: 0 });
|
||
const plot = popup.getByRole("img", { name: "合成压力分析 1", exact: true });
|
||
expect((await plot.boundingBox())!.height).toBeGreaterThanOrEqual(120);
|
||
await testInfo.attach(`live-chart-fit-${width}`, {
|
||
body: await popup.screenshot({ path: testInfo.outputPath(`live-chart-fit-${width}.png`) }), contentType: "image/png"
|
||
});
|
||
}
|
||
await page.setViewportSize({ width: 1280, height: 720 });
|
||
await popup.getByRole("button", { name: "筛选数据范围", exact: true }).click();
|
||
await expect(popup.getByRole("combobox", { name: "区间起点" })).toBeVisible();
|
||
await expect.poll(() => content.evaluate((node) => node.scrollHeight - node.clientHeight)).toBe(0);
|
||
await popup.getByRole("tab", { name: "数据", exact: true }).click();
|
||
await expect(popup.getByRole("table")).toBeVisible();
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("SCADA chart axes and legends fit every nested container", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "SCADA 压力布局核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
await mock.emit(task, { action: "open_chart", chart_id: "scada-pressure", evidence_refs: ["synthetic:pressure"], chart: {
|
||
title: "SCADA 回放压力:异常前后(2026-07-16,UTC+8)", chart_type: "line",
|
||
x_axis_name: "时间(UTC+8)", y_axis_name: "压力(m)", x_data: ["07:30", "07:45", "08:00"],
|
||
series: [{ name: "测点 123748(实测回放)", data: [26.2, 25.7, 23.5] },
|
||
{ name: "测点 159673(实测回放)", data: [24.9, 24.87, 21.6] }]
|
||
} });
|
||
const popup = page.getByRole("dialog", { name: "SCADA 回放压力:异常前后(2026-07-16)", exact: true });
|
||
await expect(popup).toBeVisible();
|
||
await expect.poll(() => popup.evaluate((node) => Array.from(node.querySelectorAll<HTMLElement>("*"))
|
||
.filter((element) => {
|
||
const style = getComputedStyle(element);
|
||
return (style.overflowX === "auto" || style.overflowX === "scroll" || style.overflowY === "auto" || style.overflowY === "scroll") &&
|
||
(element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight);
|
||
}).map((element) => ({ class: element.className, x: element.scrollWidth - element.clientWidth, y: element.scrollHeight - element.clientHeight }))
|
||
)).toEqual([]);
|
||
const plot = popup.getByRole("group", { name: /按回车查看数据点/ });
|
||
await expect.poll(() => plot.evaluate((element) => ({
|
||
x: element.scrollWidth - element.clientWidth, y: element.scrollHeight - element.clientHeight
|
||
}))).toEqual({ x: 0, y: 0 });
|
||
await testInfo.attach("scada-chart-layout", {
|
||
body: await popup.screenshot({ path: testInfo.outputPath("scada-chart-layout.png") }), contentType: "image/png"
|
||
});
|
||
});
|
||
|
||
|
||
test("SCADA pressure and flow keep full comparison data without timezone wording", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "SCADA 三小时对照核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
const labels = Array.from({ length: 13 }, (_, i) => `${String(4 + Math.floor((45 + i * 15) / 60)).padStart(2, "0")}:${String((45 + i * 15) % 60).padStart(2, "0")}`);
|
||
for (const [metric, device, unit] of [["压力", "123748", "m"], ["流量", "487434", "m³/h"]]) {
|
||
await mock.emit(task, { action: "open_chart", chart_id: `scada-${device}`, evidence_refs: ["synthetic:pressure"], chart: {
|
||
title: `回放${metric}:${device}(${unit},上海时间 07-16 04:45–07:45)`, chart_type: "line",
|
||
x_axis_name: "上海时间", y_axis_name: `${metric}(${unit})`, x_data: labels,
|
||
series: [{ name: "当前回放(2026-07-16)", type: "line", data: labels.map((_, i) => 30 + i) },
|
||
...Array.from({ length: 7 }, (_, day) => ({ name: `参考 07-${String(9 + day).padStart(2, "0")}`, type: "scatter",
|
||
data: labels.map((_, i) => i < 10 ? null : 28 + day) }))]
|
||
} });
|
||
const popup = page.getByRole("dialog", { name: `回放${metric}:${device}(${unit},07-16 04:45–07:45)`, exact: true });
|
||
await expect(popup).toBeVisible();
|
||
await expect(popup.getByText(/上海时间|UTC\+8|Asia\/Shanghai/)).toHaveCount(0);
|
||
await expect(popup.getByRole("img", { name: `回放${metric}:${device}(${unit},07-16 04:45–07:45)`, exact: true })).toBeVisible();
|
||
await testInfo.attach(`scada-${device}-comparison`, { body: await popup.screenshot(), contentType: "image/png" });
|
||
await popup.getByRole("tab", { name: "数据", exact: true }).click();
|
||
const table = popup.getByRole("table");
|
||
await expect(table).toBeVisible();
|
||
await expect(table.getByRole("row")).toHaveCount(14);
|
||
await expect(table.getByRole("columnheader")).toHaveCount(9);
|
||
await popup.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||
}
|
||
});
|