101 lines
11 KiB
TypeScript
101 lines
11 KiB
TypeScript
import {test,expect} from "@playwright/test"
|
|
const empty={"hops":[],"live":false,"rangeMinutes":5,"windowSec":300,"totalBytes":0,"namedBytes":0,"unclassifiedBytes":0,"asnLoaded":false,"countryLoaded":false,"services":[],"serviceEdges":[],"servicePaths":[],"countries":[],"countryEdges":[],"countryPaths":[],"countryServiceGroups":[],"mapServiceMinSharePct":5,"dedupApplied":false,"excludeMeshApplied":false,"excludeOverlayApplied":false}
|
|
const wan={id:"w",name:"WAN",iface:"ether1",isp:"ISP",ip:"192.0.2.1",maxDl:100,maxUl:100}
|
|
const home={id:1,name:"Домашний",host:"192.0.2.1",type:"home-router",country:"RU",enabled:true,status:"online",latency:2,wanUplinks:[wan]}
|
|
const jh={id:2,name:"Jump",host:"192.0.2.2",type:"jump-host",country:"RU",enabled:true,status:"online",latency:3}
|
|
const tunnel={id:"t1",serverId:"1",name:"gre-home",localAddress:"192.0.2.1",remoteAddress:"192.0.2.2",localInnerIp:"10.0.0.1",remoteInnerIp:"10.0.0.2",poolId:"",ipsec:null,mtu:1476,keepaliveInterval:10,keepaliveRetries:3,dscp:"inherit",clampTcpMss:true,allowFastPath:false,comment:"",enabled:true,status:"up"}
|
|
test.beforeEach(async({page})=>{
|
|
await page.route("**/health",r=>r.fulfill({json:{ok:true}}))
|
|
await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==="/api/servers"?[{...home,name:"Домашний роутер с очень длинным названием и описанием устройства"},{...jh,name:"Промежуточный узел с длинной подписью"}]:p==="/api/filters/gre-tunnels"?{tunnels:[tunnel],failures:[]}:p==="/api/uptime/speed-probes"?{probes:[]}:p==="/api/traffic/flow/map-hops"?empty:p==="/api/auth/config"?{required:false}:{}})})
|
|
})
|
|
for(const width of [1440,390,320])test("Карта: доступные детали и адаптивная оболочка "+width,async({page})=>{
|
|
await page.setViewportSize({width,height:900});await page.goto("/network-map")
|
|
await expect(page.getByRole("combobox",{name:"Устройство на карте"})).toBeEnabled()
|
|
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
|
await page.screenshot({path:"docs/ux-ui/screens/network-map-overview-"+(width===1440?"desktop":width===390?"mobile":"narrow")+".png"})
|
|
await page.getByRole("combobox",{name:"Устройство на карте"}).selectOption("1")
|
|
const panel=page.getByRole("complementary",{name:"Детали объекта карты"})
|
|
await expect(panel).toBeVisible();await expect(panel.getByText("Ресурсы",{exact:true})).toBeVisible()
|
|
const bounds=await panel.boundingBox(),map=width>=1024?await page.getByRole("group",{name:"Интерактивная карта сети"}).boundingBox():null
|
|
expect(bounds!.width).toBeGreaterThan(250);expect(bounds!.height).toBeGreaterThan(350)
|
|
if(width<1024)await expect(page.getByRole("group",{name:"Интерактивная карта сети"})).toBeHidden()
|
|
else expect(map!.height).toBeGreaterThan(0)
|
|
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
|
|
await page.screenshot({path:"docs/ux-ui/screens/network-map-detail-"+(width===1440?"desktop":width===390?"mobile":"narrow")+".png"})
|
|
await panel.getByRole("button",{name:"Закрыть детали устройства"}).click()
|
|
await expect(panel).toHaveCount(0)
|
|
const node=page.getByRole("button",{name:/^Устройство Домашний/});await node.focus();await node.press("Enter");await expect(panel).toBeVisible()
|
|
await page.getByRole("button",{name:"Действия устройства",exact:true}).click()
|
|
const dialog=page.getByRole("dialog",{name:/^Действия устройства/});await expect(dialog).toBeVisible()
|
|
await expect(dialog.getByRole("link",{name:"GRE-туннели",exact:true})).toHaveAttribute("href","/gre?server=1")
|
|
await page.keyboard.press("Tab");expect(await dialog.evaluate(element=>element.contains(document.activeElement))).toBe(true)
|
|
await page.keyboard.press("Escape");await expect(dialog).toHaveCount(0);await expect(page.getByRole("button",{name:"Действия устройства",exact:true})).toBeFocused()
|
|
await expect(page).toHaveURL(/server=1/)
|
|
await page.getByRole("button",{name:"Слои",exact:true}).click();await expect(page.getByRole("dialog",{name:"Слои карты"})).toBeVisible()
|
|
await page.keyboard.press("Escape");await expect(page.getByRole("button",{name:"Слои",exact:true})).toBeFocused();await expect(page).toHaveURL(/server=1/)
|
|
})
|
|
test("Карта: reduced motion и самостоятельный SVG экспорт",async({page})=>{
|
|
await page.emulateMedia({reducedMotion:"reduce"});await page.goto("/network-map")
|
|
await expect(page.getByRole("button",{name:/^Устройство Домашний/})).toBeVisible()
|
|
await expect(page.locator("animateMotion")).toHaveCount(0)
|
|
const wait=page.waitForEvent("download");await page.getByRole("button",{name:"Экспорт SVG",exact:true}).click();const download=await wait
|
|
const stream=await download.createReadStream();let source="";for await(const chunk of stream!)source+=chunk.toString()
|
|
expect(source).toContain("--foreground:");expect(source).toContain("--background:");expect(source).toContain('xmlns="http://www.w3.org/2000/svg"')
|
|
const parsed=await page.evaluate(source=>{const doc=new DOMParser().parseFromString(source,"image/svg+xml");return {errors:doc.querySelectorAll("parsererror").length,width:doc.documentElement.getAttribute("width")}},source)
|
|
expect(parsed.errors).toBe(0);expect(parsed.width).toBe("1240")
|
|
})
|
|
|
|
test("Карта: пропорции камеры, ручной масштаб и обновление без сброса",async({page})=>{
|
|
await page.setViewportSize({width:390,height:900});await page.goto("/network-map")
|
|
const map=page.getByRole("group",{name:"Интерактивная карта сети"})
|
|
await expect(page.getByRole("button",{name:/^Устройство Домашний/})).toBeVisible()
|
|
await expect.poll(async()=>Number((await map.getAttribute("viewBox"))!.split(" ")[2])).toBeLessThan(1240)
|
|
const camera=await map.evaluate((element:SVGSVGElement)=>{const rect=element.getBoundingClientRect(),box=element.viewBox.baseVal;return {ratio:box.width/box.height,screenRatio:rect.width/rect.height}})
|
|
expect(camera.ratio).toBeCloseTo(camera.screenRatio,3)
|
|
await page.getByRole("button",{name:"Увеличить карту",exact:true}).click()
|
|
const manual=await map.getAttribute("viewBox")
|
|
await page.getByRole("button",{name:"Обновить",exact:true}).click()
|
|
await expect(page.getByRole("button",{name:"Обновить",exact:true})).toBeEnabled();await expect(map).toHaveAttribute("viewBox",manual!)
|
|
await page.getByRole("button",{name:"Вписать карту",exact:true}).click()
|
|
expect(await map.getAttribute("viewBox")).not.toBe(manual)
|
|
await page.getByRole("combobox",{name:"Устройство на карте"}).selectOption("1")
|
|
const wan=page.getByRole("button",{name:"WAN WAN",exact:true});await wan.focus();await wan.press("Enter")
|
|
await expect(page).toHaveURL(/wan=0/);await expect(wan).toHaveAttribute("aria-pressed","true")
|
|
})
|
|
|
|
test("Карта: pointer capture, клавиатурный pan и возврат фокуса",async({page})=>{
|
|
await page.goto("/network-map");const map=page.getByRole("group",{name:"Интерактивная карта сети"}),node=page.getByRole("button",{name:/^Устройство Домашний/})
|
|
await expect(node).toBeVisible();await map.focus();const before=await map.getAttribute("viewBox");await map.press("ArrowDown");expect(await map.getAttribute("viewBox")).not.toBe(before)
|
|
await page.getByRole("combobox",{name:"Устройство на карте"}).selectOption("1")
|
|
const transform=await node.getAttribute("transform"),point=await node.evaluate((g:SVGGElement)=>{const matrix=g.getScreenCTM()!;return {x:matrix.e,y:matrix.f}})
|
|
await page.mouse.move(point.x,point.y);await page.mouse.down();await page.mouse.move(point.x+80,point.y+25,{steps:5});await page.mouse.up()
|
|
expect(await node.getAttribute("transform")).not.toBe(transform);await expect(page).toHaveURL(/server=1/)
|
|
await page.getByRole("button",{name:"Закрыть детали устройства"}).click();await expect(page.getByRole("combobox",{name:"Устройство на карте"})).toBeFocused()
|
|
const box=(await map.boundingBox())!,pan=await map.getAttribute("viewBox")
|
|
await page.mouse.move(box.x+15,box.y+box.height-50);await page.mouse.down();await page.mouse.move(box.x-30,box.y+box.height-80,{steps:5});await page.mouse.up()
|
|
expect(await map.getAttribute("viewBox")).not.toBe(pan)
|
|
const stopped=await map.getAttribute("viewBox");await page.mouse.move(box.x+100,box.y+100);expect(await map.getAttribute("viewBox")).toBe(stopped)
|
|
})
|
|
test("Карта: touch pan и отмена жеста без залипания",async({page})=>{
|
|
await page.setViewportSize({width:390,height:900});await page.goto("/network-map")
|
|
const map=page.getByRole("group",{name:"Интерактивная карта сети"});await expect(page.getByRole("combobox",{name:"Устройство на карте"})).toBeEnabled()
|
|
expect(await map.evaluate(e=>getComputedStyle(e).touchAction)).toBe("none")
|
|
const box=(await map.boundingBox())!,before=await map.getAttribute("viewBox"),session=await page.context().newCDPSession(page)
|
|
await session.send("Input.dispatchTouchEvent",{type:"touchStart",touchPoints:[{x:box.x+20,y:box.y+box.height-70,id:1}]})
|
|
await session.send("Input.dispatchTouchEvent",{type:"touchMove",touchPoints:[{x:box.x+70,y:box.y+box.height-100,id:1}]})
|
|
await session.send("Input.dispatchTouchEvent",{type:"touchCancel",touchPoints:[]})
|
|
expect(await map.getAttribute("viewBox")).not.toBe(before)
|
|
const stopped=await map.getAttribute("viewBox");await page.mouse.move(box.x+150,box.y+50);expect(await map.getAttribute("viewBox")).toBe(stopped)
|
|
await page.getByRole("button",{name:/^Устройство Домашний/}).click();await expect(page.getByRole("complementary",{name:"Детали объекта карты"})).toBeVisible()
|
|
})
|
|
test("Карта: пустой источник отличается от пустого среза",async({page})=>{
|
|
await page.goto("/network-map");await expect(page.getByRole("combobox",{name:"Устройство на карте"})).toBeEnabled()
|
|
await page.getByRole("textbox",{name:"Поиск узла"}).fill("нет такого узла");await expect(page.getByRole("status").filter({hasText:"Узлы по заданным условиям"})).toBeVisible()
|
|
await page.getByRole("button",{name:"Сбросить срез карты"}).click();await expect(page.getByRole("textbox",{name:"Поиск узла"})).toHaveValue("")
|
|
await expect(page.getByRole("button",{name:/^Устройство Домашний/})).toBeVisible()
|
|
await page.route("**/api/servers",r=>r.fulfill({json:[]}));await page.route("**/api/filters/gre-tunnels",r=>r.fulfill({json:{tunnels:[],failures:[]}}))
|
|
await page.getByRole("button",{name:"Обновить",exact:true}).click()
|
|
await expect(page.getByRole("status").filter({hasText:"Устройств на карте пока нет"})).toBeVisible();await expect(page.getByRole("link",{name:"Открыть серверы",exact:true})).toHaveAttribute("href","/servers")
|
|
await expect(page.getByRole("button",{name:"Экспорт SVG",exact:true})).toBeDisabled()
|
|
})
|