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 }; })()`);