fix(web-test): состояние группы по контролу сворачивания, а не по вёрстке содержимого

На боевой форме вскрылись случаи, которые прежнее правило не покрывало. Замеры показали,
что любая эвристика «по первому сиблингу за заголовком» нежизнеспособна:

- служебная обёртка .logicGroupContainer пишется двумя способами (у таблицы
  <дочерний>#group_div, у вложенной группы <дочерний>_div) — знали только первый;
- display самой обёртки плавает: block до первого тогла, none после;
- первые узлы группы могут быть скрыты своей логикой, а видимое содержимое идёт дальше
  по цепочке — раскрытая группа читалась как свёрнутая, и постусловие роняло успешный клик.

Теперь состояние берётся из контрола сворачивания: у варианта «картинка» — кадр gx спрайта
hideshow у каретки (полярность обратна дереву в dom/grid.mjs), у варианта «гиперссылка»
каретки нет, там принадлежность по отступу — дети группы смещены глубже её заголовка,
свободный сосед стоит на уровне заголовка. База отсчёта — левый край блока заголовка
(каретка сдвигает текст вправо), обход ограничен: у последней свёрнутой группы границы за
ней нет, и первый видимый узел нашёлся через 107 сиблингов в чужой ветке формы.

Клик по заголовку группы теперь сперва скроллит цель в вид: цель кликается по координатам,
и ниже вьюпорта клик молча не доходил.

Ответ на клик отдаёт clicked.group (техническое имя) и clicked.title (текущий заголовок):
заголовок ключом быть не может — он повторяется между блоками формы и меняется при
раскрытии (CollapsedRepresentationTitle), после чего клик по прежнему тексту не находит
элемент. При смене заголовка hint говорит, чем кликать дальше.

Фикстура: вложенная группа первым ребёнком, «скрыт первый узел» в двух вариантах контрола,
группа с меняющимся заголовком в конце формы (уезжает за вьюпорт). Каждый кейс проверен на
красноту без своей правки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-28 16:12:07 +03:00
co-authored by Claude Opus 5
parent 006e64405c
commit d0e81a1715
7 changed files with 257 additions and 45 deletions
@@ -324,6 +324,39 @@ export function findClickTargetScript(formNum, text, { tableName, gridSelector }
})()`;
}
/**
* Scroll a collapsible group's title into view and return the FRESH click point for it.
*
* A group's title is clicked by coordinates (its click target is a nested label, not the
* element with the id), and `mouse.click` outside the viewport lands nowhere — the toggle
* silently did nothing. Seen live: the second «Показать детализацию» on a long form sat at
* y=848 with a viewport of 834. Playwright's own auto-scroll does not apply here because
* that path is selector-based, not coordinate-based.
*
* Same target choice as findClickTargetScript: the caret `#titleBtn` when visible (compact,
* aim at its centre), otherwise the title text via textClickPoint.
*
* @returns `{ x, y }` after scrolling, or `null` when the group is not on the form.
*/
export function scrollGroupIntoViewScript(formNum, groupName) {
const p = `form${formNum}_`;
return `(() => {
${TEXT_CLICK_POINT_FN}
const base = ${JSON.stringify(p)} + ${JSON.stringify(groupName)};
const tt = document.getElementById(base + '#title_text');
const btn = document.getElementById(base + '#titleBtn');
const btnVisible = btn && (btn.offsetWidth > 0 || btn.offsetHeight > 0);
const anchor = btnVisible ? btn : tt;
if (!anchor) return null;
anchor.scrollIntoView({ block: 'center' });
if (btnVisible) {
const r = btn.getBoundingClientRect();
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) };
}
return textClickPoint(tt);
})()`;
}
/**
* Find a field's action button (DLB, OB, CLR, CB) by fuzzy field name.
* Returns { fieldName, buttonId, buttonType } or { error, available }.