diff --git a/.claude/skills/web-test/SKILL.md b/.claude/skills/web-test/SKILL.md index 34477ad4..c674872d 100644 --- a/.claude/skills/web-test/SKILL.md +++ b/.claude/skills/web-test/SKILL.md @@ -202,6 +202,15 @@ Hint: if `readTable()` returns `hierarchical: true`, the list has groups. `closeForm()` is preferred over `clickElement('×')` — close buttons on tabs are ambiguous. +### Keyboard shortcuts + +| Key | Context | Action | +|-----|---------|--------| +| `F8` | Reference field focused | Open creation form for the field's catalog | +| `Shift+F4` | Reference field focused | Clear field value | +| `F4` | Reference field focused | Open selection form | +| `Alt+F` | List/table form | Open advanced search dialog | + ### Submenu navigation ```js diff --git a/.claude/skills/web-test/scripts/browser.mjs b/.claude/skills/web-test/scripts/browser.mjs index 256afdf0..57f124cc 100644 --- a/.claude/skills/web-test/scripts/browser.mjs +++ b/.claude/skills/web-test/scripts/browser.mjs @@ -916,7 +916,16 @@ export async function clickElement(text, { dblclick } = {}) { if (clickErr.message.includes('intercepts pointer events')) { await page.keyboard.press('Escape'); await page.waitForTimeout(500); - await page.click(selector, { timeout: 5000 }); + try { + await page.click(selector, { timeout: 5000 }); + } catch (clickErr2) { + if (clickErr2.message.includes('intercepts pointer events')) { + // Persistent surface (e.g. grid editing overlay) — force click + await page.click(selector, { force: true, timeout: 5000 }); + } else { + throw clickErr2; + } + } } else { throw clickErr; } @@ -1204,6 +1213,7 @@ export async function fillTableRow(fields, { tab, add, row } = {}) { const formNum = await page.evaluate(detectFormScript()); if (formNum === null) return { error: 'no_form' }; + try { // 1. Switch tab if requested if (tab) { await clickElement(tab); @@ -1548,6 +1558,11 @@ export async function fillTableRow(fields, { tab, add, row } = {}) { if (notFilled.length > 0) result.notFilled = notFilled; result.form = formData; return result; + + } catch (e) { + const form = await getFormState().catch(() => null); + return { error: 'fillTableRow_failed', message: e.message, form }; + } } /** @@ -1636,7 +1651,7 @@ export async function filterList(text, { field, exact } = {}) { const searchId = await page.evaluate(`(() => { const p = 'form${formNum}_'; const el = [...document.querySelectorAll('input.editInput[id^="' + p + '"]')] - .find(el => el.offsetWidth > 0 && /Строк[аи]Поиска/i.test(el.id)); + .find(el => el.offsetWidth > 0 && /Строк[аи]Поиска|SearchString/i.test(el.id)); return el ? el.id : null; })()`); if (!searchId) return { error: 'no_search_field', message: 'No search input found on this form' }; @@ -1947,7 +1962,7 @@ export async function unfilterList({ field } = {}) { const searchInfo = await page.evaluate(`(() => { const p = 'form${formNum}_'; const el = [...document.querySelectorAll('input.editInput[id^="' + p + '"]')] - .find(el => el.offsetWidth > 0 && /Строк[аи]Поиска/i.test(el.id)); + .find(el => el.offsetWidth > 0 && /Строк[аи]Поиска|SearchString/i.test(el.id)); return el ? { id: el.id, value: el.value || '' } : null; })()`); diff --git a/.claude/skills/web-test/scripts/dom.mjs b/.claude/skills/web-test/scripts/dom.mjs index 0298fe52..7b56de91 100644 --- a/.claude/skills/web-test/scripts/dom.mjs +++ b/.claude/skills/web-test/scripts/dom.mjs @@ -25,7 +25,7 @@ const DETECT_FORM_FN = `function detectForm() { const modal = document.getElementById('modalSurface'); if (modal && modal.offsetWidth > 0) { const maxForm = Math.max(...candidates); - if (counts[maxForm] >= 2) return maxForm; + if (counts[maxForm] >= 1) return maxForm; } return candidates.reduce((best, n) => counts[n] > counts[best] ? n : best); }`; @@ -214,7 +214,7 @@ const READ_FORM_FN = `function readForm(p) { }); // Also check search field value const searchInput = [...document.querySelectorAll('input.editInput[id^="' + p + '"]')] - .find(el => el.offsetWidth > 0 && /Строк[аи]Поиска/i.test(el.id)); + .find(el => el.offsetWidth > 0 && /Строк[аи]Поиска|SearchString/i.test(el.id)); if (searchInput?.value) { filters.push({ type: 'search', value: searchInput.value }); } diff --git a/.gitignore b/.gitignore index 53ba1080..63a05ecb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ tools/ # Локальный реестр баз данных 1С .v8-project.json -# web-test: Node.js зависимости и runtime +# web-test: Node.js зависимости и runtime-артефакты .claude/skills/web-test/scripts/node_modules/ .claude/skills/web-test/.browser-session.json + +# Скриншоты (артефакты тестирования web-test) +*.png