81 lines
5.8 KiB
TypeScript
81 lines
5.8 KiB
TypeScript
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([])
|
||
})
|
||
}
|