From 4af69f16002e68cdb0b28dd9f88bec1cb14c0c51 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 11 May 2026 15:21:11 +0300 Subject: [PATCH] =?UTF-8?q?test(web-test):=20M4.A=20=E2=80=94=20validation?= =?UTF-8?q?=20messages=20+=20exception=20modal=20+=20error=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 10-validation.test.mjs (3 шага): Сообщить() → state.errors.messages, ВызватьИсключение → onecError.errors.modal с автоматическим закрытием fetchErrorStack. 14-errors-stack.test.mjs (3 шага): Path 1 OpenReport автоматически фетчит стек для серверных исключений (entries[] содержит кадр ОбщиеФункции); оставленная error modal через raw page.click закрывается closeForm; платформенный диалог «О программе» виден в state.platformDialogs и закрывается closeForm. Покрыто 4 P2-кейса coverage matrix: 10-validation/messages, 10-validation/exception-modal, 14-errors/path1, 14-errors/dismiss-platform + бонус dismiss-modal. Открытие обработки ТестовыеОшибки через navigateLink('Обработка.ТестовыеОшибки') — стандартные команды у DataProcessor отключены. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/web-test/10-validation.test.mjs | 43 ++++++++++++++ tests/web-test/14-errors-stack.test.mjs | 74 +++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tests/web-test/10-validation.test.mjs create mode 100644 tests/web-test/14-errors-stack.test.mjs diff --git a/tests/web-test/10-validation.test.mjs b/tests/web-test/10-validation.test.mjs new file mode 100644 index 00000000..d2ad8207 --- /dev/null +++ b/tests/web-test/10-validation.test.mjs @@ -0,0 +1,43 @@ +export const name = 'validation: messages panel + exception modal'; +export const tags = ['validation', 'errors']; +export const timeout = 60000; + +export default async function({ navigateLink, clickElement, closeForm, getFormState, assert, step, log }) { + + await step('open: обработка ТестовыеОшибки доступна через navigateLink', async () => { + const s = await navigateLink('Обработка.ТестовыеОшибки'); + log(`buttons: ${s.buttons?.map(b => b.name).join(', ')}`); + assert.ok(s.buttons?.some(b => b.name === 'Показать сообщение'), 'кнопка «Показать сообщение»'); + assert.ok(s.buttons?.some(b => b.name === 'Вызвать исключение'), 'кнопка «Вызвать исключение»'); + }); + + await step('messages: Сообщить() показывает текст в панели Сообщения', async () => { + const r = await clickElement('Показать сообщение'); + log(`errors.messages: ${JSON.stringify(r.errors?.messages)}`); + assert.ok(Array.isArray(r.errors?.messages), 'errors.messages — массив'); + assert.ok(r.errors.messages.includes('Тестовое сообщение'), 'содержит «Тестовое сообщение»'); + assert.ok(!r.errors.modal, 'модальной ошибки нет — это инфо-панель'); + }); + + await step('exception-modal: ВызватьИсключение приводит к onecError.errors.modal', async () => { + let caught = null; + try { + await clickElement('Вызвать исключение'); + } catch (e) { + caught = e; + } + assert.ok(caught, 'clickElement должен бросить ошибку при платформенном исключении'); + assert.equal(caught.message, 'Тестовое исключение', 'e.message = текст исключения'); + const modal = caught.onecError?.errors?.modal; + log(`modal: ${JSON.stringify(modal)}`); + assert.ok(modal, 'onecError.errors.modal присутствует'); + assert.equal(modal.message, 'Тестовое исключение', 'modal.message'); + assert.ok(typeof modal.formNum === 'number', 'modal.formNum — число'); + // После throw fetchErrorStack автоматически закрыл модалку — проверим + const after = await getFormState(); + assert.ok(!after.errors?.modal, 'модалка автоматически закрыта'); + assert.ok(!after.platformDialogs?.length, 'платформенные диалоги не оставлены'); + }); + + await closeForm(); +} diff --git a/tests/web-test/14-errors-stack.test.mjs b/tests/web-test/14-errors-stack.test.mjs new file mode 100644 index 00000000..92ab8b34 --- /dev/null +++ b/tests/web-test/14-errors-stack.test.mjs @@ -0,0 +1,74 @@ +export const name = 'errors: fetchErrorStack Path 1 + dismiss platform dialogs'; +export const tags = ['errors', 'stack']; +export const timeout = 60000; + +export default async function({ navigateLink, clickElement, closeForm, getFormState, getPage, assert, step, log }) { + + await step('path1: серверное ВызватьИсключение → автоматически фетчится стек через OpenReport', async () => { + await navigateLink('Обработка.ТестовыеОшибки'); + let caught = null; + try { + await clickElement('Вызвать исключение'); + } catch (e) { + caught = e; + } + assert.ok(caught, 'исключение брошено'); + const stack = caught.onecError?.stack; + log(`stack entries: ${stack?.entries?.length}`); + assert.ok(stack, 'onecError.stack присутствует'); + assert.ok(stack.timestamp, 'stack.timestamp'); + assert.ok(Array.isArray(stack.entries) && stack.entries.length >= 1, 'stack.entries — непустой массив'); + const root = stack.entries.find(e => /ОбщиеФункции/.test(e.location)); + assert.ok(root, 'в стеке есть кадр из ОбщегоМодуля ОбщиеФункции'); + assert.match(root.code, /ВызватьИсключение/, 'кадр содержит строку с ВызватьИсключение'); + }); + + await step('dismiss-modal: оставленная error modal видна в state и закрывается closeForm', async () => { + // Поток внутри wrapper'a clickElement автоматически зовёт fetchErrorStack и + // закрывает модалку. Чтобы получить «висящую» модалку — кликаем напрямую + // через page.click, минуя wrapper. + await navigateLink('Обработка.ТестовыеОшибки'); + const page = await getPage(); + const btnId = await page.evaluate(() => { + const el = document.querySelector('[id$="ВызватьИсключение_div"]'); + return el && el.offsetWidth > 0 ? el.id : null; + }); + assert.ok(btnId, 'кнопка «Вызвать исключение» найдена в DOM'); + await page.click('#' + btnId); + await page.waitForTimeout(2500); + + const withModal = await getFormState(); + log(`modal present: ${JSON.stringify(withModal.errors?.modal)}`); + assert.equal(withModal.modal, true, 'state.modal=true пока модалка открыта'); + assert.ok(withModal.errors?.modal, 'state.errors.modal присутствует'); + assert.equal(withModal.errors.modal.message, 'Тестовое исключение', 'modal.message'); + + await closeForm(); + const after = await getFormState(); + log(`after closeForm — modal: ${JSON.stringify(after.errors?.modal)} form: ${after.form}`); + assert.ok(!after.errors?.modal, 'модалка закрыта'); + assert.ok(!after.modal, 'state.modal не true'); + }); + + await step('dismiss-platform: открытый «О программе» виден в state.platformDialogs и закрывается closeForm', async () => { + // Форма ТестовыеОшибки осталась открытой после предыдущего шага (модалка ушла сама) + const page = await getPage(); + await page.click('#captionbarMore'); + await page.waitForTimeout(800); + await page.getByText('О программе...', { exact: true }).click(); + await page.waitForTimeout(1500); + + const before = await getFormState(); + log(`platformDialogs: ${JSON.stringify(before.platformDialogs)}`); + assert.ok(Array.isArray(before.platformDialogs) && before.platformDialogs.length === 1, + 'state.platformDialogs — массив с одним элементом'); + assert.equal(before.platformDialogs[0].type, 'about', 'тип = about'); + + await closeForm(); + const after = await getFormState(); + log(`platformDialogs after closeForm: ${after.platformDialogs?.length || 0}`); + assert.ok(!after.platformDialogs?.length, 'после closeForm нет platformDialogs'); + }); + + await closeForm(); +}