feat(web-test): closeForm закрывает форму каскадом, кнопка подтверждения по смыслу

closeForm умел только Escape. На реальном стенде Escape не закрывает ни
модальную форму, ни даже обычный список — форма оставалась открытой, и
resetState считал контекст грязным.

- Каскад закрытия (closeCrossScript в dom/forms.mjs): Escape → крестик
  плавающего окна (ps<N>, модалка поверх формы) → крестик страницы формы
  (VW_page<N>, работает и при скрытой панели вкладок) → крестик активной
  вкладки (класс select, не первый попавшийся .openedClose). Порядок и якоря
  замерены живьём. Крестик формы бьёт вкладочный, потому что панель открытых
  может быть выключена настройкой.
- nothingToClose: когда Escape не помог и крестика нет нигде — платформа сама
  говорит, что поверхность не закрывается, то есть это рабочий стол. Этот сигнал
  и питает «чисто» в resetState, без базового снимка и списков-исключений.
- Семантика подтверждения по смыслу вопроса (pickConfirmationLabel): 1С теми же
  кнопками «Да/Нет» задаёт разные вопросы. «Сохранить изменения?» → save?Да:Нет
  (как было); «Закрыть согласование?» → Да=закрыть (иначе save:false жал «Нет» =
  «остаться», и модалка утекала в следующий тест). Решение по вопросительному
  предложению; неизвестная формулировка → прежнее поведение.
- Ответственность DOM: поиск крестика вынесен в dom/forms.mjs (генератор скрипта
  для page.evaluate), close.mjs его только зовёт. Диагностика приведена к
  английскому, как остальной модуль; русским остаётся лишь цитата платформы.

Проверено на свежей базе пилота: модалка «Комментарий и согласование» →
closeForm({save:false}) = closed:true, answered:Да, viaCross:true, formCount 2→1
(критерий пилота); стопка документ→список→стол разбирается насквозь и
останавливается на столе. Полный регресс 25/25, closeForm зовёт каждый тест.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-16 11:15:32 +03:00
parent 8ccd562625
commit bb98d3c240
3 changed files with 101 additions and 13 deletions
+39 -1
View File
@@ -1,4 +1,4 @@
// web-test dom/forms v1.7 — form detection, content read, click-target/field-button resolution
// web-test dom/forms v1.9 — form detection, content read, click-target/field-button resolution
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { DETECT_FORM_FN, READ_FORM_FN, ROW_CLICK_POINT_FN } from './_shared.mjs';
@@ -13,6 +13,44 @@ export function detectFormScript() {
})()`;
}
/**
* Id of the cross that closes the topmost closable surface, or null when there is nothing to
* close — which is exactly how the platform says "this is the desktop": the home page has no
* cross at all. That `null` is the reset-between-tests "clean" signal; no baseline snapshot and
* no exception list needed. Measured on a live stand:
* desktop, 1 form → form=0 formCount=1, no cross
* desktop, 3 forms → form=5 formCount=3 openForms=[5,6,7], no cross
* list open → cross VW_page2headerTopLine_cmd_CloseButton
*
* The id must leak out for the caller to click — Escape is not enough: on a real stand it closed
* neither a modal nor even a plain list.
*
* Order matters, and each step was measured:
* 1. floating window (`ps<N>`) — a modal sits ON TOP of the form that opened it, so its cross
* wins. The index grows per window (ps0 → ps1 → …): a literal `ps0` breaks on the second
* modal of a session.
* 2. the form's own page cross — works whether or not the tab panel is switched on, which is
* why it beats the tab cross. With the panel hidden it is the ONLY path.
* 3. the ACTIVE tab's cross — last resort. `select` marks the active tab (same signal as
* dom/form-state.mjs); NOT `querySelector('.openedClose')` like closeModals does — that
* grabs the first tab in the DOM and would close someone else's form.
* Anchored on ids rather than title="Закрыть", so a non-Russian locale keeps working.
*/
export function closeCrossScript() {
return `(() => {
const vis = (e) => e.offsetWidth > 0;
const crosses = [...document.querySelectorAll('[id*="headerTopLine_cmd_CloseButton"]')].filter(vis);
const floating = crosses.filter(e => /ps\\d+headerTopLine_cmd_CloseButton$/.test(e.id));
if (floating.length) return floating.pop().id;
const page = crosses.filter(e => /^VW_page\\d+headerTopLine_cmd_CloseButton$/.test(e.id));
if (page.length) return page.pop().id;
const tab = [...document.querySelectorAll('[id^="openedCell_cmd_"]')]
.find(e => e.classList.contains('select') && vis(e));
const tabCross = tab && tab.querySelector('.openedClose');
return (tabCross && vis(tabCross)) ? tabCross.id : null;
})()`;
}
/**
* Read full form state for a given form number.
* Uses shared READ_FORM_FN.