69 lines
4.2 KiB
TypeScript
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([])
|
|
})
|