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

120 lines
7.7 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { investigationFixture, mockInvestigationPresentation } from "./support/mock-investigation-presentation";
for (const width of [1280, 375, 320]) {
test(`SCADA 新告警进入通知中心并可从弹窗打开调查 ${width}`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 900 });
const task = investigationFixture(1, "SCADA 通知关联调查");
task.workflow = "scada_anomaly";
task.source = { kind: "external_system", external_id: "scada:test", external_revision: 1, event_kind: "scada_anomaly",
replay_context: { replay_id: "97000000-0000-4000-8000-000000000001", knowledge_cutoff: task.created_at, batch_sequence: 1 } };
await mockInvestigationPresentation(page, [task]);
const alert = {
id: "scada-incident-1", condition_id: `operational-task:${task.task_id}`,
source: { kind: "scada_replay", replay_id: "replay-1", rule_id: "scada_diagnosis", batch_sequence: 1 },
title: "SCADA 指标异常", description: "已启动调查,详细诊断见调查报告。",
occurred_at: "2026-07-16T00:00:00Z", severity: "warning"
};
let alerts: typeof alert[] = [];
let cursor = 10;
await page.route("https://tjwater-api.invalid/api/v1/workbench/sync-snapshot", (route) => route.fulfill({ json: {
schema: "workbench-sync-snapshot@1", event_cursor: cursor, generated_at: task.created_at,
runtime: { availability: "not_configured", source: "none", scenario_availability: "not_configured",
artifact_availability: "not_configured", schedule_availability: "not_configured", work_order_availability: "not_configured",
alert_availability: "ready", generated_at: task.created_at, conditions: [], alerts, scenarios: [], latest_analysis: null, work_orders: [] },
tasks: [Object.fromEntries(Object.entries(task).filter(([key]) =>
!["latest_plan", "latest_report", "latest_progress", "evidence", "actions", "access_requests", "work_order_snapshot", "permissions", "questions"].includes(key)))],
notifications: []
} }));
const emit = async (topic = "workbench.alert.changed") => {
await page.evaluate((event) => {
(window as unknown as { emitInvestigationTestEvent: (event: unknown) => void }).emitInvestigationTestEvent(event);
}, { schema: "workbench-event@1", event_seq: ++cursor,
event_id: `96000000-0000-4000-8000-${String(cursor).padStart(12, "0")}`, project_id: task.project_id,
topic, aggregate_type: topic === "workbench.alert.changed" ? "task_alert" : "operational_task",
aggregate_id: topic === "workbench.alert.changed" ? alert.id : task.task_id,
aggregate_revision: cursor - 10, occurred_at: task.created_at, metadata: {} });
};
const initialSnapshot = page.waitForResponse("https://tjwater-api.invalid/api/v1/workbench/sync-snapshot");
await page.goto("/", { waitUntil: "domcontentloaded" });
expect((await (await initialSnapshot).json()).runtime.alerts).toEqual([]);
await page.waitForFunction(() => Boolean(window.__waterNetworkMap?.getLayer("scada-pressure-hit")));
const menu = page.getByRole("button", { name: /查看待办与告警/ });
await expect(menu).toHaveAccessibleName("查看待办与告警,当前 0 条待处理事项");
// Open and close the panel first: announcements belong to the workbench, not its tab.
await page.getByRole("button", { name: "外围系统", exact: true }).click();
await page.getByRole("button", { name: "关闭外围系统" }).click();
alerts = [alert];
await emit();
const atmosphere = page.getByTestId("scada-alert-atmosphere");
await expect(atmosphere).toBeVisible();
await expect(atmosphere).toHaveAttribute("data-preview", "false");
await expect(atmosphere).toContainText("爆管风险调查中");
const popup = page.locator("[data-sonner-toast]").filter({ hasText: alert.title });
await expect(popup).toBeVisible();
await expect(popup).toHaveAttribute("data-y-position", "top");
await expect(popup).toHaveAttribute("data-x-position", "center");
await expect(menu).toHaveAccessibleName("查看待办与告警,当前 1 条待处理事项");
await menu.click();
await expect(page.getByRole("menuitem", { name: `打开通知:${alert.title}` })).toBeVisible();
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "外围系统", exact: true }).click();
const action = popup.getByRole("button", { name: "查看调查" });
const summary = popup.locator("[data-slot=map-notice-summary]");
const actionRow = popup.locator("[data-slot=map-notice-actions]");
await expect(actionRow).toBeVisible();
await expect(action).toHaveCSS("height", "36px");
const layout = await popup.evaluate((element) => {
const summary = element.querySelector("[data-slot=map-notice-summary]")!.getBoundingClientRect();
const row = element.querySelector("[data-slot=map-notice-actions]")!.getBoundingClientRect();
const action = element.querySelector("[data-slot=map-notice-actions] button")!.getBoundingClientRect();
const close = element.querySelector('button[data-cancel="true"]')!.getBoundingClientRect();
return { gap: row.top - summary.bottom, rightInset: row.right - action.right,
closeOverlaps: close.bottom > action.top && close.left < action.right,
overflows: element.scrollWidth > element.clientWidth };
});
expect(layout).toEqual({ gap: 0, rightInset: 12, closeOverlaps: false, overflows: false });
await expect(summary).toContainText(alert.description);
await expect.poll(() => action.evaluate((element) => {
const box = element.getBoundingClientRect();
return element.contains(document.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2));
})).toBe(true);
await page.evaluate(() => document.fonts.ready);
await expect(popup).toHaveCSS("opacity", "1");
expect(await popup.evaluate((element) => getComputedStyle(element).fontFamily)).toBe(
await page.evaluate(() => getComputedStyle(document.body).fontFamily)
);
await testInfo.attach(`scada-notification-${width}`, {
body: await page.screenshot({ path: testInfo.outputPath("notification.png") }), contentType: "image/png"
});
await action.click();
await expect(page.getByRole("complementary", { name: "外围系统" })).toHaveCount(0);
await expect(page.getByRole("region", { name: "工况任务", exact: true })).toBeVisible();
await expect(page.getByRole("region", { name: "工况任务", exact: true }).getByRole("heading", { name: task.title })).toBeVisible();
await expect(popup).toBeVisible();
await menu.click();
const alertRow = page.locator(`[data-alert-id="${alert.id}"]`);
const titleBox = await alertRow.locator("p").first().boundingBox();
const rowBox = await alertRow.boundingBox();
await alertRow.click({ position: { x: titleBox!.x - rowBox!.x + titleBox!.width / 2,
y: titleBox!.y - rowBox!.y + titleBox!.height / 2 } });
await expect(page.getByRole("menu")).toHaveCount(0);
await expect(page.getByRole("region", { name: "工况任务", exact: true }).getByRole("heading", { name: task.title })).toBeVisible();
await emit();
await expect(popup).toBeVisible();
alerts = [];
await emit();
await expect(menu).toHaveAccessibleName("查看待办与告警,当前 0 条待处理事项");
await expect(atmosphere).toBeVisible();
task.lifecycle_status = "completed";
task.status = "completed";
task.active_run = null;
task.revision++;
await emit("operational.task.changed");
await expect(atmosphere).toHaveCount(0);
await expect(popup).toBeVisible();
await popup.getByRole("button", { name: "关闭通知" }).click();
await expect(popup).toHaveCount(0);
});
}