Files
next-tjwater-frontend/tests/browser/scada-monitoring-notice.e2e.ts
T

92 lines
6.3 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { readRenderedContrast } from "./support/theme-colors";
for (const width of [1280, 375, 320]) {
test(`SCADA 调查通知切换状态时保持高度与下方布局 ${width}`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
await page.route("**/scada-notice-preview", (route) => route.fulfill({
contentType: "text/html",
body: '<!doctype html><html lang="zh-CN"><head><link rel="stylesheet" href="/src/styles.css"></head><body><main id="notice-reader" class="workbench-shadcn-surface bg-background text-foreground mx-auto w-[min(680px,calc(100%-1.5rem))] border p-3 sm:p-4"></main></body></html>'
}));
await page.goto("/scada-notice-preview");
await page.evaluate(async () => {
const refresh = await import("/@react-refresh");
refresh.injectIntoGlobalHook(window);
Object.assign(window, { $RefreshReg$: () => {}, $RefreshSig$: () => (type: unknown) => type });
const entry = await (await fetch("/src/main.tsx")).text();
const reactUrl = entry.match(/from "([^"\n]*\/react\.js[^"\n]*)"/)?.[1];
const clientUrl = entry.match(/from "([^"\n]*\/react-dom_client\.js[^"\n]*)"/)?.[1];
if (!reactUrl || !clientUrl) throw new Error("Vite React module paths unavailable");
const reactModule = await import(reactUrl);
const React = reactModule.default ?? reactModule;
const clientModule = await import(clientUrl);
const { createRoot } = clientModule.default ?? clientModule;
const { ScadaMonitoringNotice } = await import("/src/features/workbench/peripheral-sample/scada-monitoring-notice.tsx");
const { WorkbenchSection } = await import("/src/features/workbench/components/workbench-panel-ui.tsx");
const result = {
monitoring_id: "monitoring-1", period_end: "2026-07-16T00:00:00Z", status: "attention", task_id: "task-1",
incident: { action: "raised" }
};
const states = [
{ result: null, processing: false },
{ result: null, processing: true },
{ result: { ...result, status: "normal", task_id: null, incident: null }, processing: false },
{ result, processing: false, animationId: "incident-1" },
{ result: { ...result, status: "suspected_burst" }, processing: true, animationId: "incident-1" },
{ result: { ...result, incident: { action: "ongoing" } }, processing: false, animationId: "incident-1" },
{ result: { ...result, status: "normal", incident: { action: "resolved" } }, processing: false },
{ result: { ...result, status: "insufficient_data", task_id: null, incident: null }, processing: false },
{ result: null, processing: false }
];
const root = createRoot(document.getElementById("notice-reader"));
const render = (index: number) => root.render(React.createElement(WorkbenchSection, {
title: "SCADA 数据诊断", bodyClassName: "space-y-3"
}, React.createElement(ScadaMonitoringNotice, {
animationId: null, ...states[index],
onOpenInvestigation: (taskId: string) => { document.body.dataset.openedTask = taskId; }
}), React.createElement("div", { "data-testid": "following-controls" }, "演示开始时间 / 调频时间轴")));
Object.assign(window, { setNoticeState: render });
render(0);
});
const notice = page.getByRole("status", { name: "最新监测结果" });
await expect(notice).toBeVisible();
await page.evaluate(() => document.fonts.ready);
const initial = (await page.getByTestId("following-controls").boundingBox())!;
const readSlots = () => notice.locator("[data-slot^=scada-notice-]").evaluateAll((elements) => elements.map((element) => {
const { x, y, width, height } = element.getBoundingClientRect();
return { slot: element.getAttribute("data-slot"), x, y, width, height };
}));
const initialSlots = await readSlots();
const messages = ["监测待机", "正在监测", "监测正常", "发现指标异常", "发现疑似爆管", "异常持续跟踪", "异常已恢复", "监测数据不足", "监测待机"];
const tones = ["neutral", "info", "success", "warning", "danger", "warning", "success", "neutral", "neutral"];
for (let index = 0; index < messages.length; index++) {
await page.evaluate((state) => (window as unknown as { setNoticeState: (index: number) => void }).setNoticeState(state), index);
await expect(notice).toContainText(messages[index]);
await expect(notice).toHaveAttribute("data-status-tone", tones[index]);
expect((await notice.boundingBox())!.height).toBe(160);
expect((await page.getByTestId("following-controls").boundingBox())!.y).toBe(initial.y);
expect(await readSlots()).toEqual(initialSlots);
expect(await notice.evaluate((element) => element.scrollWidth <= element.clientWidth && element.scrollHeight <= element.clientHeight)).toBe(true);
expect(await readRenderedContrast(notice.locator("[data-slot=scada-notice-summary] p").last())).toBeGreaterThanOrEqual(4.5);
if (index < 3 || index === 5) {
await expect(notice.getByRole("button", { name: "查看调查" })).toHaveCount(index === 5 ? 1 : 0);
await testInfo.attach(`notice-${width}-${index}`, { body: await notice.screenshot({ path: testInfo.outputPath(`notice-${index}.png`) }), contentType: "image/png" });
}
if (index === 3) {
await expect(notice).toHaveAttribute("data-animation-state", "running");
await notice.getByRole("button", { name: "查看调查" }).click();
await expect(page.locator("body")).toHaveAttribute("data-opened-task", "task-1");
await testInfo.attach(`notice-${width}`, { body: await notice.screenshot({ path: testInfo.outputPath("notice.png") }), contentType: "image/png" });
await expect(notice).toHaveAttribute("data-animation-state", "idle");
expect((await notice.boundingBox())!.height).toBe(160);
}
}
await page.emulateMedia({ reducedMotion: "reduce" });
await page.evaluate(() => (window as unknown as { setNoticeState: (index: number) => void }).setNoticeState(3));
await expect(notice).toContainText("发现指标异常");
await expect(notice).toHaveAttribute("data-animation-state", "idle");
expect((await notice.boundingBox())!.height).toBe(160);
expect((await page.getByTestId("following-controls").boundingBox())!.y).toBe(initial.y);
});
}