feat(web-test): navigateSection newline normalization + fillTableRow cell skip

navigateSection now normalizes \r\n to spaces, so callers don't need
literal newlines in section names. fillTableRow direct-edit path skips
cells that already contain the desired value (method: 'skip').

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-13 20:05:12 +03:00
parent 767b9fcaf0
commit 0ca2faa6a6
2 changed files with 36 additions and 7 deletions
+34 -5
View File
@@ -2129,11 +2129,24 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
if (!box) return { error: 'no_cell' };
const cell = box.querySelector('.gridBoxText') || box;
const r = cell.getBoundingClientRect();
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) };
const currentText = (cell.innerText?.trim() || '').replace(/\u00a0/g, ' ');
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2), currentText };
})()`);
if (cellCoords.error) throw new Error(`fillTableRow: ${cellCoords.error}${cellCoords.total ? ' (total rows: ' + cellCoords.total + ')' : ''}`);
// Skip if cell already contains the desired value (single-field optimization)
const firstKey0 = Object.keys(fields)[0];
const firstVal0 = typeof fields[firstKey0] === 'object' ? fields[firstKey0].value : String(fields[firstKey0]);
let firstFieldSkipped = false;
if (cellCoords.currentText && firstVal0 &&
cellCoords.currentText.toLowerCase().includes(firstVal0.toLowerCase())) {
firstFieldSkipped = true;
if (Object.keys(fields).length === 1) {
return [{ field: firstKey0, ok: true, method: 'skip', value: cellCoords.currentText }];
}
}
// Click first (tree grids enter edit on single click; dblclick toggles expand/collapse).
// Then escalate: dblclick → F4 if needed.
await page.mouse.click(cellCoords.x, cellCoords.y);
@@ -2274,9 +2287,17 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
// First field: selection form is already open from the dblclick above
const firstKey = Object.keys(fields)[0];
const firstInfo = pending.get(firstKey);
const pickResult = await directEditPick(directEditForm, firstKey, firstInfo);
firstInfo.filled = true;
results.push(pickResult);
if (firstFieldSkipped) {
firstInfo.filled = true;
results.push({ field: firstKey, ok: true, method: 'skip', value: cellCoords.currentText });
// Close the selection form that opened from the click
await page.keyboard.press('Escape');
await waitForStable(formNum);
} else {
const pickResult = await directEditPick(directEditForm, firstKey, firstInfo);
firstInfo.filled = true;
results.push(pickResult);
}
// Remaining fields: dblclick on each column cell individually
for (const [key, info] of pending) {
@@ -2315,13 +2336,21 @@ export async function fillTableRow(fields, { tab, add, row } = {}) {
if (!box) return null;
const cell = box.querySelector('.gridBoxText') || box;
const r = cell.getBoundingClientRect();
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) };
const currentText = (cell.innerText?.trim() || '').replace(/\\u00a0/g, ' ');
return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2), currentText };
})()`);
if (!nextCoords) {
info.filled = true;
results.push({ field: key, error: 'column_not_found', message: `Column for "${key}" not found` });
continue;
}
// Skip if cell already contains the desired value
if (nextCoords.currentText && info.value &&
nextCoords.currentText.toLowerCase().includes(info.value.toLowerCase())) {
info.filled = true;
results.push({ field: key, ok: true, method: 'skip', value: nextCoords.currentText });
continue;
}
await page.mouse.dblclick(nextCoords.x, nextCoords.y);
// Poll for selection form (with F4 fallback if dblclick didn't open it)
let selForm = null;
+2 -2
View File
@@ -476,8 +476,8 @@ export function getFormStateScript() {
*/
export function navigateSectionScript(name) {
return `(() => {
const norm = s => (s?.trim().replace(/\\u00a0/g, ' ') || '').replace(/ё/gi, 'е');
const target = ${JSON.stringify(name.toLowerCase().replace(/ё/g, 'е'))};
const norm = s => (s?.trim().replace(/\\u00a0/g, ' ').replace(/[\\r\\n]+/g, ' ').replace(/ +/g, ' ') || '').replace(/ё/gi, 'е');
const target = ${JSON.stringify(name.toLowerCase().replace(/ё/g, 'е').replace(/[\r\n]+/g, ' ').replace(/ +/g, ' '))};
const els = [...document.querySelectorAll('[id^="themesCell_theme_"]')];
let bestEl = els.find(el => norm(el.innerText).toLowerCase() === target);
if (!bestEl) bestEl = els.find(el => norm(el.innerText).toLowerCase().includes(target));