fix(ui): проверить терминал и мобильный выбор устройства

This commit is contained in:
Denozordec committed 2026-10-01 03:59:47 +07:00
1 parent ea4095d1f1
commit 8bda85129f
23 files changed
+102 -9

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
import {test,expect} from "@playwright/test"
const headers={"access-control-allow-origin":"*","access-control-allow-methods":"GET,POST,OPTIONS","access-control-allow-headers":"Content-Type,Authorization"}
const row=(name:string)=>({id:1,name,host:"192.0.2.1",country:"RU",enabled:true,status:"online",os:null})
for(const change of ["backend","mode"])for(const part of ["catalog","exec"])for(const fails of [false,true])test("Терминал: поздний "+part+"/"+change+"/"+fails,async({page})=>{
let started=false,completed=false,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("**/health",r=>r.fulfill({headers,json:{ok:true}}))
await page.route("**/api/**",r=>{const u=new URL(r.request().url()),p=u.pathname;return r.fulfill({headers,json:p==="/api/auth/config"?{required:false}:p==="/api/servers"?[row(u.port==="8101"?"Новый источник":"Прежний источник")]:p.endsWith("/exec")?{output:"новый вывод"}:{}})})
await page.goto("/settings");await page.getByRole("link",{name:/^Терминал/}).click();await page.getByRole("option",{name:/Прежний источник/}).click()
await expect(page.getByRole("textbox",{name:"Команда · Прежний источник"})).toBeEnabled()
const path=part==="catalog"?"/api/servers":"/api/servers/1/exec"
await page.route("**/api/**",async r=>{const u=new URL(r.request().url());if(u.port!=="8000"||u.pathname!==path||started)return r.fallback();started=true;await gate;try{await r.fulfill(fails?{headers,status:503,json:{error:"Прежняя ошибка терминала"}}:{headers,json:part==="catalog"?[row("Поздний каталог")]:{output:"Поздний вывод терминала"}})}catch{}finally{completed=true}})
if(part==="catalog")await page.getByRole("button",{name:"Обновить устройства",exact:true}).click()
else{await page.getByRole("textbox",{name:/^Команда ·/}).fill("interface print");await page.getByRole("button",{name:"Выполнить",exact:true}).click()}
await expect.poll(()=>started).toBe(true);await page.getByRole("link",{name:/^Настройки/}).click()
if(change==="backend"){const input=page.getByPlaceholder("http://localhost:8000");await input.fill("http://localhost:8101");await input.press("Enter")}else await page.getByRole("button",{name:"Моковые",exact:true}).click()
await page.getByRole("link",{name:/^Терминал/}).click()
if(change==="backend")await page.getByRole("option",{name:/Новый источник/}).click();else await page.getByRole("option").first().click()
await expect(page.getByRole("textbox",{name:/^Команда ·/})).toBeEnabled()
release();await expect.poll(()=>completed).toBe(true);await page.evaluate(()=>new Promise<void>(resolve=>requestAnimationFrame(()=>requestAnimationFrame(()=>resolve()))))
await expect(page.getByText("Поздний каталог",{exact:false})).toHaveCount(0);await expect(page.getByText("Поздний вывод терминала",{exact:false})).toHaveCount(0);await expect(page.getByText("Прежняя ошибка терминала",{exact:false})).toHaveCount(0)
if(change==="backend")await expect(page.getByRole("textbox",{name:"Команда · Новый источник"})).toBeEnabled();else await expect(page.getByRole("status").filter({hasText:"Демо: команды не отправляются"})).toBeVisible()
})
+18
View File
@@ -46,3 +46,21 @@ test("Терминал: reader видит устройство, выполнен
await page.route("**/api/auth/config",r=>r.fulfill({json:{required:true}}))
await page.goto("/terminal?server=1");await expect(command(page)).toBeDisabled();await expect(page.getByRole("status").filter({hasText:"Для выполнения требуется mm:servers:write"})).toBeVisible()
})
for(const q of ["server=1&server=2","server=1&host=192.0.2.1","server=bad%20id"])test("Терминал: неверный URL не читает каталог "+q,async({page})=>{
let reads=0;await page.route("**/api/servers",r=>{reads++;return r.fulfill({json:devices})})
await page.goto("/terminal?"+q);await expect(page.getByRole("alert").filter({hasText:"Некорректная ссылка"})).toBeVisible();expect(reads).toBe(0)
await page.getByRole("button",{name:"Сбросить контекст терминала"}).click();await expect(page.getByRole("option",{name:/Первый/})).toBeVisible();expect(reads).toBe(1)
})
test("Терминал: disabled и известный пустой каталог",async({page})=>{
await page.route("**/api/servers",r=>r.fulfill({json:[{...devices[0],enabled:false}]}));await page.goto("/terminal?server=1")
await expect(command(page)).toBeDisabled();await expect(page.getByRole("status").filter({hasText:"Устройство отключено"})).toBeVisible()
await page.route("**/api/servers",r=>r.fulfill({json:[]}));await page.goto("/terminal");await expect(page.getByText("Устройств пока нет",{exact:true})).toBeVisible();await expect(command(page)).toHaveCount(0)
})
test("Терминал: таймаут сохраняет команду и не повторяет автоматически",async({page})=>{
let calls=0,release!:()=>void;const gate=new Promise<void>(resolve=>release=resolve)
await page.route("**/api/servers/1/exec",async r=>{calls++;await gate;await r.fulfill({json:{output:"запоздалый ответ"}}).catch(()=>{})})
await page.clock.install();await page.goto("/terminal?server=1");await command(page).fill("interface print");await command(page).press("Enter");await expect.poll(()=>calls).toBe(1)
await page.clock.fastForward(60010);await expect(page.getByRole("alert").filter({hasText:"Время ожидания истекло"})).toBeVisible();await expect(command(page)).toHaveValue("interface print");await expect(command(page)).toBeEnabled();expect(calls).toBe(1)
release();await expect(page.getByRole("region",{name:"Вывод терминала"})).not.toContainText("запоздалый ответ")
})
+18
View File
@@ -0,0 +1,18 @@
import {test,expect} from "@playwright/test"
for(const width of [1440,390,320])test("Терминал: адаптивный вывод, поле и быстрые команды "+width,async({page})=>{
const row={id:1,name:"Домашний роутер с длинным названием и описанием устройства",host:"192.0.2.1",country:"RU",enabled:true,status:null,os:null}
await page.route("**/health",r=>r.fulfill({json:{ok:true}}));await page.route("**/api/**",r=>{const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==="/api/servers"?[row]:p==="/api/auth/config"?{required:false}:p.endsWith("/exec")?{output:Array.from({length:1200},(_,i)=>"Строка "+i+" "+"x".repeat(160)).join("\n")}:{}})})
await page.setViewportSize({width,height:900});await page.goto("/terminal?server=1")
const input=page.getByRole("textbox",{name:/^Команда ·/}),output=page.getByRole("region",{name:"Вывод терминала"})
await expect(input).toBeEnabled();await expect(output).not.toContainText("Connected")
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
if(width<768){const control=(await page.getByRole("button",{name:"Серверы",exact:true}).boundingBox())!;expect(control.x+control.width).toBeLessThanOrEqual(width)}
await page.screenshot({path:"docs/ux-ui/screens/terminal-ready-"+(width===1440?"desktop":width===390?"mobile":"narrow")+".png"})
await page.getByRole("combobox",{name:"Быстрая команда"}).selectOption("interface print");await input.press("Enter")
await expect(input).toHaveValue("");await expect(output).toContainText("Показаны последние 1000 строк");await expect(output).toContainText("Строка 1199");await expect(output).not.toContainText("Строка 0 ")
expect(await output.evaluate(e=>e.scrollHeight>e.clientHeight)).toBe(true);expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth)).toBe(true)
const box=(await input.boundingBox())!;expect(box.width).toBeGreaterThan(100);expect(box.y+box.height).toBeLessThanOrEqual(900)
await page.screenshot({path:"docs/ux-ui/screens/terminal-output-"+(width===1440?"desktop":width===390?"mobile":"narrow")+".png"})
await page.getByRole("button",{name:"Очистить вывод терминала"}).click();await expect(output).toContainText("Вывод очищен.")
if(width<768){await page.getByRole("button",{name:"Серверы",exact:true}).click();const dialog=page.getByRole("dialog");await expect(dialog).toBeVisible();await page.keyboard.press("Escape");await expect(dialog).toHaveCount(0);await expect(page.getByRole("button",{name:"Серверы",exact:true})).toBeFocused()}
})