Files
MikrotikManager/tests/e2e/large-wireguard-interfaces.spec.ts

64 lines
5.8 KiB
TypeScript

import { test, expect } from "@playwright/test"
import { wgListResponseSchema } from "../../packages/contracts/src/wireguard"
import axe from "axe-core"
test("WireGuard1000 interfaces: page/refresh/expansion/search/sort/cache/actions/320px", async ({ page }, info) => {
test.setTimeout(90000)
let interfaces = Array.from({ length: 1000 }, (_, i) => ({
id: "*" + (i + 1), rosId: "*" + (i + 1), name: "wg-test-" + String(i + 1).padStart(4, "0"),
serverId: "1", serverName: "Тестовый роутер", serverCountry: "RU", listenPort: 13231 + i, mtu: 1420,
comment: "", enabled: true, status: "up", peers: [{ id: "p" + (i + 1), rosId: "*P" + (i + 1),
publicKey: "test-key-" + (i + 1), allowedIps: ["10." + Math.floor(i / 256) + "." + (i % 256) + ".1/32"], transferRx: 0 }],
}))
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/wireguard") { calls++; return r.fulfill(failed ? { status: 503, json: { error: "Unavailable" } } : { json: wgListResponseSchema.parse({ interfaces }) }) }
return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [{ id: 1, name: "Тестовый роутер", host: "192.0.2.1", country: "RU", enabled: true, status: null }] : {} })
})
const started = Date.now(); await page.goto("/wireguard?keep=1")
const rows = page.locator("tr[data-row-id]")
await expect(rows).toHaveCount(50)
const profile = { count: interfaces.length, jsonBytes: Buffer.byteLength(JSON.stringify(interfaces)), rows: await rows.count(), nodes: await page.locator("*").count(), readyMs: Date.now() - started }
console.log("WG interface 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()).toContainText("wg-test-0951")
const expand = rows.first().getByRole("button", { name: "Пиры: wg-test-0951", exact: true })
await expand.focus(); await page.keyboard.press("Enter"); await expect(expand).toHaveAttribute("aria-expanded", "true")
await expect(page.getByText(interfaces[950].peers[0].allowedIps[0], { exact: true })).toBeVisible()
const before = calls; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect.poll(() => calls).toBeGreaterThan(before); await expect(rows.first()).toContainText("wg-test-0951"); await expect(expand).toHaveAttribute("aria-expanded", "true")
await expand.click()
const search = page.getByPlaceholder("Поиск по имени, серверу, IP…")
await search.fill("wg-test-1000"); await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText("wg-test-1000")
await expect(page).toHaveURL(url => url.searchParams.get("keep") === "1")
await search.fill(""); await expect(rows).toHaveCount(50)
await page.getByRole("table").getByRole("button", { name: "Порт", exact: true }).click()
await expect(rows.first()).toContainText("wg-test-1000")
await next.click(); const previous = await rows.first().getAttribute("data-row-id")
failed = true; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(page.getByText(/Показаны последние полученные данные/)).toBeVisible()
await expect(rows).toHaveCount(50); await expect(rows.first()).toHaveAttribute("data-row-id", previous!)
await page.setViewportSize({ width: 320, height: 1000 })
const scroll = page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]')
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(),v=el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(); return a.left>=v.left-1&&a.right<=v.right+1})).toBe(true)
const action = rows.first().getByRole("button", { name: /Действия:/ })
await action.scrollIntoViewIfNeeded(); await scroll.evaluate(el => { el.scrollLeft = el.scrollWidth }); await expect(action).toBeInViewport(); await action.click(); await expect(page.getByRole("menuitem", { name: "Экспорт", exact: true })).toBeVisible(); await page.keyboard.press("Escape")
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").scrollIntoViewIfNeeded()
await page.screenshot({ path: info.outputPath("wireguard-interfaces-320.png"), animations: "disabled" })
failed = false; interfaces = [interfaces[999]]; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText("wg-test-1000")
await action.click(); await page.getByRole("menuitem", { name: "Удалить", exact: true }).click()
await expect(page.getByRole("alertdialog")).toContainText("wg-test-1000"); await page.getByRole("alertdialog").getByRole("button", { name: "Отмена", exact: true }).click()
expect(writes).toEqual([])
})