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 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-02-28 16:07:19 +03:00
parent ff14880871
commit 56203e2b71
2 changed files with 58 additions and 0 deletions
+1
View File
@@ -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 |
@@ -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 <span>.
* 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.
*