From 7e56cd79db5a549328e75a1a484a526b7e9d231a Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 14 Mar 2026 11:48:57 +0300 Subject: [PATCH] fix(web-test): skip checkbox columns in row clicks + document table parameter Row selection clicks in deleteTableRow and fillTableRow commit now target the second visible gridBox instead of the first, avoiding accidental checkbox toggles on forms with checkbox columns (e.g. BP links master). Also documents the new `table` parameter in SKILL.md for readTable, clickElement, fillTableRow, deleteTableRow, and getFormState tables[]. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-test/SKILL.md | 31 +++++++++++++++++---- .claude/skills/web-test/scripts/browser.mjs | 15 ++++++---- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.claude/skills/web-test/SKILL.md b/.claude/skills/web-test/SKILL.md index 3dc761f1..276071c0 100644 --- a/.claude/skills/web-test/SKILL.md +++ b/.claude/skills/web-test/SKILL.md @@ -118,12 +118,14 @@ Switch to an already-open tab/window (fuzzy match). ### Reading form state -#### `getFormState()` → `{ fields, buttons, tabs, table, filters, reportSettings? }` +#### `getFormState()` → `{ fields, buttons, tabs, table, tables, filters, reportSettings? }` Returns current form structure. This is the primary way to understand what's on screen. **fields** — each field has: `name`, `value`, `label?`, `actions?` (select, clear, open), `required?` (true for unfilled mandatory fields) -**table** — summary only: `{ name, columns, rowCount }`. Use `readTable()` for actual data. +**tables** — array of all visible grids: `[{ name, columns, rowCount }]`. Use `readTable()` for actual data. + +**table** — backward-compatible alias for the first grid: `{ present, columns, rowCount }`. **reportSettings** — for DCS reports: human-readable filter settings instead of raw technical names: ```js @@ -140,13 +142,14 @@ const form = await getFormState(); ### Reading data -#### `readTable({ maxRows?, offset? })` → `{ columns, rows, total, shown, offset }` +#### `readTable({ maxRows?, offset?, table? })` → `{ columns, rows, total, shown, offset }` Read actual grid data with pagination. Each row is `{ columnName: value }`. | Option | Default | Description | |--------|---------|-------------| | `maxRows` | 20 | Max rows to return per call | | `offset` | 0 | Skip first N rows | +| `table` | — | Grid name from `tables[]` (for multi-grid forms) | Special row fields: - `_kind: 'group'` — hierarchical group row @@ -190,9 +193,13 @@ Sections + all open tabs. ### Actions -#### `clickElement(text, { dblclick? })` → form state +#### `clickElement(text, { dblclick?, table? })` → form state Click button, hyperlink, tab, or grid row (fuzzy match). +- `table` — scope button search to a specific grid's command panel (by name from `tables[]`): + ```js + await clickElement('Добавить', { table: 'Исходящие' }); // clicks "Добавить" near "Исходящие" grid + ``` - Single click selects a row in a list. **Double-click opens** the item: ```js await clickElement('0000-000227', { dblclick: true }); // opens document @@ -250,6 +257,13 @@ Also supports DCS labels — auto-enables the paired checkbox. #### `fillTableRow(fields, opts)` → form state Fill table row cells via Tab navigation. Value is a plain string or `{ value, type }` for composite-type cells. +| Option | Description | +|--------|-------------| +| `tab` | Switch to tab before filling | +| `add` | Add new row before filling | +| `row` | Edit existing row by 0-based index | +| `table` | Grid name from `tables[]` (for multi-grid forms) | + ```js // Add new row: await fillTableRow( @@ -261,6 +275,11 @@ await fillTableRow( { 'Количество': '20' }, { tab: 'Товары', row: 0 } ); +// Multi-grid form — add row to specific table: +await fillTableRow( + { 'Объект': 'БДДС' }, + { table: 'Исходящие', add: true } +); // Composite-type cell (e.g. SubConto accepting multiple types): await fillTableRow( { 'СубконтоКт1': { value: 'Голованов', type: 'Физическое лицо' } }, @@ -272,8 +291,8 @@ await fillTableRow( - Fuzzy cell match: "Количество" matches "ТоварыКоличество" - Reference cells auto-detected by autocomplete popup -#### `deleteTableRow(row, { tab? })` → form state -Delete row by 0-based index. +#### `deleteTableRow(row, { tab?, table? })` → form state +Delete row by 0-based index. `table` targets a specific grid on multi-grid forms. #### `closeForm({ save? })` → form state Close the current form via Escape. diff --git a/.claude/skills/web-test/scripts/browser.mjs b/.claude/skills/web-test/scripts/browser.mjs index 99ab67ba..7cf4026e 100644 --- a/.claude/skills/web-test/scripts/browser.mjs +++ b/.claude/skills/web-test/scripts/browser.mjs @@ -2417,7 +2417,8 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { const otherIdx = ${row} === 0 ? 1 : 0; const other = rows[otherIdx]; if (!other) return null; - const box = [...other.children].filter(b => b.offsetWidth > 0)[0]; + const visBoxes = [...other.children].filter(b => b.offsetWidth > 0 && !b.classList.contains('gridBoxComp')); + const box = visBoxes.length > 1 ? visBoxes[1] : visBoxes[0]; if (!box) return null; const r = box.getBoundingClientRect(); return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) }; @@ -2981,7 +2982,8 @@ export async function fillTableRow(fields, { tab, add, row, table } = {}) { const targetIdx = activeRowIdx === 0 ? 1 : 0; const target = rows[targetIdx]; if (target) { - const box = [...target.children].find(b => b.offsetWidth > 0); + const visBoxes = [...target.children].filter(b => b.offsetWidth > 0 && !b.classList.contains('gridBoxComp')); + const box = visBoxes.length > 1 ? visBoxes[1] : visBoxes[0]; if (box) { const r = box.getBoundingClientRect(); return { x: Math.round(r.x + r.width / 2), y: Math.round(r.y + r.height / 2) }; @@ -3062,9 +3064,12 @@ export async function deleteTableRow(row, { tab, table } = {}) { const rows = [...body.querySelectorAll('.gridLine')]; if (${row} >= rows.length) return { error: 'row_out_of_range', total: rows.length }; const line = rows[${row}]; - const cells = [...line.querySelectorAll('.gridBoxText')]; - const cell = cells.length > 1 ? cells[1] : cells[0]; - if (!cell) return { error: 'no_cell' }; + // Use visible gridBox containers (not gridBoxText) to avoid clicking checkboxes + const boxes = [...line.children].filter(b => b.offsetWidth > 0 && !b.classList.contains('gridBoxComp')); + // Skip first column (row number / checkbox) — pick second visible box + const box = boxes.length > 1 ? boxes[1] : boxes[0]; + 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), total: rows.length }; })()`);