diff --git a/scripts/check-gui-render.mjs b/scripts/check-gui-render.mjs index 95dc6b9..3eec169 100755 --- a/scripts/check-gui-render.mjs +++ b/scripts/check-gui-render.mjs @@ -13,7 +13,7 @@ const OUT_DIR = process.env.GUI_SMOKE_OUT || path.join(os.tmpdir(), 'verstak-gui const CHROMIUM = process.env.CHROMIUM_BIN || findChromium() const HOST = '127.0.0.1' -const flowUnderTest = 'app loads -> first meaningful screen renders -> primary visible controls respond without runtime errors.' +const flowUnderTest = 'app loads -> first meaningful screen renders -> primary visible controls respond and mutate UI state without runtime errors.' if (!CHROMIUM) { fail('Chromium executable not found. Set CHROMIUM_BIN or install chromium.') @@ -138,6 +138,11 @@ async function runReadyScenario(cdp, url) { await clickText(cdp, '.tab', 'Заметки') await assertText(cdp, 'Smoke note', 'notes: existing note visible') + await clickText(cdp, '.btn', '+ Добавить заметку') + await waitForSelector(cdp, '.create-form input[type="text"]') + await setInputValue(cdp, '.create-form input[type="text"]', 'GUI smoke note') + await clickText(cdp, '.create-form .btn', 'Создать') + await assertText(cdp, 'GUI smoke note', 'notes: created note appears') await clickText(cdp, '.tab', 'Файлы') await waitForSelector(cdp, '.file-row') @@ -146,12 +151,21 @@ async function runReadyScenario(cdp, url) { await waitForSelector(cdp, '.back-btn') await assertEval(cdp, `document.querySelector('.back-btn')?.innerText.trim() === 'Назад'`, 'files: back button has one textual label') await screenshot(cdp, 'files-folder.png') + await click(cdp, '.back-btn') + await assertText(cdp, 'brief.md', 'files: back button returns to parent folder') await clickText(cdp, '.tab', 'Действия') await assertText(cdp, 'Deploy smoke', 'actions: action card visible') await clickText(cdp, '.tab', 'Журнал') await assertText(cdp, 'Manual smoke entry', 'worklog: entry visible') + await clickText(cdp, '.worklog-toolbar .btn', 'Добавить запись') + await waitForSelector(cdp, '.modal-worklog') + await setInputValue(cdp, '.modal-worklog input[type="text"]', 'GUI smoke worklog') + await setInputValue(cdp, '.modal-worklog input[type="number"]', '15') + await clickText(cdp, '.modal-worklog .btn', 'Сохранить') + await waitForGone(cdp, '.modal-worklog') + await assertText(cdp, 'GUI smoke worklog', 'worklog: created entry appears') await clickText(cdp, '.tab', 'Активность') await assertText(cdp, 'Smoke activity', 'activity: per-node activity visible') @@ -173,9 +187,12 @@ async function runReadyScenario(cdp, url) { await click(cdp, '.nav-add-btn') await waitForSelector(cdp, '.modal-create') await assertText(cdp, 'Создать элемент', 'create node: modal opens') + await clickText(cdp, '.template-card', 'Пустое дело') + await setInputValue(cdp, '.modal-create input[type="text"]', 'GUI Smoke Created') await screenshot(cdp, 'create-node-modal.png') - await clickText(cdp, '.modal-actions .btn', 'Отмена') + await clickText(cdp, '.modal-actions .btn', 'Создать') await waitForGone(cdp, '.modal-create') + await assertText(cdp, 'GUI Smoke Created', 'create node: created node appears') await setViewport(cdp, 390, 844) await navigate(cdp, url) @@ -263,6 +280,22 @@ async function clickText(cdp, selector, text) { await sleep(200) } +async function setInputValue(cdp, selector, value) { + const ok = await evalValue(cdp, ` + (() => { + const el = document.querySelector(${JSON.stringify(selector)}); + if (!el) return false; + el.focus(); + el.value = ${JSON.stringify(value)}; + el.dispatchEvent(new Event('input', { bubbles: true })); + el.dispatchEvent(new Event('change', { bubbles: true })); + return true; + })() + `) + if (!ok) throw new Error(`Input target not found: ${selector}`) + await sleep(100) +} + async function clickFolderOpenButton(cdp, name) { const ok = await evalValue(cdp, ` (() => {