Files
MikrotikManager/tests/e2e/large-wireguard-peers.spec.ts
T

82 lines
8.9 KiB
TypeScript

import { test, expect } from "@playwright/test"
import { wgListResponseSchema } from "../../packages/contracts/src/wireguard"
import axe from "axe-core"
test("WireGuard1000 peers: full search/sort/page/cache/target/320px", async ({ page }, info) => {
test.setTimeout(90000)
let peers = Array.from({ length: 1000 }, (_, i) => ({ id: "p" + (i + 1), rosId: "*P" + (i + 1), publicKey: "key-" + String(i + 1).padStart(4, "0"), name: "Пир " + String(i + 1).padStart(4, "0"), allowedIps: ["10." + Math.floor(i/256) + "." + (i%256) + ".1/32"], transferRx: 0 }))
const iface = { id: "*1", rosId: "*1", name: "wg-main", serverId: "1", serverName: "Роутер", serverCountry: "RU", listenPort: 13231, mtu: 1420, comment: "", enabled: true, status: "up" }
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: "Каталог WireGuard недоступен" } } : { json: wgListResponseSchema.parse({ interfaces: [{ ...iface, peers }] }) }) }
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?server=1&interface=*1&keep=1")
await page.getByRole("tab", { name: /Пиры/ }).click()
const rows = page.locator("tr[data-row-id]"); await expect(rows).toHaveCount(50)
const profile = { count: peers.length, jsonBytes: Buffer.byteLength(JSON.stringify(peers)), rows: await rows.count(), nodes: await page.locator("*").count(), readyMs: Date.now()-started }
console.log("WG peers 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("Пир 0951")
const before=calls; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect.poll(()=>calls).toBeGreaterThan(before); await expect(rows.first()).toContainText("Пир 0951")
const search=page.getByPlaceholder("Поиск по ключу, IP, endpoint…")
await search.fill("key-1000"); await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText("Пир 1000")
await expect(page).toHaveURL(url=>url.searchParams.get("server")==="1"&&url.searchParams.get("interface")==="*1"&&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("Пир 0001")
await page.getByRole("table").getByRole("button", { name: "Пир", exact: true }).click()
await expect(rows.first()).toContainText("Пир 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.getByRole("status", { name: "WireGuard по ссылке", exact:true })).toContainText("сохранённые данные могут быть устаревшими")
await expect(rows).toHaveCount(50); await expect(rows.first()).toHaveAttribute("data-row-id",previous!)
await expect(rows.first().getByLabel("RX",{exact:true})).toHaveText("0 Б"); await expect(rows.first().getByLabel("TX",{exact:true})).toHaveText("—")
await page.setViewportSize({width:320,height:1000})
const scroll=page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]')
await rows.first().scrollIntoViewIfNeeded(); 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)
await expect(rows.first().getByRole("button",{name:"Экспорт peer .conf",exact:true})).toBeInViewport()
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.screenshot({path:info.outputPath("wireguard-peers-320.png"),animations:"disabled"})
failed=false;peers=[peers[999]];await page.getByRole("button",{name:"Обновить",exact:true}).click()
await expect(rows).toHaveCount(1);await expect(rows.first()).toContainText("Пир 1000")
await rows.first().getByRole("button",{name:"Удалить пира",exact:true}).click()
await expect(page.getByRole("alertdialog")).toContainText("Пир 1000");await page.getByRole("alertdialog").getByRole("button",{name:"Отмена",exact:true}).click()
expect(writes).toEqual([])
})
test("WireGuard1000 peer details: bounded DOM/page/refresh/clamp/keyboard/320px", async ({ page }, info) => {
test.setTimeout(90000)
let peers = Array.from({length:1000},(_,i)=>({id:"p"+(i+1),rosId:"*P"+(i+1),publicKey:"key-"+(i+1),name:"Пир "+(i+1),allowedIps:["10."+Math.floor(i/256)+"."+(i%256)+".1/32"],transferRx:0}))
let calls=0
await page.route("**/health",r=>r.fulfill({json:{ok:true}}))
await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;if(p==="/api/wireguard"){calls++;return r.fulfill({json:wgListResponseSchema.parse({interfaces:[{id:"*1",rosId:"*1",name:"wg-main",serverId:"1",serverName:"Роутер",listenPort:13231,mtu:1420,comment:"",enabled:true,status:"up",peers}]})})}return r.fulfill({json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[]:{}})})
await page.goto("/wireguard")
await page.getByRole("button",{name:"Пиры: wg-main",exact:true}).click()
const region=page.getByRole("region",{name:"Пиры: wg-main",exact:true}),items=region.locator("[data-wireguard-peer]")
await expect(items).toHaveCount(50)
const profile={peers:peers.length,rendered:await items.count(),nodes:await page.locator("*").count()};console.log("WG detail profile",JSON.stringify(profile));await info.attach("profile",{body:JSON.stringify(profile),contentType:"application/json"})
const next=region.getByRole("button",{name:"Следующая страница пиров",exact:true})
await next.focus();await page.keyboard.press("Enter");await expect(items.first()).toHaveAttribute("data-wireguard-peer","p51")
for(let i=1;i<19;i++)await next.click()
await expect(next).toBeDisabled();await expect(items.first()).toHaveAttribute("data-wireguard-peer","p951")
const before=calls;await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect.poll(()=>calls).toBeGreaterThan(before);await expect(items.first()).toHaveAttribute("data-wireguard-peer","p951")
await page.setViewportSize({width:320,height:1000});await items.first().scrollIntoViewIfNeeded()
const scroll=page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]');await scroll.evaluate(el=>{el.scrollLeft=el.scrollWidth})
expect(await region.evaluate(el=>{const r=el.getBoundingClientRect();return r.left>=0&&r.right<=innerWidth})).toBe(true)
await expect(items.first().getByRole("button",{name:"Удалить пира",exact:true})).toBeInViewport()
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('section[aria-label="Пиры: wg-main"]')!,{runOnly:{type:"tag",values:["wcag2a","wcag2aa","wcag21a","wcag21aa"]}}))).violations).toEqual([])
await page.addStyleTag({content:"nextjs-portal{display:none!important}"});await page.screenshot({path:info.outputPath("wireguard-detail-320.png"),animations:"disabled"})
await items.last().getByRole("button",{name:"Удалить пира",exact:true}).click();await expect(page.getByRole("alertdialog")).toContainText("Пир 1000");await page.getByRole("alertdialog").getByRole("button",{name:"Отмена",exact:true}).click()
peers=[peers[999]];await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(items).toHaveCount(1);await expect(items.first()).toHaveAttribute("data-wireguard-peer","p1000")
})