refactor(web-test): извлечены detect-new-form и edit-state из inline в dom/

Дедуплицированы 15 копий detect-new-form (13 в row-fill + 2 локальные
обёртки в select-value), 6 копий INPUT-focused, 4 проверки calendar/
calculator popup, 1 INPUT-focused-inside-grid.

Новое:
- dom/forms.mjs: detectNewFormScript(prev, {strict}) — объединяет broad
  и strict варианты
- dom/edit-state.mjs: isInputFocusedScript({allowTextarea}),
  isInputFocusedInGridScript, findOpenPopupScript
- helpers.mjs: переписан detectNewForm на dom-script; добавлены тонкие
  обёртки isInputFocused, isInputFocusedInGrid, findOpenPopup

Метрики row-fill: 1235 → 1065 LOC (−170), inline page.evaluate 47 → 20.
Поведение идентично; точечный регресс зелёный (02/03/05/06/10/16).
04-selectvalue auto-history шаг — pre-existing baseline issue (state-
driven, не связан с S1, воспроизводится на HEAD).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-26 19:54:36 +03:00
parent 65ea06ab6e
commit 85003782db
6 changed files with 154 additions and 228 deletions
+8 -1
View File
@@ -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 {
@@ -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;
})()`;
}
+27 -1
View File
@@ -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;
})()`;
}
@@ -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<number|null>} 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());
}
/**
@@ -14,8 +14,8 @@ import { highlight, unhighlight } from '../recording/highlight.mjs';
import {
safeClick, findFieldInputId, readEdd,
detectNewForm as helperDetectNewForm,
} from '../core/helpers.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, strict mode)
async function detectSelectionForm() {
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).
@@ -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';
} 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
// 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);
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);
await page.waitForTimeout(150);
inEdit = await page.evaluate(`(() => {
const f = document.activeElement;
return f && f.tagName === 'INPUT';
inEdit = await isInputFocused();
if (inEdit) break;
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);
await page.waitForTimeout(150);
inEdit = await page.evaluate(`(() => {
const f = document.activeElement;
return f && f.tagName === 'INPUT';
inEdit = await isInputFocused();
if (inEdit) break;
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);
await page.waitForTimeout(200);
inEdit = await page.evaluate(`(() => {
const f = document.activeElement;
return f && f.tagName === 'INPUT';
inEdit = await isInputFocused();
if (inEdit) break;
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);
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
// 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
// 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
// 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);
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);
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)
// 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);
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);
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 (!(await findOpenPopup())) break;
}
}
// Ensure we are in an editable INPUT for this cell
// 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);
await page.waitForTimeout(150);
const ok = await page.evaluate(`(() => {
const f = document.activeElement;
return f && (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA');
})()`);
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
// 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
// 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);
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);
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 (!(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);
await page.waitForTimeout(150);
const ok = await page.evaluate(`(() => {
const f = document.activeElement;
return f && (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA');
})()`);
if (await isInputFocused({ allowTextarea: true })) break;
}
}
}