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([]) })