From 56203e2b71ac13939ca94a2ea3a160372eb9abc0 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 28 Feb 2026 16:07:19 +0300 Subject: [PATCH] feat(web-test): add readSpreadsheet() for extracting report data Reads 1C SpreadsheetDocument (report output) rendered in iframes. Collects cells from div[x]/div[y] elements across all frames, returns { rows: string[][], total }. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/web-test/SKILL.md | 1 + .claude/skills/web-test/scripts/browser.mjs | 57 +++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/.claude/skills/web-test/SKILL.md b/.claude/skills/web-test/SKILL.md index d5ad8517..d5c969cd 100644 --- a/.claude/skills/web-test/SKILL.md +++ b/.claude/skills/web-test/SKILL.md @@ -107,6 +107,7 @@ In `exec` sandbox, all browser.mjs functions are available as globals — no `im |----------|-------------| | `getFormState()` | Current form: fields (with `required` flag for unfilled mandatory fields), buttons, tabs, table meta (columns + rowCount), filters | | `readTable({maxRows, offset})` | Table row data: `{ columns, rows: [{col: val}], total }`. Use this to read grid contents | +| `readSpreadsheet()` | Read report output (SpreadsheetDocument): `{ rows: string[][], total }`. Use after clicking "Сформировать" | | `getSections()` | Sections + commands of active section | | `getPageState()` | Sections + open tabs | | `getCommands()` | Commands of current section | diff --git a/.claude/skills/web-test/scripts/browser.mjs b/.claude/skills/web-test/scripts/browser.mjs index e7bd6100..44bd6677 100644 --- a/.claude/skills/web-test/scripts/browser.mjs +++ b/.claude/skills/web-test/scripts/browser.mjs @@ -427,6 +427,63 @@ export async function readTable({ maxRows = 20, offset = 0 } = {}) { return await page.evaluate(readTableScript(formNum, { maxRows, offset })); } +/** + * Read report output (SpreadsheetDocumentField) rendered in iframes. + * 1C renders spreadsheet documents as absolutely-positioned div cells inside iframes. + * Each cell is a div[x] inside a row div[y], text content in . + * Returns { headers: string[][], data: string[][] } — arrays of cell arrays. + */ +export async function readSpreadsheet() { + ensureConnected(); + const frames = page.frames(); + const allCells = new Map(); + + for (let fi = 1; fi < frames.length; fi++) { + try { + const cells = await frames[fi].evaluate(`(() => { + const cells = []; + document.querySelectorAll('div[x]').forEach(d => { + const span = d.querySelector('span'); + const text = span?.textContent?.trim() || ''; + if (!text) return; + const rowDiv = d.parentElement; + const row = rowDiv?.getAttribute('y') || rowDiv?.className?.match(/R(\\d+)/)?.[1] || null; + const col = d.getAttribute('x'); + if (row != null && col != null) cells.push({ r: parseInt(row), c: parseInt(col), t: text }); + }); + return cells; + })()`); + for (const cell of cells) { + const key = `${cell.r}_${cell.c}`; + if (!allCells.has(key) || cell.t.length > allCells.get(key).t.length) { + allCells.set(key, cell); + } + } + } catch { /* skip inaccessible frames */ } + } + + if (allCells.size === 0) return { error: 'no_spreadsheet', hint: 'No SpreadsheetDocument found. Report may not be generated yet.' }; + + // Group by row, determine max columns + const rowMap = new Map(); + let maxCol = 0; + for (const cell of allCells.values()) { + if (!rowMap.has(cell.r)) rowMap.set(cell.r, new Map()); + rowMap.get(cell.r).set(cell.c, cell.t); + if (cell.c > maxCol) maxCol = cell.c; + } + + const sortedRows = [...rowMap.keys()].sort((a, b) => a - b); + const rows = sortedRows.map(r => { + const colMap = rowMap.get(r); + const arr = []; + for (let c = 0; c <= maxCol; c++) arr.push(colMap.get(c) || ''); + return arr; + }); + + return { rows, total: rows.length }; +} + /** * Pick a value from an opened selection form: search + dblclick matching row. *