From 85191dc7595d9272e3a10fcbaa8e307b815cff1b Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 12 Mar 2026 15:15:16 +0300 Subject: [PATCH] fix(web-test): prioritize groups over buttons in highlight() search order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group names often collide with command bar buttons (e.g. "БизнесПроцессы" matched a tiny 38x35 button instead of the 959x580 panel). Move group search before button/field search with min-size filter (100x50). Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-test/recording.md | 2 +- .claude/skills/web-test/scripts/browser.mjs | 69 +++++++++++---------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/.claude/skills/web-test/recording.md b/.claude/skills/web-test/recording.md index 8cd933de..60cdd5ab 100644 --- a/.claude/skills/web-test/recording.md +++ b/.claude/skills/web-test/recording.md @@ -140,7 +140,7 @@ Manually highlight a UI element by name (fuzzy match). Places a semi-transparent | `text` | string | Element name — button, link, field, group/panel, section, or command | - Fuzzy match order: exact → startsWith → includes -- Search priority: popup items → commands → form elements (buttons, fields) → **form groups/panels** → sections +- Search priority: popup items → commands → **form groups/panels** → form elements (buttons, fields) → sections - Groups are matched by visible title or internal name (e.g., `highlight('Оргструктура')` finds the group panel) - `pointer-events: none` — does not block clicks diff --git a/.claude/skills/web-test/scripts/browser.mjs b/.claude/skills/web-test/scripts/browser.mjs index fa4011d1..76557f58 100644 --- a/.claude/skills/web-test/scripts/browser.mjs +++ b/.claude/skills/web-test/scripts/browser.mjs @@ -3536,11 +3536,44 @@ export async function highlight(text, opts = {}) { })()`); } - // 2. Form-scoped search (buttons, links, fields, grid rows) + // 2. Form groups/panels — checked BEFORE buttons/fields because group names + // often collide with command bar buttons (e.g. "БизнесПроцессы" is both a + // panel and a command bar element). Groups are large visual containers; + // min-area filter (100x50) prevents matching small elements. if (!elId) { const formNum = await page.evaluate(detectFormScript()); if (formNum !== null) { - // 1a. Try button/link/tab/gridRow via findClickTargetScript + elId = await page.evaluate(`(() => { + const norm = s => (s?.trim().replace(/\\u00a0/g, ' ') || '').replace(/ё/gi, 'е'); + const target = ${JSON.stringify(normYo(text.toLowerCase()))}; + const p = 'form' + ${formNum} + '_'; + // Collect visible group containers — _container or _div elements (min 100x50 to skip command bars) + const groups = [...document.querySelectorAll('[id^="' + p + '"][id$="_container"], [id^="' + p + '"][id$="_div"]')] + .filter(el => el.offsetWidth >= 100 && el.offsetHeight >= 50); + const items = groups.map(el => { + const idName = el.id.replace(p, '').replace(/_(container|div)$/, ''); + // Try to find a visible title/label for this group + const titleEl = document.getElementById(p + idName + '#title_text') + || document.getElementById(p + idName + '_title_text'); + const label = norm(titleEl?.innerText || '').toLowerCase(); + const name = norm(idName).toLowerCase(); + return { id: el.id, name, label }; + }); + // Fuzzy match: exact label → exact name → startsWith → includes + let found = items.find(i => i.label === target); + if (!found) found = items.find(i => i.name === target); + if (!found) found = items.find(i => i.label.startsWith(target) || i.name.startsWith(target)); + if (!found) found = items.find(i => i.label.includes(target) || i.name.includes(target)); + return found ? found.id : null; + })()`); + } + } + + // 3. Form-scoped search (buttons, links, fields, grid rows) + if (!elId) { + const formNum = await page.evaluate(detectFormScript()); + if (formNum !== null) { + // 3a. Try button/link/tab/gridRow via findClickTargetScript const target = await page.evaluate(findClickTargetScript(formNum, text)); if (target && !target.error) { if (target.id) { @@ -3568,7 +3601,7 @@ export async function highlight(text, opts = {}) { } } - // 1b. If not found as button — try as field via resolveFieldsScript + // 3b. If not found as button — try as field via resolveFieldsScript if (!elId) { const dummyFields = { [text]: '' }; const resolved = await page.evaluate(resolveFieldsScript(formNum, dummyFields)); @@ -3579,36 +3612,6 @@ export async function highlight(text, opts = {}) { } } - // 3. Form groups/panels (containers with title text or matching ID name) - if (!elId) { - const formNum = elId === null ? await page.evaluate(detectFormScript()) : null; - if (formNum !== null) { - elId = await page.evaluate(`(() => { - const norm = s => (s?.trim().replace(/\\u00a0/g, ' ') || '').replace(/ё/gi, 'е'); - const target = ${JSON.stringify(normYo(text.toLowerCase()))}; - const p = 'form' + ${formNum} + '_'; - // Collect visible group containers — _container or _div elements - const groups = [...document.querySelectorAll('[id^="' + p + '"][id$="_container"], [id^="' + p + '"][id$="_div"]')] - .filter(el => el.offsetWidth > 0 && el.offsetHeight > 0); - const items = groups.map(el => { - const idName = el.id.replace(p, '').replace(/_(container|div)$/, ''); - // Try to find a visible title/label for this group - const titleEl = document.getElementById(p + idName + '#title_text') - || document.getElementById(p + idName + '_title_text'); - const label = norm(titleEl?.innerText || '').toLowerCase(); - const name = norm(idName).toLowerCase(); - return { id: el.id, name, label }; - }); - // Fuzzy match: exact label → exact name → startsWith → includes - let found = items.find(i => i.label === target); - if (!found) found = items.find(i => i.name === target); - if (!found) found = items.find(i => i.label.startsWith(target) || i.name.startsWith(target)); - if (!found) found = items.find(i => i.label.includes(target) || i.name.includes(target)); - return found ? found.id : null; - })()`); - } - } - // 4. Fallback: sections (sidebar navigation) if (!elId) { elId = await page.evaluate(`(() => {