416 lines
27 KiB
TypeScript
416 lines
27 KiB
TypeScript
import { expect, test, type Page } 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] }] }
|
||
});
|
||
|
||
const managedPages = new WeakSet<Page>();
|
||
async function openChild(page: Page, title: string, live = true) {
|
||
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
|
||
if (live && !managedPages.has(page)) {
|
||
await expect(manager).toBeVisible();
|
||
managedPages.add(page);
|
||
}
|
||
if (await manager.isVisible()) await manager.getByRole("button", { name: "关闭窗口", exact: true }).click();
|
||
await page.getByLabel("调查实时展示", { exact: true }).getByRole("button", { name: `放大查看 ${title}`, exact: true }).click();
|
||
}
|
||
|
||
test.setTimeout(60_000);
|
||
// Keep unrelated avatar animation out of chart geometry and drag measurements.
|
||
test.use({ reducedMotion: "reduce" });
|
||
|
||
test("losing window focus cancels a drag and allows a fresh gesture", async ({ page }) => {
|
||
const task = investigationFixture(1, "图窗失焦恢复核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
await mock.emit(task, chart(1));
|
||
await openChild(page, "合成压力分析 1");
|
||
const popup = page.getByRole("dialog", { name: "合成压力分析 1", exact: true });
|
||
const before = (await popup.boundingBox())!;
|
||
await page.mouse.move(before.x + 180, before.y + 24);
|
||
await page.mouse.down();
|
||
await page.mouse.move(before.x + 100, before.y + 64, { steps: 4 });
|
||
await expect.poll(async () => Math.round((await popup.boundingBox())!.x - before.x)).toBe(-80);
|
||
await page.evaluate(() => window.dispatchEvent(new Event("blur")));
|
||
expect(await popup.boundingBox()).toEqual(before);
|
||
expect(await popup.evaluate((node) => (node as HTMLElement).style.willChange)).toBe("");
|
||
await page.mouse.up();
|
||
await page.mouse.move(before.x + 180, before.y + 24);
|
||
await page.mouse.down();
|
||
await page.mouse.move(before.x + 140, before.y + 44, { steps: 4 });
|
||
await page.mouse.up();
|
||
expect(Math.round((await popup.boundingBox())!.x - before.x)).toBe(-40);
|
||
expect(Math.round((await popup.boundingBox())!.y - before.y)).toBe(20);
|
||
});
|
||
|
||
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));
|
||
await openChild(page, "合成压力分析 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);
|
||
expect((await popup.boundingBox())!.y).toBe(64);
|
||
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.getByLabel("调查实时展示", { exact: true }).getByRole("button", { name: `放大查看 合成七日同期曲线 ${index}`, exact: true })).toBeVisible();
|
||
await openChild(page, `合成七日同期曲线 ${index}`);
|
||
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.getByLabel("调查实时展示", { exact: true }).getByRole("button", { name: `放大查看 合成压力分析 ${index}`, exact: true })).toBeVisible();
|
||
await openChild(page, `合成压力分析 ${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(Math.max(64, before.y - 32) - before.y);
|
||
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 updates one manager and respects dismissal", async ({ page }, testInfo) => {
|
||
const task = investigationFixture(1, "图表统一展示核查");
|
||
const mock = await mockInvestigationPresentation(page, [task]);
|
||
await selectInvestigation(page, task.title);
|
||
const panel = page.getByLabel("调查实时展示", { exact: true });
|
||
const collapse = panel.getByRole("button", { name: "收起展示", exact: true });
|
||
await collapse.focus();
|
||
await mock.emit(task, chart(1));
|
||
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
|
||
await expect(manager).toBeVisible();
|
||
await expect(collapse).toBeFocused();
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(1);
|
||
await manager.locator(".floating-window-heading").focus();
|
||
await page.keyboard.press("ArrowRight");
|
||
const moved = await manager.boundingBox();
|
||
await mock.emit(task, chart(2));
|
||
await expect(manager.getByRole("img", { name: "合成压力分析 2", exact: true })).toBeVisible();
|
||
expect(await manager.boundingBox()).toEqual(moved);
|
||
await page.keyboard.press("Escape");
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await mock.emit(task, chart(3));
|
||
await expect(panel.getByRole("img", { name: "合成压力分析 3", exact: true })).toBeVisible();
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await panel.getByRole("button", { name: "打开图表管理", exact: true }).click();
|
||
await expect(manager.getByRole("img", { name: "合成压力分析 3", exact: true })).toBeVisible();
|
||
await testInfo.attach("live-chart-manager", { body: await manager.screenshot(), contentType: "image/png" });
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("playback restores charts without opening windows 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");
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await openChild(page, "合成压力分析 1", false);
|
||
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.locator("[data-floating-window]")).toHaveCount(0);
|
||
await expect(panel.getByRole("img", { 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(panel.getByRole("img", { name: "合成压力分析 3", exact: true })).toBeVisible();
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
expect(mock.mutations).toEqual([]);
|
||
});
|
||
|
||
test("structured analysis charts retain manual enlargement without opening during 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 preview = page.getByLabel("本地预览展示", { exact: true });
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
await preview.getByRole("button", { name: "放大查看 合成压力分析 1", 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 });
|
||
await preview.getByRole("button", { name: "播放历史", exact: true }).click();
|
||
await expect(page.locator("[data-floating-window]")).toHaveCount(0);
|
||
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));
|
||
await openChild(page, "合成压力分析 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] }]
|
||
} });
|
||
await openChild(page, "SCADA 回放压力:异常前后(2026-07-16)");
|
||
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) }))]
|
||
} });
|
||
await expect(page.getByLabel("调查实时展示", { exact: true }).getByRole("button", { name: `放大查看 回放${metric}:${device}(${unit},07-16 04:45–07:45)`, exact: true })).toBeVisible();
|
||
await openChild(page, `回放${metric}:${device}(${unit},07-16 04:45–07:45)`);
|
||
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();
|
||
}
|
||
});
|