Files
MikrotikManager/tests/e2e/server-rail-accessibility.spec.ts
T

69 lines
4.2 KiB
TypeScript

import { test, expect } from "@playwright/test"
import axe from "axe-core"
import { serverReadSchema } from "../../packages/contracts/src/servers"
for (const theme of ["light", "dark"]) test(`Server type badges: all types have readable contrast/${theme}`, async ({ page }) => {
await page.addInitScript(value => localStorage.setItem("rl-theme", value), theme)
await page.route("**/health", route => route.fulfill({ json: { ok: true } }))
const servers = ["home-router", "exit-node", "jump-host"].map((type, i) => serverReadSchema.parse({
id: i + 1, name: `Контраст ${i + 1}`, host: `192.0.2.${i + 1}`, port: 443,
username: "fixture", password: "fixture", useSsl: true, verifySsl: true, type, site: "TEST", country: "RU", asn: "", comment: "",
enabled: true, lanSubnet: "", wanUplinks: [], createdAt: "2026-10-15T00:00:00Z", updatedAt: "2026-10-15T00:00:00Z",
status: "online", latency: 1, os: "7.24.5", model: "CHR", uptime: null, cpuLoad: null,
freeMemory: null, totalMemory: null, identityName: null, sessions: 0, polledAt: null,
}))
await page.route("**/api/**", route => route.fulfill({ json: new URL(route.request().url()).pathname === "/api/servers" ? servers
: new URL(route.request().url()).pathname === "/api/auth/config" ? { required: false } : {} }))
await page.goto("/servers")
for (const name of ["Home Router", "Exit Node", "JumpHost"]) await expect(page.getByRole("cell", { name, exact: true })).toBeVisible()
await page.addScriptTag({ content: axe.source })
const violations = await page.evaluate(async () => (await (window as unknown as { axe: typeof axe }).axe.run("#main-content", {
runOnly: { type: "rule", values: ["color-contrast"] },
})).violations.map(v => ({ id: v.id, targets: v.nodes.map(n => n.target) })))
expect(violations).toEqual([])
})
for (const theme of ["light", "dark"]) test(`Server rail: keyboard selection across pages and target sizes/${theme}`, async ({ page }) => {
await page.addInitScript(value => localStorage.setItem("rl-theme", value), theme)
await page.route("**/health", route => route.fulfill({ json: { ok: true } }))
await page.route("**/api/**", route => {
const path = new URL(route.request().url()).pathname
return route.fulfill({ json: path === "/api/auth/config" ? { required: false }
: path === "/api/servers" ? Array.from({ length: 61 }, (_, i) => ({
id: i + 1, name: `Роутер ${String(i + 1).padStart(3, "0")}`, host: `192.0.2.${i + 1}`,
enabled: true, status: "online", type: "home-router", country: "RU",
})) : path === "/api/gre/tunnels" ? { tunnels: [], failures: [] } : {} })
})
await page.goto("/gre")
const rail = page.getByRole("listbox", { name: "Серверы", exact: true })
const all = rail.getByRole("option", { name: /Все серверы/ })
await expect(all).toHaveAttribute("aria-selected", "true")
await all.focus()
await page.keyboard.press("ArrowDown")
const first = rail.getByRole("option", { name: /Роутер 001/ })
await expect(first).toBeFocused()
await expect(first).toHaveAttribute("aria-selected", "true")
await expect(page).toHaveURL(/server=1(?:&|$)/)
await page.keyboard.press("End")
const last = rail.getByRole("option", { name: /Роутер 061/ })
await expect(last).toBeFocused()
await expect(last).toHaveAttribute("aria-selected", "true")
await expect(last).toBeInViewport()
await expect(page).toHaveURL(/server=61(?:&|$)/)
await page.keyboard.press("ArrowUp")
await expect(rail.getByRole("option", { name: /Роутер 060/ })).toBeFocused()
await page.keyboard.press("Home")
await expect(all).toBeFocused()
await expect(all).toHaveAttribute("aria-selected", "true")
await page.keyboard.press("Tab")
await expect.poll(() => rail.evaluate(el => el.contains(document.activeElement))).toBe(false)
await page.addScriptTag({ content: axe.source })
const violations = await page.evaluate(async () => {
const engine = (window as unknown as { axe: typeof axe }).axe
return (await engine.run(document.querySelector('[role="listbox"][aria-label="Серверы"]')!, {
runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"] },
})).violations.map(v => ({ id: v.id, targets: v.nodes.map(n => n.target) }))
})
expect(violations).toEqual([])
})