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") })