57 lines
6.0 KiB
TypeScript
57 lines
6.0 KiB
TypeScript
import { test, expect } from "@playwright/test"
|
||
import { appUserReadSchema } from "../../packages/contracts/src/users"
|
||
import { user, server, catalogResponse } from "./fixtures/users"
|
||
import axe from "axe-core"
|
||
import { mkdirSync } from "node:fs"
|
||
|
||
test("Users1000: DOM, весь поиск, детали/ошибка refresh и правильное действие", async ({ page }, info) => {
|
||
test.setTimeout(90000)
|
||
let users = Array.from({ length: 1000 }, (_, i) => appUserReadSchema.parse({ ...user, id: "u-" + (i + 1), name: "Клиент " + String(i + 1).padStart(4, "0"), login: "user" + (i + 1), email: "user" + (i + 1) + "@example.test" }))
|
||
let calls = 0, failed = false; const writes: string[] = []
|
||
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() !== "GET") writes.push(path)
|
||
if (path === "/api/users") { calls++; if (failed) return r.fulfill({ status: 503, json: { error: "Каталог временно недоступен" } }) }
|
||
return catalogResponse(r, { json: path === "/api/auth/config" ? { required: false } : path === "/api/users" ? { users } : path === "/api/servers" ? [server] : { interfaces: [] } })
|
||
})
|
||
const start = Date.now(); await page.goto("/users")
|
||
const rows = page.locator("tr[data-row-id]")
|
||
await expect(page.getByText("Клиент 0001", { exact: true })).toBeVisible()
|
||
const sample = { fixtureRows: 1000, fixtureBytes: Buffer.byteLength(JSON.stringify(users)), renderedRows: await rows.count(), domNodes: await page.locator("*").count(), readyMs: Date.now() - start }
|
||
console.log(JSON.stringify(sample)); await info.attach("large-users-metrics", { body: JSON.stringify(sample), contentType: "application/json" })
|
||
expect(sample.renderedRows).toBe(50); expect(sample.domNodes).toBeLessThan(5000)
|
||
for (let i = 0; i < 3; i++) await page.getByRole("button", { name: "…", exact: true }).last().click()
|
||
await page.getByRole("button", { name: "Перейти на страницу 20", exact: true }).click()
|
||
const detail = page.getByRole("button", { name: "Доступ и привязки пользователя Клиент 1000", exact: true })
|
||
await detail.focus(); await page.keyboard.press("Enter"); await expect(detail).toHaveAttribute("aria-expanded", "true")
|
||
const refresh = page.getByRole("button", { name: "Обновить пользователей", exact: true })
|
||
const before = calls; await refresh.click(); await expect.poll(() => calls).toBeGreaterThan(before); await expect(refresh).toBeEnabled()
|
||
await expect(page.getByText("951–1000 из 1000", { exact: true })).toBeVisible(); await expect(detail).toHaveAttribute("aria-expanded", "true")
|
||
failed = true; await refresh.click(); await expect(page.getByRole("alert", { name: "Пользователи", exact: true })).toBeVisible()
|
||
await expect(rows).toHaveCount(50); await expect(detail).toHaveAttribute("aria-expanded", "true")
|
||
failed = false; await refresh.click(); await expect(page.getByRole("alert", { name: "Пользователи", exact: true })).toHaveCount(0)
|
||
const search = page.getByRole("textbox", { name: "Поиск по имени, логину или email", exact: true })
|
||
for (const q of ["Клиент 1000", "user1000@example.test"]) { await search.fill(q); await expect(rows).toHaveCount(1); await expect(rows.first()).toHaveAttribute("data-row-id", "u-1000") }
|
||
await search.fill("нет совпадений"); await expect(page.getByText("Нет совпадений", { exact: true })).toBeVisible()
|
||
await search.fill(""); await expect(rows).toHaveCount(50)
|
||
const sort = page.getByRole("table").getByRole("button", { name: "Пользователь", exact: true }); await sort.click(); await sort.click()
|
||
await expect(rows.first()).toHaveAttribute("data-row-id", "u-1000")
|
||
await page.setViewportSize({ width: 320, height: 1000 })
|
||
const scroll = page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]'); await expect(scroll).toHaveCount(1)
|
||
await scroll.evaluate(el => { el.scrollLeft = el.scrollWidth })
|
||
const edit = rows.first().getByRole("button", { name: "Редактировать пользователя Клиент 1000", exact: true })
|
||
expect(await edit.evaluate(el => { const value = el.getBoundingClientRect(), viewport = el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(), pinned = el.closest("tr")!.querySelector("td")!.getBoundingClientRect(); return value.left >= pinned.right - 1 && value.right <= viewport.right + 1 })).toBe(true)
|
||
await edit.focus(); await page.keyboard.press("Enter")
|
||
await expect(page.getByRole("textbox", { name: "Полное имя", exact: true })).toHaveValue("Клиент 1000")
|
||
await page.getByRole("dialog").getByRole("button", { name: "Отмена", exact: true }).click(); await expect(edit).toBeFocused()
|
||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
||
await page.addScriptTag({ content: axe.source })
|
||
const audit = await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[data-slot="data-grid"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } })); expect(audit.violations).toEqual([])
|
||
await page.addStyleTag({ content: "nextjs-portal { display: none !important; }" }); mkdirSync("docs/ux-ui/screens", { recursive: true })
|
||
await page.getByRole("table").getByRole("button", { name: "Пользователь", exact: true }).scrollIntoViewIfNeeded(); await page.screenshot({ path: "docs/ux-ui/screens/large-users-320.png", animations: "disabled" })
|
||
await page.getByRole("button", { name: "Следующая страница", exact: true }).click(); users = [users[999]]; const previous = calls; await refresh.click()
|
||
await expect.poll(() => calls).toBeGreaterThan(previous); await expect(rows).toHaveCount(1); await expect(rows.first()).toHaveAttribute("data-row-id", "u-1000")
|
||
expect(writes).toEqual([])
|
||
})
|