From 5b6243bbccdea7b412d107748e9b5f52a3d707ac Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 25 May 2026 22:31:45 +0300 Subject: [PATCH] =?UTF-8?q?refactor(web-test):=20=D1=8D=D1=82=D0=B0=D0=BF?= =?UTF-8?q?=20B.5.1=20=E2=80=94=20safeClick=20=D1=85=D0=B5=D0=BB=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=203=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BF=D0=B8=D0=B9=20pointer-events=20retry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit В fillReferenceField, clickElement и DLB-ветке selectValue был один и тот же паттерн: page.click → catch 'intercepts pointer events' → force-click → catch снова → Escape + retry. Три копии (плюс одна с dismissPendingErrors). core/helpers.mjs (новый): safeClick(selector, { timeout, dismissErrors }). Экономия ~60 LOC дублей. Поведение 1-в-1 (dismissErrors:true только в fillReferenceField — там единственное место, где исходно было). Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/skills/web-test/scripts/browser.mjs | 57 ++----------------- .../skills/web-test/scripts/core/helpers.mjs | 36 ++++++++++++ 2 files changed, 40 insertions(+), 53 deletions(-) create mode 100644 .claude/skills/web-test/scripts/core/helpers.mjs diff --git a/.claude/skills/web-test/scripts/browser.mjs b/.claude/skills/web-test/scripts/browser.mjs index 6abfd02e..159d5e15 100644 --- a/.claude/skills/web-test/scripts/browser.mjs +++ b/.claude/skills/web-test/scripts/browser.mjs @@ -142,6 +142,7 @@ import { closeModals, checkForErrors, dismissPendingErrors, fetchErrorStack, _detectPlatformDialogs, _closePlatformDialogs, } from './core/errors.mjs'; +import { safeClick } from './core/helpers.mjs'; // Re-export only what was publicly exported before the refactor. // waitForStable/waitForCondition/startNetworkMonitor/closeModals/checkForErrors/ // dismissPendingErrors are internal helpers — imported above for local use only. @@ -1502,23 +1503,7 @@ async function fillReferenceField(selector, fieldName, value, formNum) { } catch { /* DLB approach failed — fall through to paste */ } // 1. Focus (handle surface/modal overlay from previous interaction) - try { - await page.click(selector); - } catch (e) { - if (e.message.includes('intercepts pointer events')) { - // Try force click first (no side effects), then Escape as fallback - try { - await page.click(selector, { force: true }); - } catch (e2) { - if (e2.message.includes('intercepts pointer events')) { - await dismissPendingErrors(); - await page.keyboard.press('Escape'); - await page.waitForTimeout(500); - await page.click(selector); - } else throw e2; - } - } else throw e; - } + await safeClick(selector, { dismissErrors: true }); // 2. If field already has a value, clear using Shift+F4 (native 1C mechanism). // This is needed for reference fields — Shift+F4 properly clears the ref link. @@ -2064,27 +2049,7 @@ export async function clickElement(text, { dblclick, table, toggle, expand, modi } else { const selector = `[id="${target.id}"]`; // Use Playwright click for proper mousedown/mouseup events - try { - await page.click(selector, { timeout: 5000 }); - } catch (clickErr) { - if (clickErr.message.includes('intercepts pointer events')) { - // Surface overlay intercepts — try force click first (no side effects), - // then Escape + retry as fallback (Escape can trigger save dialogs on forms) - try { - await page.click(selector, { force: true, timeout: 5000 }); - } catch (clickErr2) { - if (clickErr2.message.includes('intercepts pointer events')) { - await page.keyboard.press('Escape'); - await page.waitForTimeout(500); - await page.click(selector, { timeout: 5000 }); - } else { - throw clickErr2; - } - } - } else { - throw clickErr; - } - } + await safeClick(selector, { timeout: 5000 }); } // If submenu button — read popup items and return them as hints @@ -2427,21 +2392,7 @@ export async function selectValue(fieldName, searchText, { type } = {}) { // 2. Click DLB (handle funcPanel / surface overlay intercept) const dlbSel = `[id="${btn.buttonId}"]`; - try { - await page.click(dlbSel, { timeout: 5000 }); - } catch (dlbErr) { - if (dlbErr.message.includes('intercepts pointer events')) { - try { - await page.click(dlbSel, { force: true, timeout: 5000 }); - } catch (dlbErr2) { - if (dlbErr2.message.includes('intercepts pointer events')) { - await page.keyboard.press('Escape'); - await page.waitForTimeout(500); - await page.click(dlbSel, { timeout: 5000 }); - } else throw dlbErr2; - } - } else throw dlbErr; - } + await safeClick(dlbSel, { timeout: 5000 }); await page.waitForTimeout(ACTION_WAIT); // 3A. Check if a dropdown popup appeared (inline quick selection) diff --git a/.claude/skills/web-test/scripts/core/helpers.mjs b/.claude/skills/web-test/scripts/core/helpers.mjs new file mode 100644 index 00000000..4e138e2f --- /dev/null +++ b/.claude/skills/web-test/scripts/core/helpers.mjs @@ -0,0 +1,36 @@ +// web-test core/helpers v1.16 — private, cross-cutting helpers used by the +// public action functions (clickElement/fillFields/selectValue/etc). +// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills + +import { page } from './state.mjs'; +import { dismissPendingErrors } from './errors.mjs'; + +/** + * page.click with the standard "intercepts pointer events" retry ladder: + * normal → force → Escape (+ optional dismissPendingErrors) → normal. + * Mirrors the three hand-written copies in fillReferenceField, clickElement + * and the DLB branch of selectValue. + * + * @param {string} selector + * @param {object} [opts] + * @param {number} [opts.timeout] — passed through to page.click + * @param {boolean} [opts.dismissErrors=false] — call dismissPendingErrors() + * before pressing Escape on the second retry (used in fillReferenceField). + */ +export async function safeClick(selector, { timeout, dismissErrors = false } = {}) { + const baseOpts = timeout != null ? { timeout } : {}; + try { + await page.click(selector, baseOpts); + } catch (e) { + if (!e.message.includes('intercepts pointer events')) throw e; + try { + await page.click(selector, { ...baseOpts, force: true }); + } catch (e2) { + if (!e2.message.includes('intercepts pointer events')) throw e2; + if (dismissErrors) await dismissPendingErrors(); + await page.keyboard.press('Escape'); + await page.waitForTimeout(500); + await page.click(selector, baseOpts); + } + } +}