mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-15 02:14:57 +03:00
fix(web-test): fillTableRow enum support + clickElement tab ambiguity
Three fixes: 1. fillTableRow: match cells by column header text (headerText fallback) when INPUT id-based fuzzy match fails due to metadata typos 2. fillTableRow: EDD filter preserves standalone enum values like "Создать" by only filtering "Создать элемент/группу/:" patterns (was: startsWith) 3. clickElement: coordinate-based click for tabs without ID, avoiding global [data-content] selector that picks invisible duplicates from background forms Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1619,31 +1619,32 @@ export async function clickElement(text, { dblclick, table, toggle, expand } = {
|
||||
return state;
|
||||
}
|
||||
|
||||
// Build selector: tabs without ID use [data-content], others use [id]
|
||||
const selector = (target.kind === 'tab' && !target.id)
|
||||
? `[data-content="${target.name}"]`
|
||||
: `[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;
|
||||
// Tabs without ID — use coordinate click to avoid global [data-content] ambiguity
|
||||
if (target.kind === 'tab' && !target.id && target.x && target.y) {
|
||||
await page.mouse.click(target.x, target.y);
|
||||
} 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;
|
||||
}
|
||||
} else {
|
||||
throw clickErr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2526,10 +2527,29 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
if (!f) return { tag: 'none' };
|
||||
if (f.tagName === 'INPUT' || f.tagName === 'TEXTAREA') {
|
||||
const inGrid = (() => { let n = f; while (n) { if (n.classList?.contains('grid') || n.classList?.contains('gridContent')) return true; n = n.parentElement; } return false; })();
|
||||
if (inGrid) return {
|
||||
tag: 'INPUT', id: f.id,
|
||||
fullName: f.id.replace(/^form\\d+_/, '').replace(/_i\\d+$/, '')
|
||||
};
|
||||
if (inGrid) {
|
||||
let headerText = '';
|
||||
let grid = f; while (grid && !grid.classList?.contains('grid')) grid = grid.parentElement;
|
||||
if (grid) {
|
||||
const fr = f.getBoundingClientRect();
|
||||
const head = grid.querySelector('.gridHead');
|
||||
const hl = head?.querySelector('.gridLine') || head;
|
||||
if (hl) for (const h of hl.children) {
|
||||
if (h.offsetWidth === 0) continue;
|
||||
const hr = h.getBoundingClientRect();
|
||||
if (fr.x >= hr.x && fr.x < hr.x + hr.width) {
|
||||
const t = h.querySelector('.gridBoxText');
|
||||
headerText = (t || h).innerText?.trim() || '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
tag: 'INPUT', id: f.id,
|
||||
fullName: f.id.replace(/^form\\d+_/, '').replace(/_i\\d+$/, ''),
|
||||
headerText
|
||||
};
|
||||
}
|
||||
}
|
||||
return { tag: f.tagName || 'none' };
|
||||
})()`);
|
||||
@@ -2578,6 +2598,19 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: match by column header text (handles metadata typos in cell id)
|
||||
if (!matchedKey && cell.headerText) {
|
||||
const htLower = cell.headerText.toLowerCase();
|
||||
for (const [key, info] of pending) {
|
||||
if (info.filled) continue;
|
||||
const kl = key.toLowerCase();
|
||||
if (htLower === kl || htLower.endsWith(kl) || htLower.includes(kl)) {
|
||||
matchedKey = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedKey) {
|
||||
// Skip this cell
|
||||
await page.keyboard.press('Tab');
|
||||
@@ -2739,7 +2772,9 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) {
|
||||
|
||||
if (eddItems && eddItems.length > 0) {
|
||||
// Reference field with autocomplete — click best match
|
||||
const realItems = eddItems.filter(i => !i.startsWith('Создать'));
|
||||
// Filter out reference field "create" actions (Создать элемент, Создать группу, Создать: ...)
|
||||
// but keep standalone enum values like "Создать" (no space/colon after)
|
||||
const realItems = eddItems.filter(i => !/^Создать[\s:]/.test(i));
|
||||
|
||||
if (realItems.length > 0) {
|
||||
const tgt = normYo(text.toLowerCase());
|
||||
|
||||
@@ -686,7 +686,9 @@ export function findClickTargetScript(formNum, text, { tableName, gridSelector }
|
||||
}
|
||||
return false;
|
||||
}).forEach(el => {
|
||||
items.push({ id: el.id, name: el.dataset.content, label: '', kind: 'tab' });
|
||||
const r = el.getBoundingClientRect();
|
||||
items.push({ id: el.id, name: el.dataset.content, label: '', kind: 'tab',
|
||||
x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) });
|
||||
});
|
||||
|
||||
// Navigation panel items (FormNavigationPanel) — in parent page{N}
|
||||
@@ -724,7 +726,7 @@ export function findClickTargetScript(formNum, text, { tableName, gridSelector }
|
||||
if (!cf) cf = containerItems.find(i => i.label && i.label.toLowerCase() === target);
|
||||
if (!cf && target.length >= 4) cf = containerItems.find(i => i.name.toLowerCase().includes(target));
|
||||
if (!cf && target.length >= 4) cf = containerItems.find(i => i.label && i.label.toLowerCase().includes(target));
|
||||
if (cf) return { id: cf.id, kind: cf.kind, name: cf.name };
|
||||
if (cf) { const res = { id: cf.id, kind: cf.kind, name: cf.name }; if (cf.x != null) { res.x = cf.x; res.y = cf.y; } return res; }
|
||||
// Fallback: filter by gridName id-prefix (e.g. ИсходящиеКоманднаяПанель_Добавить)
|
||||
const gridName = gridEl.id ? gridEl.id.replace(p, '') : '';
|
||||
if (gridName) {
|
||||
@@ -732,7 +734,7 @@ export function findClickTargetScript(formNum, text, { tableName, gridSelector }
|
||||
let pf = prefixItems.find(i => i.name.toLowerCase() === target);
|
||||
if (!pf && target.length >= 4) pf = prefixItems.find(i => i.label && i.label.toLowerCase().includes(target));
|
||||
if (!pf && target.length >= 4) pf = prefixItems.find(i => i.name.toLowerCase().includes(target));
|
||||
if (pf) return { id: pf.id, kind: pf.kind, name: pf.name };
|
||||
if (pf) { const res = { id: pf.id, kind: pf.kind, name: pf.name }; if (pf.x != null) { res.x = pf.x; res.y = pf.y; } return res; }
|
||||
}
|
||||
}
|
||||
// Fall through to unscoped search
|
||||
@@ -749,7 +751,9 @@ export function findClickTargetScript(formNum, text, { tableName, gridSelector }
|
||||
if (!found && target.length >= 4) found = items.find(i => i.label && i.label.toLowerCase().includes(target));
|
||||
|
||||
if (found) {
|
||||
return { id: found.id, kind: found.kind, name: found.name };
|
||||
const res = { id: found.id, kind: found.kind, name: found.name };
|
||||
if (found.x != null) { res.x = found.x; res.y = found.y; }
|
||||
return res;
|
||||
}
|
||||
|
||||
// Grid rows — fallback: search in table rows (for hierarchical/tree navigation)
|
||||
|
||||
Reference in New Issue
Block a user