import { test, expect, type Page } from "@playwright/test" const at = new Date("2026-10-12T12:00:00Z") async function setup(page: Page, state: "error" | "current" | "legacy", status = "online") { let calls = 0 const observations = { rxNow: state === "current", txNow: state === "current", rxSeries: [false, true], txSeries: [false, true], sampledAt: at.toISOString(), lastAttemptAt: at.toISOString(), lastSucceededAt: at.toISOString(), expiresAt: new Date(at.getTime()+90_000).toISOString(), currentState: state === "legacy" ? "unknown" : state, error: state === "error" ? "CHR: HTTP 401 — опрос не выполнен" : null } const server = { id: "1", name: "История CHR", site: "TEST", country: "RU", status, rxNow: state === "current" ? 0 : 17, txNow: 0, rxPeak: 5, txPeak: 6, rxTotal: 1, txTotal: 2, sessions: 1, rxSeries: [0,5], txSeries: [0,6], ...(state !== "legacy" ? {observations} : {}) } await page.clock.install({time:at}) await page.route("**/health", r=>r.fulfill({json:{ok:true}})) await page.route("**/api/**", r=> { const path = new URL(r.request().url()).pathname if(path.endsWith("/live")) return r.fulfill({status:503,json:{error:"Live недоступен"}}) if(path==="/api/traffic/servers")calls++ return r.fulfill({json:path==="/api/auth/config"?{required:false}:path==="/api/traffic/servers"?{servers:[server]}: path==="/api/traffic/servers/1"?{server}:path.endsWith("/interfaces")||path==="/api/traffic/bound-interfaces"?{interfaces:[]}: path==="/api/traffic/users"?{users:[]}:path==="/api/servers"?[]:{}}) }) await page.goto("/traffic") await expect(page.getByRole("heading",{name:"История CHR",exact:true})).toBeVisible() return ()=>calls } for(const width of [1440,320]) test(`Ошибка опроса: история сохранена, текущая скорость неизвестна ${width}`,async({page})=>{ await page.setViewportSize({width,height:1000});await setup(page,"error","offline") await expect(page.getByRole("status").filter({hasText:"Ошибка опроса трафика"})).toContainText("HTTP 401") await expect(page.getByRole("region",{name:"История RX/TX"})).toBeVisible() await expect(page.getByLabel("Скорость выбранного сервера")).not.toContainText("17.0") await expect(page.getByLabel("Скорость выбранного сервера").getByText("—",{exact:true})).toHaveCount(2) await expect(page.getByLabel("Попытка опроса трафика")).toBeVisible() expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true) await page.getByRole("status").filter({hasText:"Ошибка опроса трафика"}).scrollIntoViewIfNeeded() await page.getByRole("status").filter({hasText:"Ошибка опроса трафика"}).evaluate(el => { let parent = el.parentElement while (parent && (!/auto|scroll/.test(getComputedStyle(parent).overflowY) || parent.scrollHeight <= parent.clientHeight)) parent = parent.parentElement if (parent) parent.scrollTop += el.getBoundingClientRect().top - parent.getBoundingClientRect().top - 24 }) await page.screenshot({path:`docs/ux-ui/screens/traffic-history-error-${width}-2026-10-12.png`,fullPage:true}) }) test("Измеренный ноль сохраняется; по TTL скорость становится неизвестной без подмены истории",async({page})=>{ const calls=await setup(page,"current") await expect(page.getByLabel("Скорость выбранного сервера").getByText("0 Кбит/с",{exact:true})).toHaveCount(2) const before=calls();await page.clock.fastForward(100_001) await expect(page.getByRole("status").filter({hasText:"Измерения трафика устарели"})).toBeVisible() await expect(page.getByLabel("Скорость выбранного сервера").getByText("—",{exact:true})).toHaveCount(2) await expect(page.getByRole("region",{name:"История RX/TX"})).toBeVisible();expect(calls()).toBe(before) }) test("Legacy numeric DTO без происхождения не изображает измеренную историю",async({page})=>{ await setup(page,"legacy") await expect(page.getByText("Нет исторических измерений за выбранный период",{exact:true})).toBeVisible() await expect(page.getByRole("status").filter({hasText:"Скорость пока не измерена"})).toBeVisible() await expect(page.getByLabel("Скорость выбранного сервера")).not.toContainText("17.0") })