import { test, expect, type Page } from "@playwright/test" import axe from "axe-core" import { recursiveReadSchema } from "../../lib/recursive-route-response" const row = (i: number) => ({ id: "r" + i, dstAddress: "10." + Math.floor((i-1)/256) + "." + ((i-1)%256) + ".0/24", gateway: "192.0.2.1%gw-" + i, distance: 1, scope: 30, targetScope: 10, routingTable: "t" + String(i).padStart(4,"0"), checkGateway: "ping", comment: "route-" + String(i).padStart(4,"0"), disabled: false, country: "RU" }) async function setup(page: Page, control: { routes: ReturnType[]; failed: boolean; calls: number }) { 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/recursive-routes/catalog") { control.calls++; return r.fulfill(control.failed ? { status: 503, json: { error: "Чтение маршрутов недоступно тест" } } : { json: recursiveReadSchema.parse({ serverId: 1, routes: control.routes, live: true, stale: false }) }) } return r.fulfill({ json: p === "/api/auth/config" ? { required: false } : p === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", country: "RU", enabled: true, status: null }] : p === "/api/recursive-routes/gateways/catalog" ? { serverId: 1, gateways: [] } : {} }) }) } test("Recursive1000 groups: URL page/search/sort/refresh/cache/320px", async ({ page }, info) => { test.setTimeout(90000) const control = { routes: Array.from({length:1000},(_,i)=>row(i+1)), failed: false, calls: 0 } await setup(page, control); const last = control.routes[999] const started = Date.now(); await page.goto("/recursive-routes?server=1&route=" + encodeURIComponent(last.dstAddress) + "&table=t1000&keep=x") const rows = page.locator("tr[data-row-id]"); await expect(rows).toHaveCount(50) await expect(page.getByRole("button", { name: "Раскрыть маршрут " + last.dstAddress + " · t1000", exact: true })).toHaveAttribute("aria-expanded", "true") await expect(page.getByText("Endpoint 1", { exact: true })).toBeVisible() const profile = { count:1000, jsonBytes:Buffer.byteLength(JSON.stringify(control.routes)), rows:await rows.count(), nodes:await page.locator("*").count(), readyMs:Date.now()-started }; console.log("Recursive groups profile",JSON.stringify(profile)); await info.attach("profile",{body:JSON.stringify(profile),contentType:"application/json"}) const next = page.getByRole("button", { name:"Следующая страница",exact:true }); await expect(next).toBeDisabled() const refresh = page.getByRole("button", { name:"Обновить",exact:true }); const before=control.calls; await refresh.click(); await expect.poll(()=>control.calls).toBeGreaterThan(before); await expect(next).toBeDisabled() await page.getByRole("button",{name:"Раскрыть маршрут " + last.dstAddress + " · t1000",exact:true}).click() const search = page.getByRole("textbox",{name:"Поиск маршрутов",exact:true}); await search.fill("route-1000"); await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText(last.dstAddress) await search.fill(""); await expect(rows).toHaveCount(50); await page.getByRole("table").getByRole("button",{name:"Table",exact:true}).click(); await page.getByRole("table").getByRole("button",{name:"Table",exact:true}).click(); await expect(rows.first()).toContainText("t1000") await next.click(); const id=await rows.first().getAttribute("data-row-id"); control.failed=true; await refresh.click(); await expect(page.getByRole("alert").filter({hasText:"Чтение маршрутов недоступно тест"})).toBeVisible(); await expect(rows.first()).toHaveAttribute("data-row-id",id!) const edit=rows.first().getByRole("button",{name:/Редактировать маршрут/}); await expect(edit).toBeDisabled() await page.setViewportSize({width:320,height:1000}); await edit.scrollIntoViewIfNeeded(); const viewport=page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]'); await viewport.evaluate(el=>{el.scrollLeft=el.scrollWidth}); await expect(edit).toBeInViewport() await expect.poll(()=>page.evaluate(()=>document.documentElement.scrollWidth)).toBeLessThanOrEqual(320) 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("recursive-groups-320.png"),animations:"disabled"}) control.failed=false; control.routes=[last]; await refresh.click(); await expect(rows).toHaveCount(1); await expect(rows.first()).toContainText(last.dstAddress) await expect(page).toHaveURL(/keep=x/) }) test("Recursive1000 endpoints: bounded preview/detail/last page/refresh/clamp/320px", async ({page},info)=>{ test.setTimeout(90000) const control={routes:Array.from({length:1000},(_,i)=>({...row(i+1),dstAddress:"203.0.113.0/24",routingTable:"main",distance:Math.floor((999-i)/4)})),failed:false,calls:0} await setup(page,control); await page.goto("/recursive-routes?server=1&route=203.0.113.0%2F24&table=main") const region=page.getByRole("region",{name:"Конечные точки: 203.0.113.0/24 · main",exact:true}); const cards=region.locator('[data-recursive-endpoint]'); await expect(cards).toHaveCount(50) await expect(page.getByText("Ещё 997 · раскройте маршрут",{exact:true})).toBeVisible() await expect(cards.first()).toContainText("gw-997"); await expect(cards.first()).toContainText("distance: 0") const profile={count:1000,cards:await cards.count(),nodes:await page.locator("*").count()};console.log("Recursive endpoints profile",JSON.stringify(profile));await info.attach("profile",{body:JSON.stringify(profile),contentType:"application/json"}) const next=region.getByRole("button",{name:"Следующие конечные точки",exact:true});for(let i=0;i<19;i++)await next.click() await expect(next).toBeDisabled();await expect(cards.last()).toContainText("Endpoint 1000");await expect(cards.last()).toContainText("gw-4") await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(cards.last()).toContainText("Endpoint 1000") await page.setViewportSize({width:320,height:1000}); await page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]').evaluate(el=>{el.scrollLeft=0}); await cards.first().scrollIntoViewIfNeeded() await expect.poll(()=>region.evaluate(el=>el.scrollWidth<=el.clientWidth+1)).toBe(true) await expect.poll(()=>page.evaluate(()=>document.documentElement.scrollWidth)).toBeLessThanOrEqual(320) await page.addStyleTag({content:"nextjs-portal{display:none!important}"});await page.screenshot({path:info.outputPath("recursive-endpoints-320.png"),animations:"disabled"}) control.routes=[control.routes[999]];await page.getByRole("button",{name:"Обновить",exact:true}).click();await expect(cards).toHaveCount(1);await expect(cards.first()).toContainText("Endpoint 1");await expect(cards.first()).toContainText("gw-1000") })