fix(ui): исправить фокус списка серверов и контраст общих элементов
This commit is contained in:
1 parent
9b0d7bb6e8
commit
3c9096804b
21 files changed
+6952
-58
No files matched your search
@@ -0,0 +1,68 @@
|
||||
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([])
|
||||
})
|
||||
Reference in new issue
Block a user