120 lines
7.7 KiB
TypeScript
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);
|
|
});
|
|
}
|