fix(ui): ограничить редактор сценария без потери правил

This commit is contained in:
Denozordec committed 2026-10-04 16:38:19 +07:00
1 parent 2e648dcf1c
commit 9231597d88
8 files changed
+117 -17

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import { test, expect } from "@playwright/test"
import axe from "axe-core"
test("Сценарий1000: граница страниц, полный draft и симуляция скрытого правила", async ({ page }, info) => {
test.setTimeout(90000)
const packet = { chain: "forward", proto: "tcp", srcAddr: "10.10.0.100", dstAddr: "142.250.74.110", srcPort: "54321", dstPort: "443", inIface: "lan", outIface: "wan", connState: "new", srcAddrList: "", dstAddrList: "", tlsHost: "" }
const rules = Array.from({ length: 1000 }, (_, i) => ({ id: `s${i + 1}`, chain: "forward", action: i === 999 ? "drop" : "passthrough", proto: "all", src: "", dst: "", port: "", iface: "", comment: `scenario-${i + 1}`, enabled: true }))
const scenarios = [{ id: "big", name: "Сценарий1000", description: "", packet, rules, createdAt: 1 }]
await page.addInitScript(value => localStorage.setItem("routerlists-fw-scenarios", JSON.stringify(value)), scenarios)
let writes = 0
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
await page.route("**/api/**", r => {
if (r.request().method() !== "GET") writes++
const p = new URL(r.request().url()).pathname
return r.fulfill({ json: p === "/api/auth/config" ? { required: false } : p === "/api/firewall/all" ? { rules: [], addressLists: [] } : p === "/api/servers" ? [{ id: 1, name: "Роутер", host: "192.0.2.1", type: "exit-node", enabled: true, country: "RU", status: "online" }] : {} })
})
await page.goto("/firewall?server=1")
await page.getByRole("button", { name: "Симулятор", exact: true }).click()
await page.getByRole("button", { name: "Редактировать сценарий Сценарий1000", exact: true }).click()
const dialog = page.getByRole("dialog"), rows = dialog.locator("tr[data-row-id]")
await expect(rows).toHaveCount(50)
console.log("Scenario profile", JSON.stringify({ count: 1000, rows: await rows.count(), modalNodes: await dialog.locator("*").count() }))
await expect(dialog.getByRole("button", { name: "Поднять правило 1", exact: true })).toBeDisabled()
await dialog.getByRole("button", { name: "Опустить правило 50", exact: true }).click()
await expect(rows.last()).toHaveAttribute("data-row-id", "s51")
await dialog.getByRole("button", { name: "Следующая страница", exact: true }).click()
await expect(rows.first()).toHaveAttribute("data-row-id", "s50")
await dialog.getByRole("button", { name: "Поднять правило 51", exact: true }).focus(); await page.keyboard.press("Enter")
await expect(rows.first()).toHaveAttribute("data-row-id", "s51")
await dialog.getByRole("button", { name: "Удалить правило 51", exact: true }).click()
await expect(rows.first()).toHaveAttribute("data-row-id", "s52")
await page.setViewportSize({ width: 320, height: 1000 })
const remove = rows.first().getByRole("button", { name: "Удалить правило 51", exact: true })
await remove.scrollIntoViewIfNeeded()
await dialog.locator('[data-slot="scroll-area-viewport"]').evaluate(el => { el.scrollLeft = el.scrollWidth })
await expect(remove).toBeInViewport(); const box = (await remove.boundingBox())!
expect(box.height).toBeGreaterThanOrEqual(44); expect(box.width).toBeGreaterThanOrEqual(44); expect(box.x + box.width).toBeLessThanOrEqual(320)
expect((await dialog.boundingBox())?.width).toBe(320)
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320)
await page.addScriptTag({ content: axe.source })
expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[role="dialog"] [data-slot="data-grid"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([])
await page.addStyleTag({ content: "nextjs-portal{display:none!important}" })
await page.screenshot({ path: info.outputPath("scenario-320.png"), animations: "disabled" })
await dialog.getByRole("button", { name: "Сохранить изменения", exact: true }).click()
await expect(dialog).toHaveCount(0)
const saved = await page.evaluate(() => JSON.parse(localStorage.getItem("routerlists-fw-scenarios")!))
expect(saved[0].rules).toHaveLength(999)
expect(saved[0].rules.slice(48,52).map((r: { id: string }) => r.id)).toEqual(["s49", "s50", "s52", "s53"])
expect(saved[0].rules[998]).toEqual(rules[999])
await expect(page.getByRole("button", { name: "Активен", exact: true })).toBeVisible()
await page.getByRole("button", { name: "Мгновенно", exact: true }).click()
await page.getByRole("button", { name: "Запустить", exact: true }).click()
await expect(page.getByText("— правило #999", { exact: true })).toBeVisible()
await expect(page.getByText("Пакет заблокирован", { exact: false })).toBeVisible()
console.log("Scenario simulation nodes", await page.locator("*").count())
expect(writes).toBe(0)
})