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); }); }