Files
MikrotikManager/tests/e2e/optimizer-matrix-geometry.spec.ts
T

81 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from "@playwright/test"
import axe from "axe-core"
import { writeFileSync } from "node:fs"
for (const width of [320, 390, 1440]) for (const count of [1, 7]) {
test(`Матрица: измерение целиком рядом с pinned WAN/${width}/${count}`, async ({ page }, info) => {
await page.setViewportSize({ width, height: 1000 })
const longName = "WAN-" + "площадка".repeat(15)
const common = { country: "RU", enabled: true, status: "online", latency: 10, site: "TEST" }
const servers = [
{ ...common, id: 1, name: "Home", host: "192.0.2.1", type: "home-router", wanUplinks: [{ id: "wan", name: longName, iface: "ether1", isp: "ISP", ip: "198.51.100.1", maxDl: 100, maxUl: 100 }] },
...Array.from({ length: count }, (_, i) => ({ ...common, id: i + 2, name: `Jump ${i + 1}`, host: `192.0.2.${i + 2}`, type: "jump-host" })),
]
const probes = servers.slice(1).map(s => ({ id: `probe-${s.id}`, srcServerId: "1", dstServerId: String(s.id), srcInterface: "ether1", dstInterface: "", enabled: true, lastPingRttMs: 5, lastPingLossPct: 0, lastTxAvgMbps: 30, lastRxAvgMbps: 80, lastPingAt: "2026-10-07T00:00:00Z" }))
const writes: string[] = []
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
await page.route("**/api/**", r => {
const path = new URL(r.request().url()).pathname
if (r.request().method() !== "GET" && !path.endsWith("/preview")) writes.push(path)
return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? servers : path === "/api/filters/rules" ? { rulesets: [] } : path === "/api/uptime/speed-probes" ? { probes } : path === "/api/ospf/catalog" ? { interfaces: [], instances: [], neighbors: [], bfdSessions: [], routes: [], failures: [] } : path.endsWith("/preview") ? { serverId: 1, serverName: "Home", pingWeight: 60, interfacesTotal: 0, changedCount: 0, unchangedCount: 0, interfaces: [], changes: [] } : {} })
})
await page.goto("/route-optimizer")
const region = page.getByRole("region", { name: "Матрица WAN и Jump Host" })
const row = region.locator("tr[data-row-id]")
await expect(row).toHaveCount(1)
const viewport = region.locator('[data-slot="scroll-area-viewport"]')
const measurements: unknown[] = []
async function inspectColumns() {
const cells = row.locator("td").filter({ hasText: "↓80 ↑30 Мбит/с" })
for (let i = 0; i < await cells.count(); i++) {
const cell = cells.nth(i)
await cell.evaluate(el => {
const viewport = el.closest('[data-slot="scroll-area-viewport"]') as HTMLElement
const pinned = el.parentElement!.querySelector('td[data-pinned="start"]')!
viewport.scrollLeft += el.getBoundingClientRect().left - viewport.getBoundingClientRect().left - pinned.getBoundingClientRect().width
})
const geometry = await cell.evaluate(el => {
const viewport = el.closest('[data-slot="scroll-area-viewport"]')!, pinned = el.parentElement!.querySelector('td[data-pinned="start"]')!
const c = el.getBoundingClientRect(), v = viewport.getBoundingClientRect(), p = pinned.getBoundingClientRect()
return { cellLeft: c.left, cellRight: c.right, pinnedRight: p.right, viewportRight: v.right, pinnedWidth: p.width, cellWidth: c.width }
})
measurements.push({ width: page.viewportSize()!.width, count, ...geometry })
console.log("Matrix geometry", JSON.stringify({ width: page.viewportSize()!.width, count, ...geometry }))
expect(geometry.cellLeft).toBeGreaterThanOrEqual(geometry.pinnedRight - 1)
expect(geometry.cellRight).toBeLessThanOrEqual(geometry.viewportRight + 1)
const text = await cell.getByText("↓80 ↑30 Мбит/с", { exact: true }).boundingBox()
expect(text!.x).toBeGreaterThanOrEqual(geometry.pinnedRight - 1)
expect(text!.x + text!.width).toBeLessThanOrEqual(geometry.viewportRight + 1)
}
}
await inspectColumns()
if (count > 5) {
await region.getByRole("button", { name: "Следующие Jump Host" }).click()
await expect(region).toContainText("Столбцы 6–7 из 7")
await inspectColumns()
}
await expect(row.getByText(longName, { exact: true })).toHaveAttribute("title", longName)
const bounds = await viewport.boundingBox()
expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(width)
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width)
if (width === 320 && count === 1) {
await page.setViewportSize({ width: 1440, height: 1000 })
await expect.poll(() => row.locator('td[data-pinned="start"]').evaluate(el => el.getBoundingClientRect().width)).toBeGreaterThanOrEqual(180)
await inspectColumns()
await page.setViewportSize({ width: 320, height: 1000 })
await expect.poll(() => row.locator('td[data-pinned="start"]').evaluate(el => el.getBoundingClientRect().width)).toBe(112)
await inspectColumns()
await region.scrollIntoViewIfNeeded()
await page.addStyleTag({ content: "nextjs-portal{display:none!important}" })
await page.screenshot({ path: info.outputPath("matrix-readable-320.png"), animations: "disabled" })
writeFileSync(info.outputPath("geometry.json"), JSON.stringify({ width, count, measurements, writes }, null, 2))
}
if (width === 320 && count === 1) {
await page.addScriptTag({ content: axe.source })
const result = await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[aria-label="Матрица WAN и Jump Host"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))
expect(result.violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([])
}
expect(writes).toEqual([])
})
}