Files
MikrotikManager/tests/e2e/large-ospf-routes.spec.ts
T

55 lines
5.5 KiB
TypeScript

import { test, expect } from "@playwright/test"
import { ospfCatalogSchema } from "../../lib/ospf-response"
import axe from "axe-core"
test("OSPF routes1000: full search/sort, page20, refresh/cache/shrink and320px", async ({ page }, info) => {
test.setTimeout(90000)
const server = { id: 1, name: "Роутер 1", host: "192.0.2.1", country: "RU", enabled: true, status: null }
let routes = Array.from({ length: 1000 }, (_, i) => ({ id: "*" + (i + 1), serverId: 1, serverName: "Роутер 1", serverSite: "", serverCountry: "RU", destination: "10." + Math.floor(i / 256) + "." + (i % 256) + ".0/24", type: "O" as const, cost: i + 1, nextHop: "192.0.2.2", via: "ether1", area: "backbone" }))
let failed = false, calls = 0; 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") writes.push(path)
if (path === "/api/ospf/catalog") { calls++; return r.fulfill(failed ? { status: 503, json: { error: "OSPF catalog unavailable" } } : { json: ospfCatalogSchema.parse({ neighbors: [], interfaces: [], instances: [], bfdSessions: [], routes, failures: [] }) }) }
return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [server] : {} })
})
const started = Date.now(); await page.goto("/ospf?tab=routes&server=1&keep=1")
const rows = page.locator("tr[data-row-id]")
await expect(rows).toHaveCount(50)
const profile = { count: routes.length, jsonBytes: Buffer.byteLength(JSON.stringify(routes)), renderedRows: await rows.count(), nodes: await page.locator("*").count(), readyMs: Date.now() - started }
console.log("OSPF routes profile", JSON.stringify(profile)); await info.attach("profile", { body: JSON.stringify(profile), contentType: "application/json" })
const next = page.getByRole("button", { name: "Следующая страница", exact: true })
for (let i = 0; i < 19; i++) await next.click()
await expect(next).toBeDisabled(); await expect(rows.first()).toHaveAttribute("data-row-id", "1-*951")
const before = calls; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect.poll(() => calls).toBeGreaterThan(before); await expect(rows.first()).toHaveAttribute("data-row-id", "1-*951")
const search = page.getByPlaceholder("Поиск в текущем разделе OSPF…")
await search.fill(routes[999].destination); await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText(routes[999].destination)
await expect(page).toHaveURL(url => url.searchParams.get("q") === routes[999].destination && url.searchParams.get("keep") === "1")
await page.reload(); await expect(rows).toHaveCount(1)
await search.fill(""); await expect(rows).toHaveCount(50)
const costSort = page.getByRole("table").getByRole("button", { name: "Cost", exact: true }); await costSort.click()
await expect(rows.first()).toContainText(routes[999].destination)
await next.click(); const previousRow = await rows.first().getAttribute("data-row-id")
failed = true; await page.getByRole("button", { name: "Обновить", exact: true }).click()
const error = page.getByRole("alert").filter({ hasText: "Не удалось загрузить OSPF" })
await expect(error).toContainText("Показаны последние полученные данные"); await expect(rows).toHaveCount(50); await expect(rows.first()).toHaveAttribute("data-row-id", previousRow!)
await expect(rows.first()).toContainText("Неизвестно")
await page.setViewportSize({ width: 320, height: 1000 })
const scroll = page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]')
await expect(scroll).toHaveCount(1); expect(await scroll.evaluate(el => el.scrollWidth > el.clientWidth)).toBe(true)
await scroll.evaluate(el => { el.scrollLeft = el.scrollWidth }); await expect.poll(() => scroll.evaluate(el => el.scrollLeft)).toBeGreaterThan(0)
expect(await rows.first().getByRole("cell").first().evaluate(el => { const a=el.getBoundingClientRect(),b=el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect();return a.left>=b.left-1&&a.right<=b.right+1 })).toBe(true)
expect(await rows.first().getByText("backbone", { exact: true }).evaluate(el => { const a=el.getBoundingClientRect(),b=el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(),p=el.closest("tr")!.querySelector("td")!.getBoundingClientRect();return a.left>=p.right-1&&a.right<=b.right+1 })).toBe(true)
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
await page.addScriptTag({ content: axe.source })
expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[data-slot="data-grid"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations).toEqual([])
await page.addStyleTag({ content: "nextjs-portal { display: none !important; }" })
await page.getByRole("table").getByRole("button", { name: "Назначение", exact: true }).scrollIntoViewIfNeeded()
await page.screenshot({ path: info.outputPath("ospf-routes-320.png"), animations: "disabled" })
failed = false; routes = [routes[999]]; await error.getByRole("button", { name: "Повторить загрузку OSPF" }).click()
await expect(rows).toHaveCount(1); await expect(error).toHaveCount(0); await expect(rows.first()).toContainText(routes[0].destination)
expect(writes).toEqual([])
})