fix(ui): ограничить каталог сертификатов и сохранить детали

This commit is contained in:
Denozordec committed 2026-10-04 04:29:30 +07:00
1 parent 5488d86b12
commit 5fbfc752fb
7 files changed
+98 -7

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
import { test, expect } from "@playwright/test"
import { certificatesStateSchema } from "../../packages/contracts/src/certificates"
import { certificate, certificateState, server } from "./fixtures/certificates"
import axe from "axe-core"
import { mkdirSync } from "node:fs"
test("Certificates1000: DOM, страницы, детали, поиск, refresh и320px", async ({ page }, info) => {
test.setTimeout(90000)
let certificates = Array.from({ length: 1000 }, (_, i) => ({ ...certificate, id: "1:*" + (i + 1), rosId: "*" + (i + 1), name: "cert-" + (i + 1), sans: ["alt" + (i + 1) + ".example.test"], daysLeft: 300 + i, validFrom: "2026-09-01T00:00:00Z", validUntil: new Date(Date.parse("2026-10-01T00:00:00Z") + (300 + i) * 86400000).toISOString(), trusted: true, status: "valid", keySize: 2048 }))
let calls = 0; 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/certificates/state") calls++
return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [server] : path === "/api/certificates/state" ? certificatesStateSchema.parse(certificateState(certificates)) : path === "/api/certificates/acme-settings" ? { directoryUrl: "https://acme.example.test/directory", tokenConfigured: false } : path === "/api/certificates/renew-settings" ? { enabled: false, intervalSec: 300, renewBeforeDays: 30 } : {} })
})
const start = Date.now(); await page.goto("/certificates?server=1")
const rows = page.locator("tr[data-row-id]")
await expect(page.getByText("cert-1", { exact: true })).toBeVisible()
const sample = { fixtureRows: 1000, fixtureBytes: Buffer.byteLength(JSON.stringify(certificateState(certificates))), renderedRows: await rows.count(), domNodes: await page.locator("*").count(), readyMs: Date.now() - start }
console.log(JSON.stringify(sample)); await info.attach("large-certificates-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: "Детали сертификата cert-1000", exact: true })
await detail.focus(); await page.keyboard.press("Enter")
await expect(detail).toHaveAttribute("aria-expanded", "true")
await expect(page.getByText("alt1000.example.test", { exact: true })).toBeVisible()
const before = calls; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect.poll(() => calls).toBeGreaterThan(before)
await expect(page.getByRole("button", { name: "Обновить", exact: true })).toBeEnabled()
await expect(page.getByText("951–1000 из 1000", { exact: true })).toBeVisible()
await expect(detail).toHaveAttribute("aria-expanded", "true")
const search = page.getByRole("textbox", { name: "Поиск по имени, CN, эмитенту…" })
await search.fill("cert-1000"); await expect(rows).toHaveCount(1)
await expect(page).toHaveURL(url => url.searchParams.get("q") === "cert-1000" && url.searchParams.get("server") === "1")
await page.reload(); await expect(rows).toHaveCount(1); await expect(search).toHaveValue("cert-1000")
await search.fill(""); await expect(rows).toHaveCount(50)
await page.getByRole("table").getByRole("button", { name: "Срок", exact: true }).click()
await expect(rows.first()).toHaveAttribute("data-row-id", "1:*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)
expect(await scroll.evaluate(el => el.scrollWidth > el.clientWidth)).toBe(true)
await scroll.evaluate(el => { el.scrollLeft = el.scrollWidth })
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").scrollIntoViewIfNeeded(); await page.screenshot({ path: "docs/ux-ui/screens/large-certificates-320.png", animations: "disabled" })
await page.getByRole("button", { name: "Следующая страница", exact: true }).click()
certificates = [certificates[999]]; const previous = calls; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect.poll(() => calls).toBeGreaterThan(previous); await expect(rows).toHaveCount(1)
await expect(rows.first()).toHaveAttribute("data-row-id", "1:*1000")
expect(writes).toEqual([])
})