Files
MikrotikManager/tests/e2e/gre-vxlan-layout.spec.ts
T

56 lines
4.2 KiB
TypeScript

import { test, expect } from "@playwright/test"
for (const section of ["gre", "vxlan"]) for (const device of ["desktop", "mobile"] as const) {
test(`${section}: клавиатура и layout ${device}`, async ({ page }, info) => {
await page.setViewportSize(device === "desktop" ? { width: 1440, height: 1000 } : { width: 390, height: 844 })
await page.clock.install({ time: new Date("2026-09-30T05:00:00Z") })
const devices = [1, 2].map(id => ({ id, name: id === 1 ? "Основной роутер" : "Резервный роутер", host: "192.0.2." + id, enabled: true, country: "RU", status: "online", type: "exit-node" }))
const gre = ["1", "2"].map(id => ({ id: "*" + id, serverId: id, name: "gre-" + id, localAddress: "192.0.2." + id, remoteAddress: "198.51.100." + id, localInnerIp: "10.0.0.1/30", remoteInnerIp: "10.0.0.2/30", poolId: "live", ipsec: null, mtu: 1476, keepaliveInterval: 10, keepaliveRetries: 3, dscp: "inherit", clampTcpMss: true, allowFastPath: true, comment: "", enabled: true, status: "up" }))
const vxlan = ["1", "2"].map(id => ({ id: "*" + id, serverId: id, name: "vxlan-" + id, vni: Number(id) * 100, port: 0, dstPort: 8472, vtepIp: "192.0.2." + id, remoteVteps: ["198.51.100." + id], l2mtu: 1500, arpProxy: false, macLearning: true, comment: "", enabled: true, status: "up" }))
let writes = 0
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() === "PATCH") {
writes++
expect(path).toBe("/api/gre/tunnels")
expect(r.request().postDataJSON()).toMatchObject({ serverId: "2", rosId: "*2", comment: "Проверенный контекст" })
gre[1].comment = "Проверенный контекст"
return r.fulfill({ json: { ok: true } })
}
return r.fulfill({ json: path === "/api/servers" ? devices : path === "/api/gre/tunnels" ? { tunnels: gre } : path === "/api/vxlan" ? { tunnels: vxlan } : path === "/api/auth/config" ? { required: false } : {} })
})
await page.goto("/" + section + "?server=2")
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
await page.addStyleTag({ content: "nextjs-portal { display: none !important; }" })
await page.screenshot({ path: info.outputPath(`${section}-${device}.png`), animations: "disabled" })
const menu = page.getByRole("button", { name: "Действия: " + section + "-2", exact: true })
await menu.focus()
await page.keyboard.press("Enter")
await page.getByRole("menuitem", { name: section === "gre" ? "Редактировать" : "Экспорт .rsc", exact: true }).click()
const dialog = page.getByRole("dialog")
await expect.poll(() => dialog.evaluate(el => el.getBoundingClientRect().left >= 0 && el.getBoundingClientRect().right <= innerWidth)).toBe(true)
if (device === "mobile") {
await expect.poll(() => dialog.evaluate(el => el.getBoundingClientRect().width >= innerWidth - 1)).toBe(true)
const footer = dialog.locator('[data-slot="sheet-footer"]')
expect(await footer.evaluate(el => [...el.querySelectorAll("button")].every(button => button.getBoundingClientRect().right <= innerWidth && button.getBoundingClientRect().left >= 0))).toBe(true)
}
if (section === "gre") {
await expect(dialog.getByLabel("Сервер (MikroTik)")).toHaveValue("2")
await dialog.getByLabel("Комментарий").fill("Проверенный контекст")
} else {
await expect(dialog).toContainText("Резервный роутер")
await expect(dialog).toContainText("vni=200")
}
await page.screenshot({ path: info.outputPath(`${section}-${section === "gre" ? "form" : "export"}-${device}.png`), animations: "disabled" })
if (section === "gre") {
await dialog.getByRole("button", { name: "Сохранить", exact: true }).focus()
await page.keyboard.press("Enter")
await expect(dialog).toHaveCount(0)
expect(writes).toBe(1)
} else { await page.keyboard.press("Escape"); expect(writes).toBe(0) }
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
})
}