fix(web-test): improve modal detection, surface retry, search regex

- detectFormScript: lower modal threshold from >=2 to >=1 visible elements
- clickElement: force:true on third retry when surface overlay persists
- filterList/unfilterList: add SearchString pattern for selection forms
- fillTableRow: wrap body in try/catch for structured error returns
- SKILL.md: add keyboard shortcuts reference (F8, Shift+F4, F4, Alt+F)
- gitignore: exclude *.png screenshot artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-02-27 18:31:57 +03:00
co-authored by Claude Opus 4.6
parent c8f58b5461
commit 68e346d645
4 changed files with 33 additions and 6 deletions
+9
View File
@@ -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
+18 -3
View File
@@ -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;
})()`);
+2 -2
View File
@@ -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 });
}
+4 -1
View File
@@ -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