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 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-14 11:48:57 +03:00
parent 1abc44334c
commit 7e56cd79db
2 changed files with 35 additions and 11 deletions
+25 -6
View File
@@ -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.
+10 -5
View File
@@ -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 };
})()`);