import { expect, test } from "@playwright/test"; import { mockInvestigationPresentation } from "./support/mock-investigation-presentation"; for (const [width, reduced] of [[1440, false], [375, false], [320, false], [1440, true]] as const) { test(`SCADA 全屏边缘告警通过按钮预览 ${width} ${reduced ? "静态" : "呼吸"}`, async ({ page }, testInfo) => { await page.setViewportSize({ width: 1440, height: 900 }); await page.emulateMedia({ reducedMotion: reduced ? "reduce" : "no-preference" }); const mock = await mockInvestigationPresentation(page, []); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.clock.install(); const atmosphere = page.getByTestId("scada-alert-atmosphere"); await expect(atmosphere).toHaveCount(0); await page.getByRole("button", { name: "切换地图 Dev Panel", exact: true }).click(); const panel = page.getByTestId("map-dev-panel"); const preview = panel.getByRole("button", { name: "测试爆管告警效果", exact: true }); await preview.click(); await expect(atmosphere).toHaveAttribute("data-preview", "true"); await expect(atmosphere).toContainText("爆管告警效果预览"); const firstId = await atmosphere.getAttribute("data-alert-id"); await preview.click(); await expect(atmosphere).toHaveCount(1); await expect(atmosphere).not.toHaveAttribute("data-alert-id", firstId!); await expect(atmosphere).toHaveAttribute("data-intensity", "standard"); if (width === 1440 && !reduced) { await page.emulateMedia({ reducedMotion: "reduce" }); for (const [value, label] of [["soft", "柔和"], ["standard", "标准"], ["strong", "醒目"]] as const) { await panel.getByRole("group", { name: "告警灯效强度" }).getByRole("button", { name: label, exact: true }).click(); await expect(atmosphere).toHaveAttribute("data-intensity", value); await testInfo.attach(`intensity-${value}`, { body: await page.screenshot({ path: testInfo.outputPath(`intensity-${value}.png`) }), contentType: "image/png" }); } await panel.getByRole("group", { name: "告警灯效强度" }).getByRole("button", { name: "标准", exact: true }).click(); await page.emulateMedia({ reducedMotion: "no-preference" }); } // The ambient overlay leaves ordinary workbench buttons and the map hit target usable. await panel.getByRole("button", { name: "关闭 Dev Panel" }).click(); await expect(panel).toHaveCount(0); await expect(atmosphere).toBeVisible(); await page.setViewportSize({ width, height: width < 640 ? 640 : 900 }); expect(await atmosphere.boundingBox()).toEqual({ x: 0, y: 0, width, height: width < 640 ? 640 : 900 }); await expect(atmosphere).toHaveCSS("pointer-events", "none"); await expect(atmosphere).toHaveCSS("background-color", "rgba(0, 0, 0, 0)"); await expect(atmosphere.locator(".scada-alert-atmosphere-frame")).toHaveCSS("border-top-width", "1px"); const status = atmosphere.getByRole("status"); const material = await status.evaluate((element) => { const style = getComputedStyle(element); return { background: style.backgroundColor, backdrop: style.backdropFilter, shadow: style.boxShadow }; }); expect(material.background).toMatch(/(?:,|\/)\s*0\.\d+\)$/); expect(material.backdrop).toMatch(/blur\((?!0px)[\d.]+px\)/); expect(material.shadow).not.toBe("none"); const notice = (await status.boundingBox())!; const closeButton = atmosphere.getByRole("button", { name: "关闭告警灯效" }); await expect(closeButton).toHaveText("关闭灯效"); await expect(closeButton).toHaveCSS("border-top-width", "1px"); const close = (await closeButton.boundingBox())!; expect(close.height).toBeGreaterThanOrEqual(36); expect(close.y - notice.y).toBeLessThanOrEqual(14); expect(notice.x + notice.width - close.x - close.width).toBeLessThanOrEqual(14); expect(await closeButton.evaluate((element) => { const box = element.getBoundingClientRect(); return element.contains(document.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2)); })).toBe(true); const light = atmosphere.locator(".scada-alert-atmosphere-light"); await expect(light).toHaveCSS("animation-name", reduced ? "none" : "scada-alert-breathe"); const samples = await light.evaluate(async (element) => { const values: number[] = []; for (let frame = 0; frame < 30; frame++) { await new Promise((resolve) => requestAnimationFrame(() => resolve())); values.push(Number(getComputedStyle(element).opacity)); } return values; }); if (reduced) expect(new Set(samples).size).toBe(1); else expect(Math.max(...samples) - Math.min(...samples)).toBeGreaterThan(0.01); expect(await page.evaluate(() => Boolean(document.elementFromPoint(window.innerWidth / 2, window.innerHeight / 2)?.closest("[data-testid=scada-alert-atmosphere]")))).toBe(false); expect(mock.mutations).toEqual([]); await page.evaluate(() => document.fonts.ready); await testInfo.attach(`scada-atmosphere-${width}-${reduced}`, { body: await page.screenshot({ path: testInfo.outputPath("scada-atmosphere.png") }), contentType: "image/png" }); await page.clock.fastForward(60_000); await expect(atmosphere).toBeVisible(); await page.setViewportSize({ width: 1440, height: 900 }); await page.getByRole("button", { name: "切换地图 Dev Panel", exact: true }).click(); await panel.getByRole("button", { name: "模拟事件处理结束", exact: true }).click(); await expect(atmosphere).toHaveCount(0); await preview.click(); await atmosphere.getByRole("button", { name: "关闭告警灯效" }).click(); await expect(atmosphere).toHaveCount(0); await expect(panel.getByRole("button", { name: "模拟事件处理结束", exact: true })).toBeDisabled(); await panel.getByRole("button", { name: "关闭 Dev Panel" }).click(); await page.getByRole("button", { name: "外围系统", exact: true }).click(); const peripheral = page.getByRole("complementary", { name: "外围系统" }); await peripheral.getByRole("tab", { name: "SCADA 系统", exact: true }).click(); await expect(peripheral.getByRole("button", { name: "测试爆管告警效果", exact: true })).toHaveCount(0); expect(mock.mutations).toEqual([]); }); }