@cerios/playwright-table 1.1.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -7
- package/README.md +911 -447
- package/dist/index.d.mts +330 -92
- package/dist/index.d.ts +330 -92
- package/dist/index.js +823 -242
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +823 -242
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -23
package/dist/index.js
CHANGED
|
@@ -25,8 +25,27 @@ __export(index_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
|
-
// src/
|
|
29
|
-
|
|
28
|
+
// src/polling.ts
|
|
29
|
+
async function pollTable(fn, options) {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
if (!options) {
|
|
32
|
+
options = {};
|
|
33
|
+
}
|
|
34
|
+
const timeout = (_a = options.timeout) != null ? _a : 3e4;
|
|
35
|
+
const interval = (_b = options.interval) != null ? _b : 100;
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
let lastError;
|
|
38
|
+
while (Date.now() - startTime < timeout) {
|
|
39
|
+
try {
|
|
40
|
+
await fn();
|
|
41
|
+
return;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
lastError = err;
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
throw lastError || new Error("Playwright Tables: Polling timeout reached");
|
|
48
|
+
}
|
|
30
49
|
|
|
31
50
|
// src/cell-content-type.ts
|
|
32
51
|
var CellContentType = /* @__PURE__ */ ((CellContentType2) => {
|
|
@@ -37,34 +56,127 @@ var CellContentType = /* @__PURE__ */ ((CellContentType2) => {
|
|
|
37
56
|
|
|
38
57
|
// src/table-utils.ts
|
|
39
58
|
var TableUtils = class {
|
|
59
|
+
constructor() {
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extracts text content from a table cell.
|
|
63
|
+
* @param cell - The Playwright locator for the cell.
|
|
64
|
+
* @param cellContentType - The type of content to extract (InnerText or TextContent).
|
|
65
|
+
* @returns The trimmed text content of the cell, or empty string if no content.
|
|
66
|
+
* @throws Error if the cell locator doesn't exist in the DOM.
|
|
67
|
+
*/
|
|
40
68
|
static async getCellContent(cell, cellContentType) {
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
try {
|
|
70
|
+
const count = await cell.count();
|
|
71
|
+
if (count === 0) {
|
|
72
|
+
throw new Error("Cell locator does not exist in the DOM");
|
|
73
|
+
}
|
|
74
|
+
const content = cellContentType === "innerText" /* InnerText */ ? await cell.innerText() : await cell.textContent();
|
|
75
|
+
return (content == null ? void 0 : content.trim()) || "";
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const locatorDescription = this.getLocatorDescription(cell);
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Failed to get cell content.
|
|
80
|
+
Locator: ${locatorDescription}
|
|
81
|
+
Content type: ${cellContentType}
|
|
82
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
43
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Parses rowspan and colspan attributes from a table cell.
|
|
88
|
+
* Fetches both attributes in parallel for optimal performance.
|
|
89
|
+
* @param cell - The Playwright locator for the cell.
|
|
90
|
+
* @returns Object containing rowspan and colspan values (defaults to 1 if not present).
|
|
91
|
+
* @throws Error if attributes contain invalid values (non-numeric or less than 1).
|
|
92
|
+
*/
|
|
44
93
|
static async parseSpanAttributes(cell) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
94
|
+
try {
|
|
95
|
+
const [rowspanAttr, colspanAttr] = await Promise.all([
|
|
96
|
+
cell.getAttribute("rowspan"),
|
|
97
|
+
cell.getAttribute("colspan")
|
|
98
|
+
]);
|
|
99
|
+
const rowspan = parseInt(rowspanAttr || "1", 10);
|
|
100
|
+
const colspan = parseInt(colspanAttr || "1", 10);
|
|
101
|
+
if (isNaN(rowspan) || rowspan < 1) {
|
|
102
|
+
throw new Error(`Invalid rowspan attribute: "${rowspanAttr}". Expected positive integer.`);
|
|
103
|
+
}
|
|
104
|
+
if (isNaN(colspan) || colspan < 1) {
|
|
105
|
+
throw new Error(`Invalid colspan attribute: "${colspanAttr}". Expected positive integer.`);
|
|
106
|
+
}
|
|
107
|
+
return { rowspan, colspan };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const locatorDescription = this.getLocatorDescription(cell);
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Failed to parse span attributes.
|
|
112
|
+
Locator: ${locatorDescription}
|
|
113
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extracts a human-readable locator description from a Playwright Locator.
|
|
119
|
+
* This captures Playwright's internal selector chain for better error messages.
|
|
120
|
+
* @param locator - The Playwright locator to describe.
|
|
121
|
+
* @returns A string representation of the locator's selector chain.
|
|
122
|
+
*/
|
|
123
|
+
static getLocatorDescription(locator) {
|
|
124
|
+
const locatorString = locator.toString();
|
|
125
|
+
return locatorString;
|
|
49
126
|
}
|
|
50
127
|
};
|
|
51
128
|
|
|
52
129
|
// src/table-body.ts
|
|
53
130
|
var TableBody = class {
|
|
131
|
+
constructor() {
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extracts body rows from a table with proper handling of rowspan and colspan.
|
|
135
|
+
*
|
|
136
|
+
* @param rowLocator - The Playwright locator for body rows.
|
|
137
|
+
* @param columnsSelector - CSS selector for locating columns within rows (e.g., "td").
|
|
138
|
+
* @param bodyRowsOptions - Optional configuration for cell content extraction.
|
|
139
|
+
* @param bodyRowsOptions.cellContentType - The type of content to extract (InnerText or TextContent).
|
|
140
|
+
* @returns Promise resolving to an array of processed body rows.
|
|
141
|
+
* @throws Error if columnsSelector is empty or if row processing fails.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* const rows = await TableBody.getRows(
|
|
145
|
+
* page.locator('tbody tr'),
|
|
146
|
+
* 'td',
|
|
147
|
+
* { cellContentType: CellContentType.TextContent }
|
|
148
|
+
* );
|
|
149
|
+
*/
|
|
54
150
|
static async getRows(rowLocator, columnsSelector, bodyRowsOptions) {
|
|
151
|
+
if (!columnsSelector || columnsSelector.trim() === "") {
|
|
152
|
+
throw new Error("columnsSelector cannot be empty");
|
|
153
|
+
}
|
|
154
|
+
const locatorDescription = rowLocator.toString();
|
|
55
155
|
const rows = [];
|
|
56
156
|
const rowsCount = await rowLocator.count();
|
|
157
|
+
if (rowsCount === 0) {
|
|
158
|
+
return rows;
|
|
159
|
+
}
|
|
57
160
|
const spannedCells = {};
|
|
58
161
|
for (let rowIndex = 0; rowIndex < rowsCount; rowIndex++) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
162
|
+
try {
|
|
163
|
+
const row = rowLocator.nth(rowIndex);
|
|
164
|
+
const columns = await this.extractColumns(
|
|
165
|
+
row,
|
|
166
|
+
columnsSelector,
|
|
167
|
+
rowIndex,
|
|
168
|
+
spannedCells,
|
|
169
|
+
bodyRowsOptions == null ? void 0 : bodyRowsOptions.cellContentType
|
|
170
|
+
);
|
|
171
|
+
rows.push(columns);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Failed to process body row at index ${rowIndex}.
|
|
175
|
+
Body row locator: ${locatorDescription}
|
|
176
|
+
Columns selector: "${columnsSelector}"
|
|
177
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
68
180
|
}
|
|
69
181
|
return rows;
|
|
70
182
|
}
|
|
@@ -72,35 +184,77 @@ var TableBody = class {
|
|
|
72
184
|
const columns = [];
|
|
73
185
|
const columnLocators = row.locator(columnsSelector);
|
|
74
186
|
const columnCount = await columnLocators.count();
|
|
187
|
+
if (columnCount === 0) {
|
|
188
|
+
this.applySpannedCells(spannedCells, rowIndex, columns);
|
|
189
|
+
return columns;
|
|
190
|
+
}
|
|
191
|
+
const cellDataPromises = [];
|
|
75
192
|
for (let colIndex = 0; colIndex < columnCount; colIndex++) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
193
|
+
cellDataPromises.push(this.fetchCellData(columnLocators.nth(colIndex), cellContentType));
|
|
194
|
+
}
|
|
195
|
+
const cellDataArray = await Promise.all(cellDataPromises);
|
|
196
|
+
const spannedPositions = /* @__PURE__ */ new Set();
|
|
197
|
+
if (spannedCells[rowIndex]) {
|
|
198
|
+
spannedCells[rowIndex].forEach((_, colIndex) => {
|
|
199
|
+
if (spannedCells[rowIndex][colIndex] !== void 0) {
|
|
200
|
+
spannedPositions.add(colIndex);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
let actualColIndex = 0;
|
|
205
|
+
for (let domCellIndex = 0; domCellIndex < columnCount; domCellIndex++) {
|
|
206
|
+
while (spannedPositions.has(actualColIndex)) {
|
|
207
|
+
actualColIndex++;
|
|
208
|
+
}
|
|
209
|
+
const cellData = cellDataArray[domCellIndex];
|
|
210
|
+
this.processCellData(cellData, actualColIndex, columns, rowIndex, spannedCells);
|
|
211
|
+
actualColIndex += cellData.colspan;
|
|
84
212
|
}
|
|
85
213
|
this.applySpannedCells(spannedCells, rowIndex, columns);
|
|
86
214
|
return columns;
|
|
87
215
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Fetches cell content and span attributes in parallel.
|
|
218
|
+
* This method performs DOM operations that can be parallelized across multiple cells.
|
|
219
|
+
*/
|
|
220
|
+
static async fetchCellData(column, cellContentType = "innerText" /* InnerText */) {
|
|
221
|
+
const [content, spanAttributes] = await Promise.all([
|
|
222
|
+
TableUtils.getCellContent(column, cellContentType),
|
|
223
|
+
TableUtils.parseSpanAttributes(column)
|
|
224
|
+
]);
|
|
225
|
+
return {
|
|
226
|
+
content,
|
|
227
|
+
rowspan: spanAttributes.rowspan,
|
|
228
|
+
colspan: spanAttributes.colspan
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Processes pre-fetched cell data and applies it to the columns array.
|
|
233
|
+
* This method is purely computational and doesn't perform any DOM operations.
|
|
234
|
+
*/
|
|
235
|
+
static processCellData(cellData, colIndex, columns, rowIndex, spannedCells) {
|
|
236
|
+
const { content, rowspan, colspan } = cellData;
|
|
237
|
+
if (colIndex < 0) {
|
|
238
|
+
throw new Error(`Invalid column index: ${colIndex}. Must be non-negative.`);
|
|
239
|
+
}
|
|
240
|
+
if (rowIndex < 0) {
|
|
241
|
+
throw new Error(`Invalid row index: ${rowIndex}. Must be non-negative.`);
|
|
242
|
+
}
|
|
91
243
|
for (let span = 0; span < colspan; span++) {
|
|
92
244
|
columns[colIndex + span] = content;
|
|
93
245
|
}
|
|
94
246
|
if (rowspan > 1) {
|
|
95
|
-
this.storeSpannedCells(rowIndex, colIndex, rowspan, content, spannedCells);
|
|
247
|
+
this.storeSpannedCells(rowIndex, colIndex, rowspan, colspan, content, spannedCells);
|
|
96
248
|
}
|
|
97
249
|
}
|
|
98
|
-
static storeSpannedCells(rowIndex, colIndex, rowspan, content, spannedCells) {
|
|
99
|
-
for (let
|
|
100
|
-
if (!spannedCells[rowIndex +
|
|
101
|
-
spannedCells[rowIndex +
|
|
250
|
+
static storeSpannedCells(rowIndex, colIndex, rowspan, colspan, content, spannedCells) {
|
|
251
|
+
for (let rowSpan = 1; rowSpan < rowspan; rowSpan++) {
|
|
252
|
+
if (!spannedCells[rowIndex + rowSpan]) {
|
|
253
|
+
spannedCells[rowIndex + rowSpan] = [];
|
|
254
|
+
}
|
|
255
|
+
for (let colSpan = 0; colSpan < colspan; colSpan++) {
|
|
256
|
+
spannedCells[rowIndex + rowSpan][colIndex + colSpan] = content;
|
|
102
257
|
}
|
|
103
|
-
spannedCells[rowIndex + span][colIndex] = content;
|
|
104
258
|
}
|
|
105
259
|
}
|
|
106
260
|
static applySpannedCells(spannedCells, rowIndex, columns) {
|
|
@@ -116,52 +270,131 @@ var TableBody = class {
|
|
|
116
270
|
|
|
117
271
|
// src/table-header.ts
|
|
118
272
|
var TableHeader = class {
|
|
273
|
+
constructor() {
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Extracts header rows from a table with optional processing for colspan, rowspan, and duplicates.
|
|
277
|
+
*
|
|
278
|
+
* @param headerRowLocator - The Playwright locator for header rows.
|
|
279
|
+
* @param columnsSelector - CSS selector for locating header columns (e.g., "th").
|
|
280
|
+
* @param headerRowOptions - Optional configuration for header processing.
|
|
281
|
+
* @returns Promise resolving to an array of processed header rows.
|
|
282
|
+
* @throws Error if columnsSelector is empty or if no header rows are found.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* const headers = await TableHeader.getRows(
|
|
286
|
+
* page.locator('thead tr'),
|
|
287
|
+
* 'th',
|
|
288
|
+
* { duplicateSuffix: true, colspan: { enabled: true } }
|
|
289
|
+
* );
|
|
290
|
+
*/
|
|
119
291
|
static async getRows(headerRowLocator, columnsSelector, headerRowOptions) {
|
|
292
|
+
if (!columnsSelector || columnsSelector.trim() === "") {
|
|
293
|
+
throw new Error("columnsSelector cannot be empty");
|
|
294
|
+
}
|
|
295
|
+
const locatorDescription = headerRowLocator.toString();
|
|
120
296
|
const options = this.getDefaultOptions(headerRowOptions);
|
|
121
297
|
const rows = await headerRowLocator.all();
|
|
122
298
|
const headerRows = [];
|
|
299
|
+
if (rows.length === 0) {
|
|
300
|
+
return headerRows;
|
|
301
|
+
}
|
|
123
302
|
const rowSpans = /* @__PURE__ */ new Map();
|
|
124
|
-
for (
|
|
125
|
-
|
|
126
|
-
|
|
303
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
304
|
+
try {
|
|
305
|
+
const headerRow = await this.processRow(rows[rowIndex], columnsSelector, rowSpans, options);
|
|
306
|
+
headerRows.push(headerRow);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Failed to process header row at index ${rowIndex}.
|
|
310
|
+
Header row locator: ${locatorDescription}
|
|
311
|
+
Columns selector: "${columnsSelector}"
|
|
312
|
+
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
127
315
|
}
|
|
128
316
|
this.applyColspanOptions(headerRows, options);
|
|
129
317
|
this.applyDuplicateSuffix(headerRows, options);
|
|
130
318
|
return headerRows;
|
|
131
319
|
}
|
|
132
320
|
static getDefaultOptions(options) {
|
|
133
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
321
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
134
322
|
return {
|
|
135
323
|
cellContentType: (_a = options == null ? void 0 : options.cellContentType) != null ? _a : "innerText" /* InnerText */,
|
|
136
324
|
emptyCellReplacement: (_b = options == null ? void 0 : options.emptyCellReplacement) != null ? _b : false,
|
|
137
325
|
duplicateSuffix: (_c = options == null ? void 0 : options.duplicateSuffix) != null ? _c : false,
|
|
326
|
+
normalizeWhitespace: (_d = options == null ? void 0 : options.normalizeWhitespace) != null ? _d : false,
|
|
327
|
+
stripIconGlyphs: (_e = options == null ? void 0 : options.stripIconGlyphs) != null ? _e : false,
|
|
138
328
|
colspan: {
|
|
139
|
-
enabled: (
|
|
140
|
-
suffix: (
|
|
329
|
+
enabled: (_g = (_f = options == null ? void 0 : options.colspan) == null ? void 0 : _f.enabled) != null ? _g : false,
|
|
330
|
+
suffix: (_i = (_h = options == null ? void 0 : options.colspan) == null ? void 0 : _h.suffix) != null ? _i : false
|
|
141
331
|
}
|
|
142
332
|
};
|
|
143
333
|
}
|
|
144
334
|
static async processRow(row, columnsSelector, rowSpans, options) {
|
|
145
335
|
const headerRow = [];
|
|
146
336
|
const cells = await row.locator(columnsSelector).all();
|
|
337
|
+
if (cells.length === 0) {
|
|
338
|
+
return headerRow;
|
|
339
|
+
}
|
|
340
|
+
const cellDataArray = await Promise.all(
|
|
341
|
+
cells.map(
|
|
342
|
+
(cell) => this.fetchHeaderCellData(cell, options.cellContentType, options.normalizeWhitespace, options.stripIconGlyphs)
|
|
343
|
+
)
|
|
344
|
+
);
|
|
147
345
|
let columnIndex = 0;
|
|
148
|
-
for (
|
|
149
|
-
columnIndex =
|
|
346
|
+
for (let i = 0; i < cellDataArray.length; i++) {
|
|
347
|
+
columnIndex = this.processHeaderCellData(
|
|
348
|
+
cellDataArray[i],
|
|
349
|
+
headerRow,
|
|
350
|
+
rowSpans,
|
|
351
|
+
columnIndex,
|
|
352
|
+
options.emptyCellReplacement
|
|
353
|
+
);
|
|
150
354
|
}
|
|
151
355
|
this.addRemainingRowSpans(headerRow, rowSpans, columnIndex);
|
|
152
356
|
return headerRow;
|
|
153
357
|
}
|
|
154
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Fetches header cell content and span attributes in parallel.
|
|
360
|
+
* This method performs DOM operations that can be parallelized across multiple cells.
|
|
361
|
+
*/
|
|
362
|
+
static async fetchHeaderCellData(cell, cellContentType, normalizeWhitespace, stripIconGlyphs) {
|
|
363
|
+
const [text, spanAttributes] = await Promise.all([
|
|
364
|
+
TableUtils.getCellContent(cell, cellContentType),
|
|
365
|
+
TableUtils.parseSpanAttributes(cell)
|
|
366
|
+
]);
|
|
367
|
+
return {
|
|
368
|
+
text: this.sanitizeHeaderText(text, normalizeWhitespace, stripIconGlyphs),
|
|
369
|
+
rowspan: spanAttributes.rowspan,
|
|
370
|
+
colspan: spanAttributes.colspan
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
static sanitizeHeaderText(text, normalizeWhitespace, stripIconGlyphs) {
|
|
374
|
+
let result = text;
|
|
375
|
+
if (stripIconGlyphs) {
|
|
376
|
+
result = result.replace(this.ICON_GLYPH_PATTERN, "");
|
|
377
|
+
}
|
|
378
|
+
if (normalizeWhitespace) {
|
|
379
|
+
result = result.replace(/\s+/g, " ");
|
|
380
|
+
}
|
|
381
|
+
return result.trim();
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Processes pre-fetched header cell data and applies it to the header row.
|
|
385
|
+
* This method is purely computational and doesn't perform any DOM operations.
|
|
386
|
+
*/
|
|
387
|
+
static processHeaderCellData(cellData, headerRow, rowSpans, columnIndex, emptyCellReplacement) {
|
|
155
388
|
while (rowSpans.has(columnIndex)) {
|
|
156
389
|
headerRow.push(rowSpans.get(columnIndex));
|
|
157
390
|
rowSpans.delete(columnIndex);
|
|
158
391
|
columnIndex++;
|
|
159
392
|
}
|
|
160
|
-
let text =
|
|
161
|
-
if (!text &&
|
|
162
|
-
text =
|
|
393
|
+
let text = cellData.text;
|
|
394
|
+
if (!text && emptyCellReplacement) {
|
|
395
|
+
text = this.EMPTY_CELL_PLACEHOLDER;
|
|
163
396
|
}
|
|
164
|
-
const { colspan, rowspan } =
|
|
397
|
+
const { colspan, rowspan } = cellData;
|
|
165
398
|
headerRow.push(text);
|
|
166
399
|
this.handleColspan(headerRow, text, colspan);
|
|
167
400
|
this.handleRowspan(rowSpans, text, columnIndex, colspan, rowspan);
|
|
@@ -169,7 +402,7 @@ var TableHeader = class {
|
|
|
169
402
|
}
|
|
170
403
|
static handleColspan(headerRow, text, colspan) {
|
|
171
404
|
for (let i = 1; i < colspan; i++) {
|
|
172
|
-
headerRow.push(`${text}
|
|
405
|
+
headerRow.push(`${text}${this.COLSPAN_SUFFIX_PREFIX}${i}`);
|
|
173
406
|
}
|
|
174
407
|
}
|
|
175
408
|
static handleRowspan(rowSpans, text, columnIndex, colspan, rowspan) {
|
|
@@ -195,20 +428,15 @@ var TableHeader = class {
|
|
|
195
428
|
}
|
|
196
429
|
}
|
|
197
430
|
static removeColspanCells(headerRows) {
|
|
198
|
-
for (
|
|
199
|
-
|
|
200
|
-
if (/__C\d/.test(headerRow[i])) {
|
|
201
|
-
headerRow.splice(i, 1);
|
|
202
|
-
i--;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
431
|
+
for (let i = 0; i < headerRows.length; i++) {
|
|
432
|
+
headerRows[i] = headerRows[i].filter((cell) => !this.COLSPAN_PATTERN.test(cell));
|
|
205
433
|
}
|
|
206
434
|
}
|
|
207
435
|
static removeColspanSuffix(headerRows) {
|
|
208
436
|
for (const headerRow of headerRows) {
|
|
209
437
|
for (let i = 0; i < headerRow.length; i++) {
|
|
210
|
-
if (
|
|
211
|
-
headerRow[i] = headerRow[i].replace(
|
|
438
|
+
if (this.COLSPAN_PATTERN.test(headerRow[i])) {
|
|
439
|
+
headerRow[i] = headerRow[i].replace(this.COLSPAN_PATTERN, "");
|
|
212
440
|
}
|
|
213
441
|
}
|
|
214
442
|
}
|
|
@@ -219,10 +447,10 @@ var TableHeader = class {
|
|
|
219
447
|
const headerCountMap = /* @__PURE__ */ new Map();
|
|
220
448
|
for (let i = 0; i < headerRow.length; i++) {
|
|
221
449
|
const header = headerRow[i];
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
headerRow[i] = `${header}
|
|
225
|
-
headerCountMap.set(header,
|
|
450
|
+
const currentCount = headerCountMap.get(header);
|
|
451
|
+
if (currentCount !== void 0) {
|
|
452
|
+
headerRow[i] = `${header}${this.DUPLICATE_SUFFIX_PREFIX}${currentCount}`;
|
|
453
|
+
headerCountMap.set(header, currentCount + 1);
|
|
226
454
|
} else {
|
|
227
455
|
headerCountMap.set(header, 1);
|
|
228
456
|
}
|
|
@@ -230,78 +458,11 @@ var TableHeader = class {
|
|
|
230
458
|
}
|
|
231
459
|
}
|
|
232
460
|
};
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const validateCellsWithContent = async () => {
|
|
239
|
-
const cells = await rowLocator.locator(cellSelector).all();
|
|
240
|
-
const cellsWithContent = (await Promise.all(
|
|
241
|
-
cells.map(async (cell) => {
|
|
242
|
-
const content = await cell.textContent();
|
|
243
|
-
return content !== null && content.trim() !== "" ? cell : null;
|
|
244
|
-
})
|
|
245
|
-
)).filter((cell) => cell !== null);
|
|
246
|
-
(0, import_test.expect)(cellsWithContent.length, `No ${rowKind} cells with content found`).toBeGreaterThan(0);
|
|
247
|
-
};
|
|
248
|
-
const validateRowAmount = (rows2, expectedAmount) => {
|
|
249
|
-
(0, import_test.expect)(rows2.length, `Expected ${expectedAmount} ${rowKind} rows, but found ${rows2.length}`).toBe(expectedAmount);
|
|
250
|
-
};
|
|
251
|
-
const validateCellCount = async (rows2, expectedCount) => {
|
|
252
|
-
const rowsWithMatchingCells = (await Promise.all(
|
|
253
|
-
rows2.map(async (row) => {
|
|
254
|
-
const cellCount = await row.locator(cellSelector).count();
|
|
255
|
-
return cellCount === expectedCount ? row : null;
|
|
256
|
-
})
|
|
257
|
-
)).filter((row) => row !== null);
|
|
258
|
-
(0, import_test.expect)(
|
|
259
|
-
rowsWithMatchingCells.length,
|
|
260
|
-
`Expected amount of ${expectedCount} ${rowKind} cells for row not found`
|
|
261
|
-
).toBeGreaterThan(0);
|
|
262
|
-
};
|
|
263
|
-
const validateCellContentCount = async (rows2, expectedContentCount) => {
|
|
264
|
-
const rowsWithMatchingContent = (await Promise.all(
|
|
265
|
-
rows2.map(async (row) => {
|
|
266
|
-
const cells = await row.locator(cellSelector).all();
|
|
267
|
-
const cellsWithContent = (await Promise.all(
|
|
268
|
-
cells.map(async (cell) => {
|
|
269
|
-
const content = await cell.textContent();
|
|
270
|
-
return content !== null && content.trim() !== "" ? cell : null;
|
|
271
|
-
})
|
|
272
|
-
)).filter((cell) => cell !== null);
|
|
273
|
-
return cellsWithContent.length === expectedContentCount ? row : null;
|
|
274
|
-
})
|
|
275
|
-
)).filter((row) => row !== null);
|
|
276
|
-
(0, import_test.expect)(
|
|
277
|
-
rowsWithMatchingContent.length,
|
|
278
|
-
`Expected amount of ${expectedContentCount} ${rowKind} cells with content for row not found`
|
|
279
|
-
).toBeGreaterThan(0);
|
|
280
|
-
};
|
|
281
|
-
const rows = await rowLocator.all();
|
|
282
|
-
if (options == null ? void 0 : options.row) {
|
|
283
|
-
if (options.row.amount) {
|
|
284
|
-
validateRowAmount(rows, options.row.amount);
|
|
285
|
-
} else {
|
|
286
|
-
(0, import_test.expect)(rows.length, `No ${rowKind} rows found`).toBeGreaterThan(0);
|
|
287
|
-
}
|
|
288
|
-
if (options.row.cell) {
|
|
289
|
-
if (options.row.cell.totalCount) {
|
|
290
|
-
await validateCellCount(rows, options.row.cell.totalCount);
|
|
291
|
-
}
|
|
292
|
-
if (options.row.cell.contentCount) {
|
|
293
|
-
await validateCellContentCount(rows, options.row.cell.contentCount);
|
|
294
|
-
} else {
|
|
295
|
-
await validateCellsWithContent();
|
|
296
|
-
}
|
|
297
|
-
} else {
|
|
298
|
-
await validateCellsWithContent();
|
|
299
|
-
}
|
|
300
|
-
} else {
|
|
301
|
-
await validateCellsWithContent();
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
};
|
|
461
|
+
TableHeader.EMPTY_CELL_PLACEHOLDER = "{{Empty}}";
|
|
462
|
+
TableHeader.COLSPAN_SUFFIX_PREFIX = "__C";
|
|
463
|
+
TableHeader.DUPLICATE_SUFFIX_PREFIX = "__D";
|
|
464
|
+
TableHeader.COLSPAN_PATTERN = /__C\d+/;
|
|
465
|
+
TableHeader.ICON_GLYPH_PATTERN = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/gu;
|
|
305
466
|
|
|
306
467
|
// src/playwright-table.ts
|
|
307
468
|
var PlaywrightTable = class {
|
|
@@ -336,85 +497,170 @@ var PlaywrightTable = class {
|
|
|
336
497
|
constructor(_tableLocator, _options) {
|
|
337
498
|
this._tableLocator = _tableLocator;
|
|
338
499
|
this._options = _options;
|
|
339
|
-
|
|
340
|
-
this.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
500
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
|
|
501
|
+
const headerRowSelector = (_b = (_a = this._options) == null ? void 0 : _a.header) == null ? void 0 : _b.rowSelector;
|
|
502
|
+
const bodyRowSelector = (_d = (_c = this._options) == null ? void 0 : _c.row) == null ? void 0 : _d.rowSelector;
|
|
503
|
+
if (headerRowSelector && bodyRowSelector && headerRowSelector === bodyRowSelector) {
|
|
504
|
+
console.warn(
|
|
505
|
+
`PlaywrightTable: header.rowSelector and row.rowSelector are identical ("${headerRowSelector}"). This can cause header rows to be included in body rows. Consider scoping selectors to separate row groups.`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
this._headerRowLocator = this._tableLocator.locator((_g = (_f = (_e = this._options) == null ? void 0 : _e.header) == null ? void 0 : _f.rowSelector) != null ? _g : "thead>tr");
|
|
509
|
+
this._headerColumnSelector = (_j = (_i = (_h = this._options) == null ? void 0 : _h.header) == null ? void 0 : _i.columnSelector) != null ? _j : "th";
|
|
510
|
+
this._bodyRowLocator = this._tableLocator.locator((_m = (_l = (_k = this._options) == null ? void 0 : _k.row) == null ? void 0 : _l.rowSelector) != null ? _m : "tbody>tr");
|
|
511
|
+
this._bodyRowColumnSelector = (_p = (_o = (_n = this._options) == null ? void 0 : _n.row) == null ? void 0 : _o.columnSelector) != null ? _p : "td";
|
|
346
512
|
}
|
|
347
513
|
/**
|
|
348
514
|
* Retrieves all header rows of the table.
|
|
349
|
-
* @param options - Optional parameters for
|
|
350
|
-
* @param options.timeout - Timeout in milliseconds for loading table data.
|
|
351
|
-
* @param options.headerRowOptions - See {@link HeaderRowOptions} for available options.
|
|
515
|
+
* @param options - Optional parameters for header row parsing configuration.
|
|
352
516
|
* @returns A promise that resolves to an array of header rows.
|
|
517
|
+
* @throws Error if no header rows found after table loads.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* // Get headers with colspan enabled
|
|
521
|
+
* const headers = await table.getHeaderRows({
|
|
522
|
+
* colspan: { enabled: true, suffix: true },
|
|
523
|
+
* duplicateSuffix: true
|
|
524
|
+
* });
|
|
525
|
+
*
|
|
526
|
+
* @see {@link getMainHeaderRow} for getting only the main header row
|
|
527
|
+
* @see {@link HeaderRowOptions} for all available configuration options
|
|
353
528
|
*/
|
|
354
529
|
async getHeaderRows(options) {
|
|
355
|
-
await this.
|
|
356
|
-
|
|
530
|
+
const table = await this.getTable({
|
|
531
|
+
headerRowOptions: options
|
|
532
|
+
});
|
|
533
|
+
return table.headerRows;
|
|
357
534
|
}
|
|
358
535
|
/**
|
|
359
536
|
* Retrieves the main header row of the table.
|
|
360
|
-
*
|
|
361
|
-
* @param options
|
|
362
|
-
* @param options.headerRowOptions - See {@link HeaderRowOptions} for available options.
|
|
537
|
+
* By default, uses the last header row if multiple exist. Can be customized via constructor.
|
|
538
|
+
* @param options - Optional parameters for header row parsing configuration.
|
|
363
539
|
* @returns A promise that resolves to the main header row.
|
|
540
|
+
* @throws Error if no header rows available.
|
|
541
|
+
* @throws Error if setMainHeaderRow index is out of bounds (negative or >= header row count).
|
|
542
|
+
*
|
|
543
|
+
* @example
|
|
544
|
+
* // Get main header with custom index set in constructor
|
|
545
|
+
* const table = new PlaywrightTable(page.locator('table'), {
|
|
546
|
+
* header: { setMainHeaderRow: 0 } // Use first row as main header
|
|
547
|
+
* });
|
|
548
|
+
* const mainHeader = await table.getMainHeaderRow();
|
|
549
|
+
*
|
|
550
|
+
* @see {@link getHeaderRows} for getting all header rows
|
|
364
551
|
*/
|
|
365
552
|
async getMainHeaderRow(options) {
|
|
366
|
-
await this.
|
|
367
|
-
|
|
553
|
+
const table = await this.getTable({
|
|
554
|
+
headerRowOptions: options
|
|
555
|
+
});
|
|
556
|
+
return this.mainHeaderRow(table.headerRows);
|
|
368
557
|
}
|
|
369
558
|
/**
|
|
370
559
|
* Retrieves all body rows of the table.
|
|
371
|
-
* @param options
|
|
372
|
-
* @param options.
|
|
560
|
+
* @param options - Optional table or body-row configuration.
|
|
561
|
+
* @param options.cellContentType - Legacy shortcut for body row content extraction. See {@link CellContentType}.
|
|
562
|
+
* @param options.bodyRowOptions - Body row parsing options.
|
|
563
|
+
* @param options.headerRowOptions - Header parsing options used when resolving row/colspan layouts.
|
|
373
564
|
* @returns A promise that resolves to an array of body rows.
|
|
565
|
+
* @throws Error if no body rows found after table loads.
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* // Get body rows with TextContent (includes hidden elements)
|
|
569
|
+
* const rows = await table.getBodyRows({
|
|
570
|
+
* cellContentType: CellContentType.TextContent
|
|
571
|
+
* });
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* // Get body rows with default InnerText
|
|
575
|
+
* const rows = await table.getBodyRows();
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* // Use full table options (for example to sanitize noisy treegrid headers)
|
|
579
|
+
* const rows = await table.getBodyRows({
|
|
580
|
+
* bodyRowOptions: { cellContentType: CellContentType.InnerText },
|
|
581
|
+
* headerRowOptions: { normalizeWhitespace: true, stripIconGlyphs: true }
|
|
582
|
+
* });
|
|
374
583
|
*/
|
|
375
584
|
async getBodyRows(options) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
bodyRowOptions:
|
|
379
|
-
headerRowOptions: {
|
|
380
|
-
|
|
381
|
-
}
|
|
585
|
+
const tableOptions = this.normalizeGetBodyRowsOptions(options);
|
|
586
|
+
const table = await this.getTable({
|
|
587
|
+
bodyRowOptions: tableOptions.bodyRowOptions,
|
|
588
|
+
headerRowOptions: this.resolveHeaderRowOptions(tableOptions.headerRowOptions, {
|
|
589
|
+
forceColspanEnabled: true
|
|
590
|
+
})
|
|
382
591
|
});
|
|
383
|
-
return
|
|
592
|
+
return table.bodyRows;
|
|
384
593
|
}
|
|
385
594
|
/**
|
|
386
|
-
* Retrieves a specific cell locator in the body of the table.
|
|
595
|
+
* Retrieves a specific cell locator in the body of the table by row and column index.
|
|
387
596
|
* @param rowNumber - The row number (0-based index).
|
|
388
|
-
* @param headerPosition - The header position (0-based index).
|
|
597
|
+
* @param headerPosition - The column/header position (0-based index).
|
|
389
598
|
* @returns A Playwright Locator for the specified cell.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* // Get cell at row 1, column 2
|
|
602
|
+
* const cellLocator = table.getBodyCellLocator(1, 2);
|
|
603
|
+
* await expect(cellLocator).toHaveText('Expected Text');
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* // Get first cell in first row
|
|
607
|
+
* const firstCell = table.getBodyCellLocator(0, 0);
|
|
608
|
+
* await firstCell.click();
|
|
609
|
+
*
|
|
610
|
+
* @see {@link getBodyCellLocatorByRowConditions} for finding cells by content matching
|
|
611
|
+
* @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting all cells in a column
|
|
612
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells under a header
|
|
390
613
|
*/
|
|
391
614
|
getBodyCellLocator(rowNumber, headerPosition) {
|
|
392
615
|
return this._bodyRowLocator.nth(rowNumber).locator(this._bodyRowColumnSelector).nth(headerPosition);
|
|
393
616
|
}
|
|
394
617
|
/**
|
|
395
618
|
* Retrieves a cell locator in the body of the table based on row conditions and a target header.
|
|
396
|
-
*
|
|
397
|
-
* @param
|
|
398
|
-
* @param
|
|
619
|
+
* Finds the first row where all conditions match, then returns the cell in the target column.
|
|
620
|
+
* @param conditions - An object where keys are header names and values are expected cell contents, or a single [header, value] tuple.
|
|
621
|
+
* @param targetHeader - The header name of the column containing the cell to retrieve.
|
|
622
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
399
623
|
* @returns A promise that resolves to a Playwright Locator for the matching cell.
|
|
400
|
-
* @throws
|
|
624
|
+
* @throws Error if no row matches all specified conditions.
|
|
625
|
+
* @throws Error if any condition header or target header is not found in the table.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* // Find cell with multiple conditions
|
|
629
|
+
* const emailLocator = await table.getBodyCellLocatorByRowConditions(
|
|
630
|
+
* { "First name": "John", "Status": "Active" },
|
|
631
|
+
* "Email"
|
|
632
|
+
* );
|
|
633
|
+
* await expect(emailLocator).toContainText('@example.com');
|
|
634
|
+
*
|
|
635
|
+
* @example
|
|
636
|
+
* // Find cell with single condition using tuple syntax
|
|
637
|
+
* const actionCell = await table.getBodyCellLocatorByRowConditions(
|
|
638
|
+
* ["Username", "john.doe"],
|
|
639
|
+
* "Actions"
|
|
640
|
+
* );
|
|
641
|
+
* await actionCell.locator('button').click();
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* // Find cell with single condition using object syntax
|
|
645
|
+
* const statusCell = await table.getBodyCellLocatorByRowConditions(
|
|
646
|
+
* { "Username": "john.doe" },
|
|
647
|
+
* "Status"
|
|
648
|
+
* );
|
|
649
|
+
*
|
|
650
|
+
* @see {@link getBodyCellLocator} for getting cells by direct index
|
|
651
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells in a column
|
|
401
652
|
*/
|
|
402
653
|
async getBodyCellLocatorByRowConditions(conditions, targetHeader, options) {
|
|
403
654
|
var _a;
|
|
404
|
-
await this.
|
|
405
|
-
const
|
|
406
|
-
const targetHeaderIndex =
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
for (let rowIndex = 0; rowIndex < this._rows.length; rowIndex++) {
|
|
411
|
-
const row = this._rows[rowIndex];
|
|
655
|
+
const table = await this.getTable(options);
|
|
656
|
+
const mainHeader = this.mainHeaderRow(table.headerRows);
|
|
657
|
+
const targetHeaderIndex = this.getHeaderIndex(targetHeader, mainHeader);
|
|
658
|
+
const conditionsObj = Array.isArray(conditions) ? { [conditions[0]]: conditions[1] } : conditions;
|
|
659
|
+
for (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {
|
|
660
|
+
const row = table.bodyRows[rowIndex];
|
|
412
661
|
let matches = true;
|
|
413
|
-
for (const [header, value] of Object.entries(
|
|
414
|
-
const headerIndex =
|
|
415
|
-
if (headerIndex === -1) {
|
|
416
|
-
throw new Error(`Header "${header}" not found.`);
|
|
417
|
-
}
|
|
662
|
+
for (const [header, value] of Object.entries(conditionsObj)) {
|
|
663
|
+
const headerIndex = this.getHeaderIndex(header, mainHeader);
|
|
418
664
|
const cellValue = (_a = row[headerIndex]) != null ? _a : "";
|
|
419
665
|
if (cellValue !== value) {
|
|
420
666
|
matches = false;
|
|
@@ -425,24 +671,42 @@ var PlaywrightTable = class {
|
|
|
425
671
|
return this.getBodyCellLocator(rowIndex, targetHeaderIndex);
|
|
426
672
|
}
|
|
427
673
|
}
|
|
428
|
-
throw new Error(
|
|
674
|
+
throw new Error(
|
|
675
|
+
`No row found matching conditions: ${JSON.stringify(conditionsObj)}
|
|
676
|
+
Target header: "${targetHeader}"
|
|
677
|
+
Searched ${table.bodyRows.length} rows
|
|
678
|
+
Body row locator: ${this._bodyRowLocator.toString()}`
|
|
679
|
+
);
|
|
429
680
|
}
|
|
430
681
|
/**
|
|
431
682
|
* Retrieves all cell locators in the body of the table for a specific header name.
|
|
432
|
-
*
|
|
433
|
-
* @param
|
|
434
|
-
* @
|
|
435
|
-
* @
|
|
683
|
+
* Returns locators for every cell in the column identified by the header.
|
|
684
|
+
* @param header - The header name identifying the column.
|
|
685
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
686
|
+
* @returns A promise that resolves to an array of Playwright Locators for all cells in the column.
|
|
687
|
+
* @throws Error if the header is not found in the table.
|
|
688
|
+
*
|
|
689
|
+
* @example
|
|
690
|
+
* // Get all cells in the "Status" column
|
|
691
|
+
* const statusCells = await table.getAllBodyCellLocatorsByHeaderName("Status");
|
|
692
|
+
* for (const cell of statusCells) {
|
|
693
|
+
* await expect(cell).toHaveText(/Active|Inactive/);
|
|
694
|
+
* }
|
|
695
|
+
*
|
|
696
|
+
* @example
|
|
697
|
+
* // Get text from all cells in "Name" column
|
|
698
|
+
* const nameCells = await table.getAllBodyCellLocatorsByHeaderName("Name");
|
|
699
|
+
* const names = await Promise.all(nameCells.map(cell => cell.textContent()));
|
|
700
|
+
*
|
|
701
|
+
* @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting cells by column index
|
|
702
|
+
* @see {@link getBodyCellLocatorByRowConditions} for finding specific cells by content
|
|
436
703
|
*/
|
|
437
704
|
async getAllBodyCellLocatorsByHeaderName(header, options) {
|
|
438
|
-
await this.
|
|
439
|
-
const headers =
|
|
440
|
-
const headerIndex =
|
|
441
|
-
if (headerIndex === -1) {
|
|
442
|
-
throw new Error(`Header "${header}" not found.`);
|
|
443
|
-
}
|
|
705
|
+
const table = await this.getTable(options);
|
|
706
|
+
const headers = this.mainHeaderRow(table.headerRows);
|
|
707
|
+
const headerIndex = this.getHeaderIndex(header, headers);
|
|
444
708
|
const locators = [];
|
|
445
|
-
for (let rowIndex = 0; rowIndex <
|
|
709
|
+
for (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {
|
|
446
710
|
const cellLocator = this.getBodyCellLocator(rowIndex, headerIndex);
|
|
447
711
|
locators.push(cellLocator);
|
|
448
712
|
}
|
|
@@ -450,40 +714,79 @@ var PlaywrightTable = class {
|
|
|
450
714
|
}
|
|
451
715
|
/**
|
|
452
716
|
* Retrieves all cell locators in the body of the table for a specific header index.
|
|
453
|
-
*
|
|
454
|
-
* @param
|
|
455
|
-
* @
|
|
717
|
+
* Returns locators for every cell in the column at the specified position.
|
|
718
|
+
* @param headerIndex - The header/column index (0-based).
|
|
719
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
720
|
+
* @returns A promise that resolves to an array of Playwright Locators for all cells in the column.
|
|
721
|
+
* @throws Error if the header index is out of bounds (negative or >= column count).
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* // Get all cells in the third column (index 2)
|
|
725
|
+
* const thirdColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(2);
|
|
726
|
+
* for (const cell of thirdColumnCells) {
|
|
727
|
+
* await expect(cell).toBeVisible();
|
|
728
|
+
* }
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* // Get all cells in first column
|
|
732
|
+
* const firstColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(0);
|
|
733
|
+
* const values = await Promise.all(firstColumnCells.map(c => c.textContent()));
|
|
734
|
+
*
|
|
735
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting cells by header name
|
|
736
|
+
* @see {@link getBodyCellLocator} for getting a specific cell by row and column index
|
|
456
737
|
*/
|
|
457
738
|
async getAllBodyCellLocatorsByHeaderIndex(headerIndex, options) {
|
|
458
|
-
await this.
|
|
739
|
+
const table = await this.getTable(options);
|
|
740
|
+
const columnCount = this.mainHeaderRow(table.headerRows).length;
|
|
741
|
+
if (headerIndex < 0 || headerIndex >= columnCount) {
|
|
742
|
+
throw new Error(
|
|
743
|
+
`Header index ${headerIndex} out of bounds. Table has ${columnCount} columns.
|
|
744
|
+
Header row locator: ${this._headerRowLocator.toString()}`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
459
747
|
const locators = [];
|
|
460
|
-
for (let rowIndex = 0; rowIndex <
|
|
748
|
+
for (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {
|
|
461
749
|
const cellLocator = this.getBodyCellLocator(rowIndex, headerIndex);
|
|
462
750
|
locators.push(cellLocator);
|
|
463
751
|
}
|
|
464
752
|
return locators;
|
|
465
753
|
}
|
|
466
754
|
/**
|
|
467
|
-
* Converts the table data into a JSON object.
|
|
468
|
-
*
|
|
469
|
-
* @
|
|
755
|
+
* Converts the table data into a JSON array where each object represents a row.
|
|
756
|
+
* Keys are header names, values are cell contents.
|
|
757
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
758
|
+
* @returns A promise that resolves to an array of objects representing table rows.
|
|
759
|
+
* @throws Error if no header rows or body rows found.
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* // Convert table to JSON with default options
|
|
763
|
+
* const data = await table.getJson();
|
|
764
|
+
* // Result: [{"Name": "John", "Age": "30"}, {"Name": "Jane", "Age": "25"}]
|
|
765
|
+
*
|
|
766
|
+
* @example
|
|
767
|
+
* // Convert with colspan enabled and duplicate suffix
|
|
768
|
+
* const data = await table.getJson({
|
|
769
|
+
* headerRowOptions: {
|
|
770
|
+
* colspan: { enabled: true, suffix: true },
|
|
771
|
+
* duplicateSuffix: true,
|
|
772
|
+
* emptyCellReplacement: true
|
|
773
|
+
* }
|
|
774
|
+
* });
|
|
470
775
|
*/
|
|
471
776
|
async getJson(options) {
|
|
472
|
-
|
|
473
|
-
await this.load({
|
|
474
|
-
timeout: options == null ? void 0 : options.timeout,
|
|
777
|
+
const table = await this.getTable({
|
|
475
778
|
bodyRowOptions: options == null ? void 0 : options.bodyRowOptions,
|
|
476
|
-
headerRowOptions: {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
779
|
+
headerRowOptions: this.resolveHeaderRowOptions(options == null ? void 0 : options.headerRowOptions, {
|
|
780
|
+
defaultColspanEnabled: true,
|
|
781
|
+
defaultColspanSuffix: true,
|
|
782
|
+
defaultDuplicateSuffix: true,
|
|
783
|
+
defaultEmptyCellReplacement: true,
|
|
784
|
+
defaultNormalizeWhitespace: false,
|
|
785
|
+
defaultStripIconGlyphs: false
|
|
786
|
+
})
|
|
484
787
|
});
|
|
485
|
-
const headers = this.mainHeaderRow();
|
|
486
|
-
return
|
|
788
|
+
const headers = this.mainHeaderRow(table.headerRows);
|
|
789
|
+
return table.bodyRows.map((row) => {
|
|
487
790
|
const rowObj = {};
|
|
488
791
|
headers.forEach((header, index) => {
|
|
489
792
|
rowObj[header] = row[index] || "";
|
|
@@ -492,44 +795,322 @@ var PlaywrightTable = class {
|
|
|
492
795
|
});
|
|
493
796
|
}
|
|
494
797
|
/**
|
|
495
|
-
* Waits for the
|
|
496
|
-
* @param options -
|
|
497
|
-
* @returns A promise that resolves when
|
|
798
|
+
* Waits for the table body to be empty (no body rows).
|
|
799
|
+
* @param options - Polling options (timeout, interval, retries).
|
|
800
|
+
* @returns A promise that resolves when table body is empty.
|
|
801
|
+
* @throws Error if table is not empty within timeout period.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* // Wait for table to clear after delete operation
|
|
805
|
+
* await table.waitForEmpty({ timeout: 5000 });
|
|
806
|
+
*
|
|
807
|
+
* @see {@link waitForNonEmpty} for waiting until table has data
|
|
808
|
+
*
|
|
809
|
+
* @remarks
|
|
810
|
+
* **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:
|
|
811
|
+
* ```ts
|
|
812
|
+
* await expect.poll(async () => (await table.getBodyRows()).length).toBe(0);
|
|
813
|
+
* ```
|
|
498
814
|
*/
|
|
499
|
-
async
|
|
500
|
-
await (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
815
|
+
async waitForEmpty(options) {
|
|
816
|
+
await pollTable(async () => {
|
|
817
|
+
const rows = await this._bodyRowLocator.all();
|
|
818
|
+
if (rows.length > 0) {
|
|
819
|
+
throw new Error(
|
|
820
|
+
`Expected table to be empty, but found ${rows.length} body rows.
|
|
821
|
+
Body row locator: ${this._bodyRowLocator.toString()}`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}, options);
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Waits for the table body to be non-empty (at least one body row with content).
|
|
828
|
+
* A row is considered valid if it has at least one cell containing text.
|
|
829
|
+
* @param options - Polling options (timeout, interval, retries).
|
|
830
|
+
* @returns A promise that resolves when table has at least one body row with content.
|
|
831
|
+
* @throws Error if table remains empty or has no rows with content within timeout period.
|
|
832
|
+
*
|
|
833
|
+
* @example
|
|
834
|
+
* // Wait for table to populate after search
|
|
835
|
+
* await table.waitForNonEmpty({ timeout: 5000 });
|
|
836
|
+
*
|
|
837
|
+
* @see {@link waitForEmpty} for waiting until table is cleared
|
|
838
|
+
*
|
|
839
|
+
* @remarks
|
|
840
|
+
* **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:
|
|
841
|
+
* ```ts
|
|
842
|
+
* await expect.poll(async () => (await table.getBodyRows()).length).toBeGreaterThan(0);
|
|
843
|
+
* ```
|
|
844
|
+
*/
|
|
845
|
+
async waitForNonEmpty(options) {
|
|
846
|
+
await pollTable(async () => {
|
|
847
|
+
const rows = await this._bodyRowLocator.all();
|
|
848
|
+
if (rows.length === 0) {
|
|
849
|
+
throw new Error(
|
|
850
|
+
`Expected table to be non-empty, but found 0 body rows.
|
|
851
|
+
Body row locator: ${this._bodyRowLocator.toString()}`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
let hasRowWithContent = false;
|
|
855
|
+
for (const row of rows) {
|
|
856
|
+
const cells = await row.locator(this._bodyRowColumnSelector).all();
|
|
857
|
+
for (const cell of cells) {
|
|
858
|
+
const content = await cell.innerText();
|
|
859
|
+
if (content.trim().length > 0) {
|
|
860
|
+
hasRowWithContent = true;
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (hasRowWithContent) break;
|
|
865
|
+
}
|
|
866
|
+
if (!hasRowWithContent) {
|
|
867
|
+
throw new Error(
|
|
868
|
+
`Expected table to have at least one row with content, but all ${rows.length} rows are empty.
|
|
869
|
+
Body row locator: ${this._bodyRowLocator.toString()}`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
}, options);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Waits for the table body to have exactly N rows.
|
|
876
|
+
* @param count - The exact number of body rows expected.
|
|
877
|
+
* @param options - Polling options (timeout, interval, retries).
|
|
878
|
+
* @returns A promise that resolves when the table has exactly the specified number of rows.
|
|
879
|
+
* @throws Error if row count doesn't match within timeout period.
|
|
880
|
+
*
|
|
881
|
+
* @example
|
|
882
|
+
* // Wait for exactly 10 rows after pagination
|
|
883
|
+
* await table.waitForExactRowCount(10, { timeout: 5000 });
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* // Verify table has exactly 0 rows (alternative to waitForEmpty)
|
|
887
|
+
* await table.waitForExactRowCount(0);
|
|
888
|
+
*
|
|
889
|
+
* @see {@link getRowCount} for getting current row counts
|
|
890
|
+
*
|
|
891
|
+
* @remarks
|
|
892
|
+
* **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:
|
|
893
|
+
* ```ts
|
|
894
|
+
* await expect.poll(async () => (await table.getBodyRows()).length).toBe(10);
|
|
895
|
+
* ```
|
|
896
|
+
*/
|
|
897
|
+
async waitForExactRowCount(count, options) {
|
|
898
|
+
if (count < 0 || !Number.isInteger(count)) {
|
|
899
|
+
throw new Error(`Row count must be a non-negative integer, got: ${count}`);
|
|
900
|
+
}
|
|
901
|
+
await pollTable(async () => {
|
|
902
|
+
const rows = await this._bodyRowLocator.all();
|
|
903
|
+
if (rows.length !== count) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
`Expected exactly ${count} rows, but found ${rows.length}.
|
|
906
|
+
Body row locator: ${this._bodyRowLocator.toString()}`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}, options);
|
|
505
910
|
}
|
|
506
911
|
/**
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
* @returns A promise that resolves
|
|
912
|
+
* Gets the count of header and body rows in the table.
|
|
913
|
+
* Lightweight method that doesn't fetch cell data, only counts rows.
|
|
914
|
+
* @returns A promise that resolves to an object with header and body row counts.
|
|
915
|
+
*
|
|
916
|
+
* @example
|
|
917
|
+
* // Get current row counts
|
|
918
|
+
* const counts = await table.getRowCount();
|
|
919
|
+
* console.log(`Headers: ${counts.header}, Body: ${counts.body}`);
|
|
920
|
+
*
|
|
921
|
+
* @example
|
|
922
|
+
* // Assert specific row count
|
|
923
|
+
* const { body } = await table.getRowCount();
|
|
924
|
+
* expect(body).toBe(10);
|
|
510
925
|
*/
|
|
511
|
-
async
|
|
512
|
-
await (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
926
|
+
async getRowCount() {
|
|
927
|
+
const [headerRows, bodyRows] = await Promise.all([this._headerRowLocator.all(), this._bodyRowLocator.all()]);
|
|
928
|
+
return {
|
|
929
|
+
header: headerRows.length,
|
|
930
|
+
body: bodyRows.length
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Gets all distinct (unique) values from a specific column.
|
|
935
|
+
* Returns a sorted array of unique string values, excluding empty values.
|
|
936
|
+
* @param headerName - The header name of the column to extract distinct values from.
|
|
937
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
938
|
+
* @returns A promise that resolves to a sorted array of distinct values.
|
|
939
|
+
* @throws Error if the specified header is not found in the table.
|
|
940
|
+
*
|
|
941
|
+
* @example
|
|
942
|
+
* // Get all unique status values
|
|
943
|
+
* const statuses = await table.getDistinctColumnValues("Status");
|
|
944
|
+
* // Result: ["Active", "Inactive", "Pending"]
|
|
945
|
+
*
|
|
946
|
+
* @example
|
|
947
|
+
* // Get distinct countries for filtering
|
|
948
|
+
* const countries = await table.getDistinctColumnValues("Country");
|
|
949
|
+
* console.log(`Available countries: ${countries.join(", ")}`);
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* // Verify expected values exist
|
|
953
|
+
* const roles = await table.getDistinctColumnValues("Role");
|
|
954
|
+
* expect(roles).toContain("Admin");
|
|
955
|
+
* expect(roles).toHaveLength(3);
|
|
956
|
+
*
|
|
957
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cell locators in a column
|
|
958
|
+
* @see {@link getJson} for getting full table data
|
|
959
|
+
*/
|
|
960
|
+
async getDistinctColumnValues(headerName, options) {
|
|
961
|
+
var _a;
|
|
962
|
+
const table = await this.getTable(options);
|
|
963
|
+
const mainHeader = this.mainHeaderRow(table.headerRows);
|
|
964
|
+
const headerIndex = this.getHeaderIndex(headerName, mainHeader);
|
|
965
|
+
const values = /* @__PURE__ */ new Set();
|
|
966
|
+
for (const row of table.bodyRows) {
|
|
967
|
+
const value = String((_a = row[headerIndex]) != null ? _a : "").trim();
|
|
968
|
+
if (value) values.add(value);
|
|
969
|
+
}
|
|
970
|
+
return Array.from(values).sort();
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Finds the index of the first body row matching the specified conditions.
|
|
974
|
+
* Returns -1 if no matching row is found.
|
|
975
|
+
* @param conditions - Record of header names and expected cell values to match.
|
|
976
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
977
|
+
* @returns A promise that resolves to the 0-based index of the first matching row, or -1 if not found.
|
|
978
|
+
* @throws Error if specified headers don't exist in the table.
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* // Find index of row where Status is "Active"
|
|
982
|
+
* const index = await table.findRowIndex({ Status: "Active" });
|
|
983
|
+
* if (index >= 0) {
|
|
984
|
+
* console.log(`Found at row ${index}`);
|
|
985
|
+
* }
|
|
986
|
+
*
|
|
987
|
+
* @example
|
|
988
|
+
* // Find row and interact with it
|
|
989
|
+
* const rowIndex = await table.findRowIndex({ Username: "john.doe" });
|
|
990
|
+
* if (rowIndex >= 0) {
|
|
991
|
+
* const cell = table.getBodyCellLocator(rowIndex, 0);
|
|
992
|
+
* await cell.click();
|
|
993
|
+
* }
|
|
994
|
+
*
|
|
995
|
+
* @see {@link getBodyCellLocatorByRowConditions} for getting cell locators from matching rows
|
|
996
|
+
* @see {@link waitForRowByConditions} for waiting until matching row appears
|
|
997
|
+
*/
|
|
998
|
+
async findRowIndex(conditions, options) {
|
|
999
|
+
var _a;
|
|
1000
|
+
const table = await this.getTable({
|
|
1001
|
+
headerRowOptions: this.resolveHeaderRowOptions(options == null ? void 0 : options.headerRowOptions, {
|
|
1002
|
+
forceColspanEnabled: true
|
|
1003
|
+
}),
|
|
1004
|
+
bodyRowOptions: options == null ? void 0 : options.bodyRowOptions
|
|
516
1005
|
});
|
|
1006
|
+
const mainHeader = this.mainHeaderRow(table.headerRows);
|
|
1007
|
+
const headerIndices = /* @__PURE__ */ new Map();
|
|
1008
|
+
for (const header of Object.keys(conditions)) {
|
|
1009
|
+
headerIndices.set(header, this.getHeaderIndex(header, mainHeader));
|
|
1010
|
+
}
|
|
1011
|
+
for (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {
|
|
1012
|
+
const row = table.bodyRows[rowIndex];
|
|
1013
|
+
let rowMatches = true;
|
|
1014
|
+
for (const [header, expectedValue] of Object.entries(conditions)) {
|
|
1015
|
+
const headerIndex = headerIndices.get(header);
|
|
1016
|
+
const cellValue = String((_a = row[headerIndex]) != null ? _a : "");
|
|
1017
|
+
if (cellValue !== expectedValue) {
|
|
1018
|
+
rowMatches = false;
|
|
1019
|
+
break;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (rowMatches) {
|
|
1023
|
+
return rowIndex;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return -1;
|
|
517
1027
|
}
|
|
518
|
-
async
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1028
|
+
async getTable(options) {
|
|
1029
|
+
const [headerRows, bodyRows] = await Promise.all([
|
|
1030
|
+
TableHeader.getRows(this._headerRowLocator, this._headerColumnSelector, options == null ? void 0 : options.headerRowOptions),
|
|
1031
|
+
TableBody.getRows(this._bodyRowLocator, this._bodyRowColumnSelector, options == null ? void 0 : options.bodyRowOptions)
|
|
1032
|
+
]);
|
|
1033
|
+
if (headerRows.length === 0) {
|
|
1034
|
+
throw new Error(
|
|
1035
|
+
`No header rows found, is table loaded completely?
|
|
1036
|
+
Header row locator: ${this._headerRowLocator.toString()}
|
|
1037
|
+
Table locator: ${this._tableLocator.toString()}`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
if (bodyRows.length === 0) {
|
|
1041
|
+
throw new Error(
|
|
1042
|
+
`No body rows found, is table loaded completely?
|
|
1043
|
+
Body row locator: ${this._bodyRowLocator.toString()}
|
|
1044
|
+
Table locator: ${this._tableLocator.toString()}`
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
return { headerRows, bodyRows };
|
|
1048
|
+
}
|
|
1049
|
+
normalizeGetBodyRowsOptions(options) {
|
|
1050
|
+
if (!options) {
|
|
1051
|
+
return {};
|
|
1052
|
+
}
|
|
1053
|
+
if (options.bodyRowOptions || options.headerRowOptions) {
|
|
1054
|
+
return options;
|
|
1055
|
+
}
|
|
1056
|
+
if (options.cellContentType !== void 0) {
|
|
1057
|
+
return {
|
|
1058
|
+
bodyRowOptions: {
|
|
1059
|
+
cellContentType: options.cellContentType
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
return options;
|
|
529
1064
|
}
|
|
530
|
-
|
|
531
|
-
var _a, _b;
|
|
532
|
-
|
|
1065
|
+
resolveHeaderRowOptions(headerOptions, defaults) {
|
|
1066
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1067
|
+
const resolvedColspanEnabled = (defaults == null ? void 0 : defaults.forceColspanEnabled) ? true : (_b = (_a = headerOptions == null ? void 0 : headerOptions.colspan) == null ? void 0 : _a.enabled) != null ? _b : defaults == null ? void 0 : defaults.defaultColspanEnabled;
|
|
1068
|
+
return {
|
|
1069
|
+
cellContentType: headerOptions == null ? void 0 : headerOptions.cellContentType,
|
|
1070
|
+
emptyCellReplacement: (_c = headerOptions == null ? void 0 : headerOptions.emptyCellReplacement) != null ? _c : defaults == null ? void 0 : defaults.defaultEmptyCellReplacement,
|
|
1071
|
+
duplicateSuffix: (_d = headerOptions == null ? void 0 : headerOptions.duplicateSuffix) != null ? _d : defaults == null ? void 0 : defaults.defaultDuplicateSuffix,
|
|
1072
|
+
normalizeWhitespace: (_e = headerOptions == null ? void 0 : headerOptions.normalizeWhitespace) != null ? _e : defaults == null ? void 0 : defaults.defaultNormalizeWhitespace,
|
|
1073
|
+
stripIconGlyphs: (_f = headerOptions == null ? void 0 : headerOptions.stripIconGlyphs) != null ? _f : defaults == null ? void 0 : defaults.defaultStripIconGlyphs,
|
|
1074
|
+
colspan: {
|
|
1075
|
+
enabled: resolvedColspanEnabled,
|
|
1076
|
+
suffix: (_h = (_g = headerOptions == null ? void 0 : headerOptions.colspan) == null ? void 0 : _g.suffix) != null ? _h : defaults == null ? void 0 : defaults.defaultColspanSuffix
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
mainHeaderRow(headers) {
|
|
1081
|
+
var _a, _b, _c;
|
|
1082
|
+
if (headers.length === 0) {
|
|
1083
|
+
throw new Error(
|
|
1084
|
+
`No header rows available. Ensure table has been loaded.
|
|
1085
|
+
Header row locator: ${this._headerRowLocator.toString()}`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
const headerIndex = (_c = (_b = (_a = this._options) == null ? void 0 : _a.header) == null ? void 0 : _b.setMainHeaderRow) != null ? _c : headers.length - 1;
|
|
1089
|
+
if (headerIndex < 0 || headerIndex >= headers.length) {
|
|
1090
|
+
throw new Error(
|
|
1091
|
+
`Header row index ${headerIndex} out of bounds. Table has ${headers.length} header rows.
|
|
1092
|
+
Header row locator: ${this._headerRowLocator.toString()}`
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
return headers[headerIndex];
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Gets the index of a header in the main header row, throwing an error if not found.
|
|
1099
|
+
* @param header - The header name to find.
|
|
1100
|
+
* @param mainHeader - The main header row to search in.
|
|
1101
|
+
* @returns The 0-based index of the header.
|
|
1102
|
+
* @throws Error if the header is not found.
|
|
1103
|
+
*/
|
|
1104
|
+
getHeaderIndex(header, mainHeader) {
|
|
1105
|
+
const index = mainHeader.indexOf(header);
|
|
1106
|
+
if (index === -1) {
|
|
1107
|
+
throw new Error(
|
|
1108
|
+
`Header "${header}" not found.
|
|
1109
|
+
Available headers: [${mainHeader.join(", ")}]
|
|
1110
|
+
Header row locator: ${this._headerRowLocator.toString()}`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
return index;
|
|
533
1114
|
}
|
|
534
1115
|
};
|
|
535
1116
|
// Annotate the CommonJS export names for ESM import in node:
|