mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-26 06:54:38 +03:00
refactor(web-test): извлечены grid read-helpers + cloud-popup в dom/
Новое в dom/grid.mjs (все принимают опциональный gridSelector): - countGridRowsScript — кол-во .gridLine в body - isTreeGridScript — тип grid'а (есть .gridBoxTree) - findGridHeadCenterCoordsScript — центр .gridHead для commit-клика - getSelectedOrLastRowIndexScript — selected row index, fallback на последний Также: - isInputFocusedInGrid wrapper (S1) применён в add-row "ready" поллинге - isNotInListCloudVisibleScript (S3) применён вместо локального notInList - clickShowAllInNotInListCloudScript — новая в dom/forms.mjs (клик "Показать все" в "нет в списке" cloud popup через dispatchEvent) Метрики row-fill: 1041 → 971 LOC (−70), evaluates 17 → 10. Регресс 05/08/16/10 — зелёный. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// web-test dom v1.12 — facade re-exporting injectable DOM scripts from dom/
|
||||
// web-test dom v1.13 — 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.
|
||||
@@ -23,6 +23,7 @@ export {
|
||||
findPatternInputIdScript,
|
||||
isTypeDialogScript,
|
||||
isNotInListCloudVisibleScript,
|
||||
clickShowAllInNotInListCloudScript,
|
||||
findChildFormByButtonScript,
|
||||
readTypeDialogVisibleRowsScript,
|
||||
} from './dom/forms.mjs';
|
||||
@@ -55,6 +56,10 @@ export { getFormStateScript } from './dom/form-state.mjs';
|
||||
export {
|
||||
resolveGridScript,
|
||||
readTableScript,
|
||||
countGridRowsScript,
|
||||
isTreeGridScript,
|
||||
findGridHeadCenterCoordsScript,
|
||||
getSelectedOrLastRowIndexScript,
|
||||
} from './dom/grid.mjs';
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test dom/forms v1.3 — form detection, content read, click-target/field-button resolution
|
||||
// web-test dom/forms v1.4 — 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';
|
||||
|
||||
@@ -517,6 +517,39 @@ export function isTypeDialogScript(formNum) {
|
||||
})()`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the "Показать все" / "Show all" link inside the "нет в списке"
|
||||
* cloud popup via `dispatchEvent`. Returns boolean — whether clicked.
|
||||
*/
|
||||
export function clickShowAllInNotInListCloudScript() {
|
||||
return `(() => {
|
||||
for (const el of document.querySelectorAll('div')) {
|
||||
if (el.offsetWidth === 0 || el.offsetHeight === 0) continue;
|
||||
const s = getComputedStyle(el);
|
||||
if (s.position !== 'absolute' && s.position !== 'fixed') continue;
|
||||
if ((parseInt(s.zIndex) || 0) < 100) continue;
|
||||
if (!(el.innerText || '').includes('нет в списке')) continue;
|
||||
const links = [...el.querySelectorAll('a, span, div')]
|
||||
.filter(e => e.offsetWidth > 0 && e.children.length === 0);
|
||||
const showAll = links.find(e => {
|
||||
const t = (e.innerText?.trim() || '').toLowerCase();
|
||||
return t === 'показать все' || t === 'show all';
|
||||
});
|
||||
if (showAll) {
|
||||
const r = showAll.getBoundingClientRect();
|
||||
const opts = { bubbles:true, cancelable:true,
|
||||
clientX: r.x + r.width/2, clientY: r.y + r.height/2 };
|
||||
showAll.dispatchEvent(new MouseEvent('mousedown', opts));
|
||||
showAll.dispatchEvent(new MouseEvent('mouseup', opts));
|
||||
showAll.dispatchEvent(new MouseEvent('click', opts));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
})()`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the "нет в списке" cloud popup visible? 1C shows it as a positioned div
|
||||
* (absolute/fixed, high z-index) whose text contains "нет в списке".
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test dom/grid v1.0 — grid resolution + table reading
|
||||
// web-test dom/grid v1.1 — grid resolution + table reading + edit-time helpers
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
/**
|
||||
@@ -247,3 +247,75 @@ export function readTableScript(formNum, { maxRows = 20, offset = 0, gridSelecto
|
||||
return result;
|
||||
})()`;
|
||||
}
|
||||
|
||||
// ─── Edit-time grid helpers (for fillTableRow / row-fill) ────────────────────
|
||||
//
|
||||
// All helpers below accept an optional `gridSelector`. When passed, they target
|
||||
// that exact grid; when null/undefined they pick the LAST visible `.grid` on
|
||||
// the page (this matches the implicit "current grid" used by row-fill).
|
||||
|
||||
/** Inline JS fragment that resolves the target grid into `const grid`. */
|
||||
function gridResolver(gridSelector) {
|
||||
return gridSelector
|
||||
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
||||
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count `.gridLine` rows in the body of the target grid.
|
||||
* Returns the row count, or `0` when grid/body absent.
|
||||
*/
|
||||
export function countGridRowsScript(gridSelector) {
|
||||
return `(() => {
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
const body = grid?.querySelector('.gridBody');
|
||||
return body ? body.querySelectorAll('.gridLine').length : 0;
|
||||
})()`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the target grid a tree grid? (presence of `.gridBoxTree`)
|
||||
* Returns boolean.
|
||||
*/
|
||||
export function isTreeGridScript(gridSelector) {
|
||||
return `(() => {
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
return grid ? !!grid.querySelector('.gridBoxTree') : false;
|
||||
})()`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return center coords of the grid's `.gridHead` element.
|
||||
* Used as a click target to commit a pending cell edit (clicking the header
|
||||
* defocuses the input without selecting another row).
|
||||
*
|
||||
* Returns `{ x, y } | null`.
|
||||
*/
|
||||
export function findGridHeadCenterCoordsScript(gridSelector) {
|
||||
return `(() => {
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
if (!grid) return null;
|
||||
const head = grid.querySelector('.gridHead');
|
||||
if (!head) return null;
|
||||
const r = head.getBoundingClientRect();
|
||||
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) };
|
||||
})()`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the currently selected row in the target grid, or
|
||||
* fall back to the last row when nothing is selected.
|
||||
*
|
||||
* Returns row index, or `-1` when no rows.
|
||||
*/
|
||||
export function getSelectedOrLastRowIndexScript(gridSelector) {
|
||||
return `(() => {
|
||||
const grid = ${gridResolver(gridSelector)};
|
||||
if (!grid) return -1;
|
||||
const body = grid.querySelector('.gridBody');
|
||||
if (!body) return -1;
|
||||
const lines = [...body.querySelectorAll('.gridLine')];
|
||||
const sel = lines.findIndex(l => l.classList.contains('selected'));
|
||||
return sel >= 0 ? sel : lines.length - 1;
|
||||
})()`;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import {
|
||||
} from '../core/state.mjs';
|
||||
import {
|
||||
detectFormScript, resolveGridScript, readTableScript,
|
||||
countGridRowsScript, isTreeGridScript, findGridHeadCenterCoordsScript,
|
||||
getSelectedOrLastRowIndexScript,
|
||||
isNotInListCloudVisibleScript, clickShowAllInNotInListCloudScript,
|
||||
} from '../../dom.mjs';
|
||||
import { dismissPendingErrors, checkForErrors } from '../core/errors.mjs';
|
||||
import { waitForStable, waitForCondition, startNetworkMonitor } from '../core/wait.mjs';
|
||||
@@ -62,24 +65,12 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
let addedRowIdx = -1;
|
||||
if (add) {
|
||||
// Count rows before add — new row will be appended at this index
|
||||
addedRowIdx = await page.evaluate(`(() => {
|
||||
const grid = ${gridSelector
|
||||
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
||||
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`};
|
||||
const body = grid?.querySelector('.gridBody');
|
||||
return body ? body.querySelectorAll('.gridLine').length : 0;
|
||||
})()`);
|
||||
addedRowIdx = await page.evaluate(countGridRowsScript(gridSelector));
|
||||
await clickElement('Добавить', { table });
|
||||
// Poll for edit mode (INPUT inside grid) instead of fixed 1000ms wait
|
||||
for (let aw = 0; aw < 6; aw++) {
|
||||
await page.waitForTimeout(150);
|
||||
const ready = 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;
|
||||
})()`);
|
||||
if (ready) break;
|
||||
if (await isInputFocusedInGrid()) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,12 +285,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
// When click entered INPUT mode but no selection form yet — try F4 only for tree grids
|
||||
// (tree grid ref fields need F4 to open selection form; flat grids work via Tab-loop)
|
||||
if (inEdit && directEditForm === null) {
|
||||
const isTreeGrid = await page.evaluate(`(() => {
|
||||
const grid = ${gridSelector
|
||||
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
||||
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`};
|
||||
return grid ? !!grid.querySelector('.gridBoxTree') : false;
|
||||
})()`);
|
||||
const isTreeGrid = await page.evaluate(isTreeGridScript(gridSelector));
|
||||
if (isTreeGrid) {
|
||||
await page.keyboard.press('F4');
|
||||
for (let fw = 0; fw < 8; fw++) {
|
||||
@@ -782,46 +768,11 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check for "нет в списке" cloud popup (reference field, value not found)
|
||||
const notInList = await page.evaluate(`(() => {
|
||||
for (const el of document.querySelectorAll('div')) {
|
||||
if (el.offsetWidth === 0 || el.offsetHeight === 0) continue;
|
||||
const s = getComputedStyle(el);
|
||||
if (s.position !== 'absolute' && s.position !== 'fixed') continue;
|
||||
if ((parseInt(s.zIndex) || 0) < 100) continue;
|
||||
if ((el.innerText || '').includes('нет в списке')) return true;
|
||||
}
|
||||
return false;
|
||||
})()`);
|
||||
const notInList = await page.evaluate(isNotInListCloudVisibleScript());
|
||||
|
||||
if (notInList) {
|
||||
// Cloud has "Показать все" link — try to open selection form via it
|
||||
const clickedShowAll = await page.evaluate(`(() => {
|
||||
for (const el of document.querySelectorAll('div')) {
|
||||
if (el.offsetWidth === 0 || el.offsetHeight === 0) continue;
|
||||
const s = getComputedStyle(el);
|
||||
if (s.position !== 'absolute' && s.position !== 'fixed') continue;
|
||||
if ((parseInt(s.zIndex) || 0) < 100) continue;
|
||||
if (!(el.innerText || '').includes('нет в списке')) continue;
|
||||
// Found the cloud — look for "Показать все" hyperlink inside
|
||||
const links = [...el.querySelectorAll('a, span, div')]
|
||||
.filter(e => e.offsetWidth > 0 && e.children.length === 0);
|
||||
const showAll = links.find(e => {
|
||||
const t = (e.innerText?.trim() || '').toLowerCase();
|
||||
return t === 'показать все' || t === 'show all';
|
||||
});
|
||||
if (showAll) {
|
||||
const r = showAll.getBoundingClientRect();
|
||||
const opts = { bubbles:true, cancelable:true,
|
||||
clientX: r.x + r.width/2, clientY: r.y + r.height/2 };
|
||||
showAll.dispatchEvent(new MouseEvent('mousedown', opts));
|
||||
showAll.dispatchEvent(new MouseEvent('mouseup', opts));
|
||||
showAll.dispatchEvent(new MouseEvent('click', opts));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
})()`);
|
||||
const clickedShowAll = await page.evaluate(clickShowAllInNotInListCloudScript());
|
||||
|
||||
if (clickedShowAll) {
|
||||
await waitForStable(formNum);
|
||||
@@ -958,18 +909,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
// Clicking a different data row would re-enter edit mode on that row.
|
||||
// Without this commit click, the row stays in "uncommitted add" state
|
||||
// and a subsequent Escape (e.g. from closeForm) would cancel the entire row.
|
||||
const commitTarget = await page.evaluate(`(() => {
|
||||
const grid = ${gridSelector
|
||||
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
||||
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`};
|
||||
if (!grid) return null;
|
||||
const head = grid.querySelector('.gridHead');
|
||||
if (head) {
|
||||
const r = head.getBoundingClientRect();
|
||||
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) };
|
||||
}
|
||||
return null;
|
||||
})()`);
|
||||
const commitTarget = await page.evaluate(findGridHeadCenterCoordsScript(gridSelector));
|
||||
if (commitTarget) {
|
||||
await page.mouse.click(commitTarget.x, commitTarget.y);
|
||||
await page.waitForTimeout(500);
|
||||
@@ -1001,17 +941,7 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
}
|
||||
if (Object.keys(checkboxFields).length > 0) {
|
||||
// Use row index: addedRowIdx (from add mode) or fallback to selected row
|
||||
const currentRow = addedRowIdx >= 0 ? addedRowIdx : (row != null ? row : await page.evaluate(`(() => {
|
||||
const grid = ${gridSelector
|
||||
? `document.querySelector(${JSON.stringify(gridSelector)})`
|
||||
: `(() => { const grids = [...document.querySelectorAll('.grid')].filter(el => el.offsetWidth > 0); return grids[grids.length - 1]; })()`};
|
||||
if (!grid) return -1;
|
||||
const body = grid.querySelector('.gridBody');
|
||||
if (!body) return -1;
|
||||
const lines = [...body.querySelectorAll('.gridLine')];
|
||||
const sel = lines.findIndex(l => l.classList.contains('selected'));
|
||||
return sel >= 0 ? sel : lines.length - 1;
|
||||
})()`)
|
||||
const currentRow = addedRowIdx >= 0 ? addedRowIdx : (row != null ? row : await page.evaluate(getSelectedOrLastRowIndexScript(gridSelector))
|
||||
);
|
||||
if (currentRow >= 0) {
|
||||
const more = await fillTableRow(checkboxFields, { row: currentRow, table });
|
||||
|
||||
Reference in New Issue
Block a user