diff --git a/.claude/skills/web-test/scripts/dom.mjs b/.claude/skills/web-test/scripts/dom.mjs index f0238cf1..a404cb18 100644 --- a/.claude/skills/web-test/scripts/dom.mjs +++ b/.claude/skills/web-test/scripts/dom.mjs @@ -1,4 +1,4 @@ -// web-test dom v1.8 — facade re-exporting injectable DOM scripts from dom/ +// web-test dom v1.9 — facade re-exporting injectable DOM scripts from dom/ // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills /** * Facade: re-exports DOM selector & semantic mapping script generators. @@ -15,8 +15,15 @@ export { findClickTargetScript, findFieldButtonScript, resolveFieldsScript, + detectNewFormScript, } from './dom/forms.mjs'; +export { + isInputFocusedScript, + isInputFocusedInGridScript, + findOpenPopupScript, +} from './dom/edit-state.mjs'; + export { getFormStateScript } from './dom/form-state.mjs'; export { diff --git a/.claude/skills/web-test/scripts/dom/edit-state.mjs b/.claude/skills/web-test/scripts/dom/edit-state.mjs new file mode 100644 index 00000000..d5666950 --- /dev/null +++ b/.claude/skills/web-test/scripts/dom/edit-state.mjs @@ -0,0 +1,53 @@ +// web-test dom/edit-state v1.0 — focus and popup detection inside the 1C web client +// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills + +/** + * Is the currently focused element an INPUT (optionally TEXTAREA too)? + * Returns boolean. + * + * @param {object} [opts] + * @param {boolean} [opts.allowTextarea=false] — also return true for TEXTAREA. + */ +export function isInputFocusedScript({ allowTextarea = false } = {}) { + const cond = allowTextarea + ? `f.tagName === 'INPUT' || f.tagName === 'TEXTAREA'` + : `f.tagName === 'INPUT'`; + return `(() => { + const f = document.activeElement; + return !!(f && (${cond})); + })()`; +} + +/** + * Is the currently focused INPUT/TEXTAREA inside a `.grid` ancestor? + * Used to verify grid edit-mode (active cell editor). + * Returns boolean. + */ +export function isInputFocusedInGridScript() { + return `(() => { + const f = document.activeElement; + if (!f || (f.tagName !== 'INPUT' && f.tagName !== 'TEXTAREA')) return false; + let n = f; + while (n) { + if (n.classList?.contains('grid')) return true; + n = n.parentElement; + } + return false; + })()`; +} + +/** + * Is a calculator (`.calculate`) or calendar (`.frameCalendar`) popup visible? + * Returns `'calculator' | 'calendar' | null`. + * + * For the "popup gone" check, callers use: `!await findOpenPopup()`. + */ +export function findOpenPopupScript() { + return `(() => { + const calc = document.querySelector('.calculate'); + if (calc && calc.offsetWidth > 0) return 'calculator'; + const cal = document.querySelector('.frameCalendar'); + if (cal && cal.offsetWidth > 0) return 'calendar'; + return null; + })()`; +} diff --git a/.claude/skills/web-test/scripts/dom/forms.mjs b/.claude/skills/web-test/scripts/dom/forms.mjs index fcbe9af0..6216e2a9 100644 --- a/.claude/skills/web-test/scripts/dom/forms.mjs +++ b/.claude/skills/web-test/scripts/dom/forms.mjs @@ -1,4 +1,4 @@ -// web-test dom/forms v1.0 — form detection, content read, click-target/field-button resolution +// web-test dom/forms v1.1 — form detection, content read, click-target/field-button resolution // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import { DETECT_FORM_FN, READ_FORM_FN } from './_shared.mjs'; @@ -396,3 +396,29 @@ export function resolveFieldsScript(formNum, fields) { return results; })()`; } + +/** + * Detect a new form opened above `prevFormNum`. Two modes: + * default (broad) — counts any visible `[id]` element; finds dialogs whose + * `a.press` buttons have empty IDs. Used by selectValue / fillTableRow. + * `{ strict: true }` — only counts visible interactive elements + * (`input.editInput[id], a.press[id]`); used by fillReferenceField. + * + * Returns the highest new form number or `null`. + */ +export function detectNewFormScript(prevFormNum, { strict = false } = {}) { + const selector = strict ? 'input.editInput[id], a.press[id]' : '[id]'; + const visibleCheck = strict + ? 'el.offsetWidth === 0' + : 'el.offsetWidth === 0 && el.offsetHeight === 0'; + return `(() => { + const forms = {}; + document.querySelectorAll(${JSON.stringify(selector)}).forEach(el => { + if (${visibleCheck}) return; + const m = el.id.match(/^form(\\d+)_/); + if (m) forms[m[1]] = true; + }); + const nums = Object.keys(forms).map(Number).filter(n => n > ${prevFormNum}); + return nums.length > 0 ? Math.max(...nums) : null; + })()`; +} diff --git a/.claude/skills/web-test/scripts/engine/core/helpers.mjs b/.claude/skills/web-test/scripts/engine/core/helpers.mjs index e7d626f8..98344511 100644 --- a/.claude/skills/web-test/scripts/engine/core/helpers.mjs +++ b/.claude/skills/web-test/scripts/engine/core/helpers.mjs @@ -1,10 +1,16 @@ -// web-test core/helpers v1.17 — private, cross-cutting helpers used by the +// web-test core/helpers v1.18 — 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, checkForErrors } from './errors.mjs'; import { getFormState } from '../forms/state.mjs'; +import { + detectNewFormScript, + isInputFocusedScript, + isInputFocusedInGridScript, + findOpenPopupScript, +} from '../../dom.mjs'; /** * page.click with the standard "intercepts pointer events" retry ladder: @@ -69,20 +75,33 @@ export async function findFieldInputId(formNum, fieldName) { * @returns {Promise} new form number or null */ export async function detectNewForm(prevFormNum, { strict = false } = {}) { - const selector = strict ? 'input.editInput[id], a.press[id]' : '[id]'; - const visibleCheck = strict - ? 'el.offsetWidth === 0' - : 'el.offsetWidth === 0 && el.offsetHeight === 0'; - return page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll(${JSON.stringify(selector)}).forEach(el => { - if (${visibleCheck}) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${prevFormNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + return page.evaluate(detectNewFormScript(prevFormNum, { strict })); +} + +/** + * Thin wrapper: is the currently focused element an INPUT (or TEXTAREA)? + * + * @param {object} [opts] + * @param {boolean} [opts.allowTextarea=false] + */ +export async function isInputFocused({ allowTextarea = false } = {}) { + return page.evaluate(isInputFocusedScript({ allowTextarea })); +} + +/** + * Thin wrapper: is the currently focused INPUT/TEXTAREA inside a `.grid`? + * Used to verify grid edit-mode. + */ +export async function isInputFocusedInGrid() { + return page.evaluate(isInputFocusedInGridScript()); +} + +/** + * Thin wrapper: is calculator (`.calculate`) or calendar (`.frameCalendar`) + * popup visible? Returns `'calculator' | 'calendar' | null`. + */ +export async function findOpenPopup() { + return page.evaluate(findOpenPopupScript()); } /** diff --git a/.claude/skills/web-test/scripts/engine/forms/select-value.mjs b/.claude/skills/web-test/scripts/engine/forms/select-value.mjs index fa3c8ff4..6a82bcbc 100644 --- a/.claude/skills/web-test/scripts/engine/forms/select-value.mjs +++ b/.claude/skills/web-test/scripts/engine/forms/select-value.mjs @@ -14,8 +14,8 @@ import { highlight, unhighlight } from '../recording/highlight.mjs'; import { safeClick, findFieldInputId, readEdd, detectNewForm as helperDetectNewForm, -} from '../core/helpers.mjs'; -import { pasteText } from '../core/clipboard.mjs'; +} from '../core/helpers.mjs'; +import { pasteText } from '../core/clipboard.mjs'; import { getFormState } from './state.mjs'; /** @@ -757,18 +757,9 @@ export async function selectValue(fieldName, searchText, { type } = {}) { if (!isChecked) { await page.click(cbSel); await waitForStable(); } } - // Helper: detect selection form (form number > formNum) + // Helper: detect selection form (form number > formNum, strict mode) async function detectSelectionForm() { - return page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('input.editInput[id], a.press[id]').forEach(el => { - if (el.offsetWidth === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + return helperDetectNewForm(formNum, { strict: true }); } // detectNewForm is hoisted at the top of selectValue (see above). diff --git a/.claude/skills/web-test/scripts/engine/table/row-fill.mjs b/.claude/skills/web-test/scripts/engine/table/row-fill.mjs index 30c7c18f..eda8bf24 100644 --- a/.claude/skills/web-test/scripts/engine/table/row-fill.mjs +++ b/.claude/skills/web-test/scripts/engine/table/row-fill.mjs @@ -13,13 +13,14 @@ import { highlight, unhighlight } from '../recording/highlight.mjs'; import { safeClick, findFieldInputId, detectNewForm as helperDetectNewForm, + isInputFocused, isInputFocusedInGrid, findOpenPopup, } from '../core/helpers.mjs'; import { clickElement } from '../core/click.mjs'; import { pickFromSelectionForm, isTypeDialog, pickFromTypeDialog, fillReferenceField, selectValue, -} from '../forms/select-value.mjs'; -import { pasteText } from '../core/clipboard.mjs'; +} from '../forms/select-value.mjs'; +import { pasteText } from '../core/clipboard.mjs'; import { getFormState } from '../forms/state.mjs'; /** @@ -199,16 +200,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { if (firstVal0 === '') { await page.waitForTimeout(500); // Check if click opened a selection form — close it first - let openedForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + let openedForm = await helperDetectNewForm(formNum); if (openedForm !== null) { await page.keyboard.press('Escape'); await page.waitForTimeout(500); @@ -216,16 +208,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { // No form opened — need to enter edit mode first (dblclick), then close any form that opens await page.mouse.dblclick(cellCoords.x, cellCoords.y); await page.waitForTimeout(500); - openedForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + openedForm = await helperDetectNewForm(formNum); if (openedForm !== null) { await page.keyboard.press('Escape'); await page.waitForTimeout(500); @@ -279,21 +262,9 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { let directEditForm = null; for (let dw = 0; dw < 4; dw++) { await page.waitForTimeout(150); - inEdit = await page.evaluate(`(() => { - const f = document.activeElement; - return f && f.tagName === 'INPUT'; - })()`); + inEdit = await isInputFocused(); if (inEdit) break; - directEditForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + directEditForm = await helperDetectNewForm(formNum); if (directEditForm !== null) break; } // Click didn't enter edit — try dblclick (works for flat grids) @@ -301,21 +272,9 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await page.mouse.dblclick(cellCoords.x, cellCoords.y); for (let dw = 0; dw < 4; dw++) { await page.waitForTimeout(150); - inEdit = await page.evaluate(`(() => { - const f = document.activeElement; - return f && f.tagName === 'INPUT'; - })()`); + inEdit = await isInputFocused(); if (inEdit) break; - directEditForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + directEditForm = await helperDetectNewForm(formNum); if (directEditForm !== null) break; } } @@ -324,21 +283,9 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await page.keyboard.press('F4'); for (let fw = 0; fw < 8; fw++) { await page.waitForTimeout(200); - inEdit = await page.evaluate(`(() => { - const f = document.activeElement; - return f && f.tagName === 'INPUT'; - })()`); + inEdit = await isInputFocused(); if (inEdit) break; - directEditForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + directEditForm = await helperDetectNewForm(formNum); if (directEditForm !== null) break; } } @@ -356,16 +303,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await page.keyboard.press('F4'); for (let fw = 0; fw < 8; fw++) { await page.waitForTimeout(200); - directEditForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + directEditForm = await helperDetectNewForm(formNum); if (directEditForm !== null) break; } // If F4 didn't open a selection form, fall through to Tab loop @@ -394,16 +332,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await pickFromTypeDialog(selForm, info.type); await waitForStable(selForm); // After type selection, detect the actual selection form - selForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + selForm = await helperDetectNewForm(formNum); if (selForm === null) { return { field: key, error: 'no_selection_after_type', message: `Type selected but no selection form opened for "${key}"` }; } @@ -490,23 +419,9 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await page.mouse.dblclick(nextCoords.x, nextCoords.y); await page.waitForTimeout(300); // Check if dblclick entered INPUT mode (plain text/numeric field) — before F4 which may open calculator - const inInputAfterDblclick = await page.evaluate(`(() => { - const f = document.activeElement; - if (!f || (f.tagName !== 'INPUT' && f.tagName !== 'TEXTAREA')) return false; - let n = f; while (n) { if (n.classList?.contains('grid')) return true; n = n.parentElement; } - return false; - })()`); + const inInputAfterDblclick = await isInputFocusedInGrid(); // Also check if a selection form already appeared - let selForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + let selForm = await helperDetectNewForm(formNum); if (selForm === null && inInputAfterDblclick) { // Plain text/numeric field — fill via clipboard paste await pasteText(info.value, { confirm: ['Control+a', 'Control+v'] }); @@ -530,16 +445,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { if (attempt === 1) await page.keyboard.press('F4'); // F4 fallback for (let sw = 0; sw < 6; sw++) { await page.waitForTimeout(200); - selForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + selForm = await helperDetectNewForm(formNum); if (selForm !== null) break; } } @@ -744,44 +650,20 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { let typeForm = null; for (let tw = 0; tw < 6; tw++) { await page.waitForTimeout(200); - typeForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + typeForm = await helperDetectNewForm(formNum); if (typeForm !== null) break; } if (typeForm !== null && await isTypeDialog(typeForm)) { await pickFromTypeDialog(typeForm, info.type); await waitForStable(typeForm); // After type selection, check if a selection form opened (ref types) - const selForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + const selForm = await helperDetectNewForm(formNum); if (selForm === null) { // Primitive type — poll for calculator/calendar popup or settle on INPUT let hasPopup = null; for (let pw = 0; pw < 5; pw++) { await page.waitForTimeout(200); - hasPopup = await page.evaluate(`(() => { - const calc = document.querySelector('.calculate'); - if (calc && calc.offsetWidth > 0) return 'calculator'; - const cal = document.querySelector('.frameCalendar'); - if (cal && cal.offsetWidth > 0) return 'calendar'; - return null; - })()`); + hasPopup = await findOpenPopup(); if (hasPopup) break; } if (hasPopup) { @@ -789,21 +671,11 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { // Poll for popup to disappear for (let dw = 0; dw < 4; dw++) { await page.waitForTimeout(150); - const gone = await page.evaluate(`(() => { - const calc = document.querySelector('.calculate'); - if (calc && calc.offsetWidth > 0) return false; - const cal = document.querySelector('.frameCalendar'); - if (cal && cal.offsetWidth > 0) return false; - return true; - })()`); - if (gone) break; + if (!(await findOpenPopup())) break; } } // Ensure we are in an editable INPUT for this cell - const inInput = await page.evaluate(`(() => { - const f = document.activeElement; - return f && (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA'); - })()`); + const inInput = await isInputFocused({ allowTextarea: true }); if (!inInput) { const cellRect = await page.evaluate(`(() => { const el = document.getElementById(${JSON.stringify(cell.id)}); @@ -816,11 +688,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { // Poll for INPUT focus for (let fw = 0; fw < 4; fw++) { await page.waitForTimeout(150); - const ok = await page.evaluate(`(() => { - const f = document.activeElement; - return f && (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA'); - })()`); - if (ok) break; + if (await isInputFocused({ allowTextarea: true })) break; } } } @@ -982,16 +850,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { if (clickedShowAll) { await waitForStable(formNum); // Check if selection form opened - const selForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('input.editInput[id], a.press[id]').forEach(el => { - if (el.offsetWidth === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + const selForm = await helperDetectNewForm(formNum, { strict: true }); if (selForm !== null) { const pickResult = await pickFromSelectionForm(selForm, matchedKey, text, formNum); @@ -1037,48 +896,23 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await pickFromTypeDialog(newForm, info.type); await waitForStable(newForm); // After type selection, the actual selection form should open - const selForm = await page.evaluate(`(() => { - const forms = {}; - document.querySelectorAll('[id]').forEach(el => { - if (el.offsetWidth === 0 && el.offsetHeight === 0) return; - const m = el.id.match(/^form(\\d+)_/); - if (m) forms[m[1]] = true; - }); - const nums = Object.keys(forms).map(Number).filter(n => n > ${formNum}); - return nums.length > 0 ? Math.max(...nums) : null; - })()`); + const selForm = await helperDetectNewForm(formNum); if (selForm === null) { // Primitive type — poll for calculator/calendar popup or settle on INPUT let hasPopup = null; for (let pw = 0; pw < 5; pw++) { await page.waitForTimeout(200); - hasPopup = await page.evaluate(`(() => { - const calc = document.querySelector('.calculate'); - if (calc && calc.offsetWidth > 0) return 'calculator'; - const cal = document.querySelector('.frameCalendar'); - if (cal && cal.offsetWidth > 0) return 'calendar'; - return null; - })()`); + hasPopup = await findOpenPopup(); if (hasPopup) break; } if (hasPopup) { await page.keyboard.press('Escape'); for (let dw = 0; dw < 4; dw++) { await page.waitForTimeout(150); - const gone = await page.evaluate(`(() => { - const calc = document.querySelector('.calculate'); - if (calc && calc.offsetWidth > 0) return false; - const cal = document.querySelector('.frameCalendar'); - if (cal && cal.offsetWidth > 0) return false; - return true; - })()`); - if (gone) break; + if (!(await findOpenPopup())) break; } } - const inInput = await page.evaluate(`(() => { - const f = document.activeElement; - return f && (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA'); - })()`); + const inInput = await isInputFocused({ allowTextarea: true }); if (!inInput) { const cellRect = await page.evaluate(`(() => { const el = document.getElementById(${JSON.stringify(cell.id)}); @@ -1090,11 +924,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { await page.mouse.dblclick(cellRect.x, cellRect.y); for (let fw = 0; fw < 4; fw++) { await page.waitForTimeout(150); - const ok = await page.evaluate(`(() => { - const f = document.activeElement; - return f && (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA'); - })()`); - if (ok) break; + if (await isInputFocused({ allowTextarea: true })) break; } } }