import { expect, test, type Locator, type Page } from "@playwright/test"; import { basename, resolve } from "node:path"; import { investigationFixture, mockInvestigationPresentation, selectInvestigation, type SavedPresentationEvent } from "./support/mock-investigation-presentation"; const pressureIds = ["170486", "175618", "159673", "135160", "174754", "123781", "142493"]; const flowIds = ["541061", "542017", "486033"]; const times = Array.from({ length: 13 }, (_, index) => `${String(5 + Math.floor(index / 4)).padStart(2, "0")}:${String(index % 4 * 15).padStart(2, "0")}`); const pressureTitle = "压力当前曲线与前七天同期对比(m)"; const flowTitle = "流量当前曲线与前七天同期对比(m³/h)"; // These synthetic fixtures preserve the naming shape found in read-only persisted // SCADA task charts. Values, timestamps and references are self-contained test data. function comparison(id: string, flow = false, dateOnlyHistory = false) { const metric = flow ? "流量" : "压力"; const unit = flow ? "m³/h" : "m"; const offset = (flow ? flowIds : pressureIds).indexOf(id); const base = (flow ? 100 : 32) + offset; return { action: "open_chart", chart_id: `legacy-${flow ? "flow" : "pressure"}-${id}`, evidence_refs: [`synthetic:legacy-shape:${id}`], chart: { title: dateOnlyHistory ? `测点 ${id} ${metric}三小时与历史对照(${unit})` : `${metric}测点 ${id} 当前曲线与前七天同期对比(${unit})`, chart_type: dateOnlyHistory ? "line" : "time_comparison", x_axis_name: "时间", y_axis_name: `${metric}(${unit})`, x_data: times, series: [{ name: `${id} 当日`, type: "line", data: times.map((_, index) => base - index / 4), sample_times: times.map((time) => `2026-07-16T${time}:00+08:00`) }, ...Array.from({ length: 7 }, (_, day) => ({ name: `${dateOnlyHistory ? "" : `${id} `}07-${String(9 + day).padStart(2, "0")}`, type: "scatter", data: times.map((_, index) => index < 10 ? null : base + day / 2), sample_times: times.map((time, index) => index < 10 ? null : `2026-07-${String(9 + day).padStart(2, "0")}T${time}:00+08:00`) }))] } }; } const trend = (flow: boolean) => ({ action: "open_chart", chart_id: `trend-${flow ? "flow" : "pressure"}`, evidence_refs: ["synthetic:legacy-trend"], chart: { title: `${flow ? "流量" : "压力"}测点当前三小时总览`, chart_type: "line", x_data: times, series: [{ name: "合成当前趋势", data: times.map((_, index) => (flow ? 100 : 32) - index / 4) }] } }); function savedTask(index: number, title: string, commands: unknown[]) { const task = investigationFixture(index, title); const run = task.active_run!; task.status = "waiting_human"; task.lifecycle_status = "waiting_human"; task.latest_run = { ...run, status: "succeeded", finished_at: task.updated_at }; task.active_run = null; const events: SavedPresentationEvent[] = commands.map((command, sequence) => ({ task_id: task.task_id, run_id: run.run_id, event_seq: sequence + 1, execution_generation: 1, occurred_at: task.created_at, command })); return { task, events }; } const managed = (manager: Locator, title: string) => manager.locator(`[data-managed-chart][data-chart-title="${title}"]`); const plot = (article: Locator) => article.locator('[data-chart-view] div[role="img"]:not(.echarts-for-react)'); async function locate(page: Page, manager: Locator, title: string) { await manager.getByRole("combobox", { name: "快速定位图表", exact: true }).click(); await page.getByRole("option").filter({ hasText: title }).click(); await expect(managed(manager, title)).toBeVisible(); } async function expectBottomLegend(article: Locator) { const drawing = (await plot(article).boundingBox())!; const legend = (await article.getByLabel("图表图例", { exact: true }).boundingBox())!; const bounds = (await article.boundingBox())!; expect(legend.y).toBeGreaterThanOrEqual(drawing.y + drawing.height - 1); expect(legend.y + legend.height).toBeLessThanOrEqual(bounds.y + bounds.height + 1); expect(drawing.height).toBeGreaterThanOrEqual(100); } test.use({ reducedMotion: "reduce", hasTouch: true, screenshot: "only-on-failure" }); test.setTimeout(90_000); test.beforeEach(async ({ page }) => { await page.route("**/@fs/**/noto-sans-sc-*.woff2", (route) => route.fulfill({ path: resolve("node_modules/@fontsource-variable/noto-sans-sc/files", basename(new URL(route.request().url()).pathname)), contentType: "font/woff2" })); }); test("saved waiting-human SCADA charts restore silently into two trends and two object groups", async ({ page }, testInfo) => { const { task, events } = savedTask(21, "合成历史 SCADA 旧标题恢复", [trend(false), trend(true), ...pressureIds.map((id) => comparison(id)), ...flowIds.map((id) => comparison(id, true))]); const mock = await mockInvestigationPresentation(page, [task], events); await selectInvestigation(page, task.title); const panel = page.getByLabel("调查实时展示", { exact: true }); await expect(panel.locator("[data-investigation-chart]")).toHaveCount(12); await expect(page.locator("[data-floating-window]")).toHaveCount(0); await panel.getByRole("button", { name: "打开图表管理", exact: true }).click(); const manager = page.getByRole("dialog", { name: "图表管理", exact: true }); await expect(manager.locator("[data-managed-chart]")).toHaveCount(4); await manager.getByRole("button", { name: "关闭窗口", exact: true }).click(); await page.reload({ waitUntil: "domcontentloaded" }); await selectInvestigation(page, task.title); await expect(panel.locator("[data-investigation-chart]")).toHaveCount(12); await expect(page.locator("[data-floating-window]")).toHaveCount(0); await panel.getByRole("button", { name: "打开图表管理", exact: true }).click(); await expect(manager.locator("[data-managed-chart]")).toHaveCount(4); await expect(manager).toContainText("4 张图表"); const pressure = managed(manager, pressureTitle); const flow = managed(manager, flowTitle); await expect(pressure).toBeVisible(); await expect(flow).toBeVisible(); await expect(managed(manager, "压力测点当前三小时总览")).toBeVisible(); await expect(managed(manager, "流量测点当前三小时总览")).toBeVisible(); const pressureObjects = pressure.locator('aside[aria-label="选择图表对象"]'); const flowObjects = flow.locator('aside[aria-label="选择图表对象"]'); await expect(pressureObjects.getByRole("button")).toHaveCount(7); await expect(flowObjects.getByRole("button")).toHaveCount(3); await pressureObjects.getByRole("button", { name: "切换对象 175618", exact: true }).click(); await expect(plot(pressure)).toHaveAttribute("aria-label", "压力测点 175618 当前曲线与前七天同期对比(m)"); const legend = pressure.getByLabel("图表图例", { exact: true }); await expect(legend.getByRole("button")).toHaveCount(8); await expect(legend.getByRole("button", { name: "175618 当日", exact: true })).toBeVisible(); await expectBottomLegend(pressure); await flowObjects.getByRole("button", { name: "切换对象 486033", exact: true }).click(); await expect(plot(flow)).toHaveAttribute("aria-label", "流量测点 486033 当前曲线与前七天同期对比(m³/h)"); await expectBottomLegend(flow); await pressure.getByRole("tab", { name: "数据", exact: true }).click(); const table = pressure.getByRole("table"); await expect(table.getByRole("row")).toHaveCount(14); await expect(table.getByRole("row").last()).toContainText("2026-07-16 08:00:00+08:00"); await expect(table.getByRole("row").last()).toContainText("2026-07-09 08:00:00+08:00"); await expect(table.getByRole("row").last()).toContainText("30 (2026-07-16"); await pressure.getByRole("tab", { name: "复合", exact: true }).click(); await pressure.getByRole("button", { name: `放大查看 ${pressureTitle}`, exact: true }).click(); const child = page.getByRole("dialog", { name: pressureTitle, exact: true }); await expect(child).toBeVisible(); await expect(manager).toBeVisible(); await expect(page.locator("[data-floating-window]")).toHaveCount(2); await child.getByRole("button", { name: "切换对象 142493", exact: true }).click(); await expect(child.getByRole("img", { name: "压力测点 142493 当前曲线与前七天同期对比(m)", exact: true })).toBeVisible(); await child.getByRole("button", { name: "关闭窗口", exact: true }).click(); await expect(page.locator("[data-floating-window]")).toHaveCount(1); await page.evaluate(() => document.fonts.ready); await testInfo.attach("legacy-scada-groups-desktop", { body: await manager.screenshot({ path: testInfo.outputPath("legacy-scada-groups-desktop.png") }), contentType: "image/png" }); for (const width of [375, 320]) { await page.setViewportSize({ width, height: 812 }); await expect(manager.locator("[data-chart-layout]")).toHaveAttribute("data-chart-layout", "one"); await locate(page, manager, pressureTitle); const id = width === 375 ? "175618" : "159673"; await pressure.getByRole("combobox", { name: "选择图表对象", exact: true }).tap(); await page.getByRole("option", { name: id, exact: true }).tap(); await expect(plot(pressure)).toHaveAttribute("aria-label", `压力测点 ${id} 当前曲线与前七天同期对比(m)`); await expectBottomLegend(pressure); expect(await manager.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width); await testInfo.attach(`legacy-scada-groups-${width}`, { body: await manager.screenshot({ path: testInfo.outputPath(`legacy-scada-groups-${width}.png`) }), contentType: "image/png" }); await locate(page, manager, flowTitle); await flow.getByRole("combobox", { name: "选择图表对象", exact: true }).tap(); await page.getByRole("option", { name: "541061", exact: true }).tap(); await expect(plot(flow)).toHaveAttribute("aria-label", "流量测点 541061 当前曲线与前七天同期对比(m³/h)"); expect(await manager.evaluate((node) => node.scrollWidth - node.clientWidth)).toBeLessThanOrEqual(1); } expect(mock.mutations).toEqual([]); }); test("saved generic point titles group date-only historical scatter labels without changing sampling times", async ({ page }, testInfo) => { const { task, events } = savedTask(22, "合成早期测点图恢复", [comparison("170486", false, true), comparison("175618", false, true)]); const mock = await mockInvestigationPresentation(page, [task], events); await selectInvestigation(page, task.title); const panel = page.getByLabel("调查实时展示", { exact: true }); await expect(panel.locator("[data-investigation-chart]")).toHaveCount(2); await expect(page.locator("[data-floating-window]")).toHaveCount(0); await panel.getByRole("button", { name: "打开图表管理", exact: true }).click(); const manager = page.getByRole("dialog", { name: "图表管理", exact: true }); await expect(manager.locator("[data-managed-chart]")).toHaveCount(1); const group = managed(manager, "压力三小时与历史对照(m)"); await expect(group.locator('aside[aria-label="选择图表对象"]').getByRole("button")).toHaveCount(2); await group.getByRole("button", { name: "切换对象 175618", exact: true }).click(); await expect(plot(group)).toHaveAttribute("aria-label", "测点 175618 压力三小时与历史对照(m)"); const legend = group.getByLabel("图表图例", { exact: true }); await expect(legend.getByRole("button")).toHaveCount(8); await expect(legend.getByRole("button", { name: "07-09", exact: true })).toBeVisible(); await expectBottomLegend(group); await group.getByRole("tab", { name: "数据", exact: true }).click(); const row = group.getByRole("table").getByRole("row").filter({ hasText: /^07:30/ }); await expect(row).toContainText("2026-07-16 07:30:00+08:00"); await expect(row).toContainText("2026-07-09 07:30:00+08:00"); await expect(row).toContainText("33 (2026-07-09"); await page.evaluate(() => document.fonts.ready); await testInfo.attach("legacy-date-only-history", { body: await manager.screenshot({ path: testInfo.outputPath("legacy-date-only-history.png") }), contentType: "image/png" }); expect(mock.mutations).toEqual([]); });