import { test, expect } from "@playwright/test" const emptyFlow = { hops: [], live: false, rangeMinutes: 5, windowSec: 300, totalBytes: 0, namedBytes: 0, unclassifiedBytes: 0, asnLoaded: false, countryLoaded: false, services: [], serviceEdges: [], servicePaths: [], countries: [], countryEdges: [], countryPaths: [], countryServiceGroups: [], mapServiceMinSharePct: 5, dedupApplied: false, excludeMeshApplied: false, excludeOverlayApplied: false } test.beforeEach(async ({ page }) => { await page.route("**/health", r => r.fulfill({ json: { ok: true } })) await page.route("**/api/**", r => { const path = new URL(r.request().url()).pathname return r.fulfill({ json: path === "/api/servers" ? [ { id: 1, name: "Тест JH", host: "192.0.2.1", type: "jump-host", country: "RU", enabled: true, status: "online" }, { id: 2, name: "Тест EN", host: "192.0.2.2", type: "exit-node", country: "SE", enabled: true, status: "online" }, ] : path === "/api/filters/gre-tunnels" ? { tunnels: [], failures: [] } : path === "/api/uptime/speed-probes" ? { probes: [] } : path === "/api/traffic/flow/map-hops" ? emptyFlow : path === "/api/auth/config" ? { required: false } : {} }) }) }) test("Карта: раскрытая легенда объясняет статусы, типы, линии и неизвестные измерения", async ({ page }) => { await page.goto("/network-map") const legend = page.getByRole("region", { name: "Легенда карты" }) await expect(legend).toBeVisible() for (const text of ["Онлайн", "Внимание", "Оффлайн", "Неизвестно", "JumpHost", "Exit Node", "Home Router", "GRE + IPsec", "Поток NetFlow"]) await expect(legend.getByText(text, { exact: true })).toBeVisible() await expect(legend).toContainText("нет измерения") await expect(legend).toContainText("не пропускная способность канала") await page.locator("[data-map-legend] summary").click() await expect(legend).not.toBeVisible() await page.locator("[data-map-legend] summary").press("Enter") await expect(legend).toBeVisible() await legend.getByText("Онлайн", { exact: true }).click() await page.screenshot({ path: "tmp/map270-desktop.png" }) }) test("Карта: мышь не рисует прямоугольную рамку, клавиатурный фокус остаётся видимым", async ({ page }) => { await page.goto("/network-map") const node = page.getByRole("button", { name: "Устройство Тест JH", exact: true }) await expect(node).toBeVisible() await node.click() await expect(node).toHaveCSS("outline-style", "none") await expect(node.locator(":scope > .network-map-focus-ring")).toHaveCSS("opacity", "0") // Return to this object through real keyboard navigation, not programmatic focus. await node.press("Shift+Tab") await page.keyboard.press("Tab") await expect(node).toBeFocused() await expect(node.locator(":scope > .network-map-focus-ring")).toHaveCSS("opacity", "1") await node.press("Enter") await expect(node).toHaveAttribute("aria-pressed", "false") }) test("Карта: легенда помещается на телефоне и сворачивается", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }) await page.goto("/network-map") const legend = page.getByRole("region", { name: "Легенда карты" }) await expect(page.locator("[data-map-legend]")).not.toHaveAttribute("open", "") await page.screenshot({ path: "tmp/map270-mobile.png" }) await page.locator("[data-map-legend] summary").click() await expect(legend).toBeVisible() const bounds = await legend.boundingBox() expect(bounds!.x).toBeGreaterThanOrEqual(0) expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390) await page.locator("[data-map-legend] summary").click() await expect(legend).not.toBeVisible() }) test("Карта: экспорт не добавляет клавиатурные контуры всем объектам", async ({ page }) => { await page.goto("/network-map") await expect(page.getByRole("button", { name: "Устройство Тест JH", exact: true })).toBeVisible() const download = page.waitForEvent("download") await page.getByRole("button", { name: "Экспорт SVG", exact: true }).click() const stream = await (await download).createReadStream() let svg = "" for await (const chunk of stream!) svg += chunk.toString() expect(svg).not.toContain("network-map-focus-ring") expect(svg).toContain("Тест JH") })