@cerios/playwright-table 2.0.0 → 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/README.md CHANGED
@@ -140,6 +140,8 @@ console.log(headers);
140
140
  cellContentType?: CellContentType.InnerText | CellContentType.TextContent, // Default: InnerText
141
141
  emptyCellReplacement?: boolean, // Replace empty cells with "{{Empty}}" (default: false)
142
142
  duplicateSuffix?: boolean, // Add suffix to duplicate headers (default: false)
143
+ normalizeWhitespace?: boolean, // Collapse internal whitespace/newlines (default: false)
144
+ stripIconGlyphs?: boolean, // Remove icon-font private-use glyphs from headers (default: false)
143
145
  colspan?: {
144
146
  enabled?: boolean, // Enable colspan parsing (default: false)
145
147
  suffix?: boolean // Add suffix to colspan cells (default: false)
@@ -185,7 +187,7 @@ Retrieves all body rows with support for rowspan and colspan.
185
187
 
186
188
  **Parameters:**
187
189
 
188
- - `options?: { cellContentType?: CellContentType }` - Content extraction type
190
+ - `options?: TableOptions` - Full table options (`bodyRowOptions`, `headerRowOptions`) and legacy `cellContentType` shortcut
189
191
 
190
192
  **Returns:** `Promise<BodyRow[]>` - Array of body rows
191
193
 
@@ -194,9 +196,20 @@ Retrieves all body rows with support for rowspan and colspan.
194
196
  **Example:**
195
197
 
196
198
  ```ts
199
+ // Legacy shortcut
197
200
  const rows = await table.getBodyRows({
198
201
  cellContentType: CellContentType.TextContent, // Include hidden elements
199
202
  });
203
+
204
+ // Full table options (useful for custom grids with noisy headers)
205
+ const rowsWithOptions = await table.getBodyRows({
206
+ bodyRowOptions: { cellContentType: CellContentType.InnerText },
207
+ headerRowOptions: {
208
+ normalizeWhitespace: true,
209
+ stripIconGlyphs: true,
210
+ },
211
+ });
212
+
200
213
  console.log(rows);
201
214
  // Output: [["John", "30", "NYC"], ["Jane", "25", "LA"]]
202
215
  ```
@@ -206,6 +219,8 @@ console.log(rows);
206
219
  - `CellContentType.InnerText` - Rendered text (default, excludes hidden elements)
207
220
  - `CellContentType.TextContent` - Raw text content (includes hidden elements)
208
221
 
222
+ Header sanitization options (`normalizeWhitespace`, `stripIconGlyphs`) can be used in all methods that accept `HeaderRowOptions` or `TableOptions`, such as `getHeaderRows`, `getMainHeaderRow`, `getJson`, `findRowIndex`, and `getBodyCellLocatorByRowConditions`.
223
+
209
224
  ---
210
225
 
211
226
  ### 4. `getBodyCellLocator(rowNumber, headerPosition)`
@@ -669,6 +684,25 @@ const divTable = new PlaywrightTable(page.locator(".divTable"), {
669
684
  });
670
685
  ```
671
686
 
687
+ ### ARIA Treegrid Tables
688
+
689
+ For `role="treegrid"` structures, make sure header and body row selectors are scoped to different containers.
690
+ If both use the same selector (for example `div[role='row']`), header rows can be included in body rows.
691
+
692
+ ```ts
693
+ const table = new PlaywrightTable(page.locator("[role='treegrid']"), {
694
+ header: {
695
+ setMainHeaderRow: 0,
696
+ rowSelector: "[data-kind='header-group'] > div[role='row']",
697
+ columnSelector: "div[role='columnheader']",
698
+ },
699
+ row: {
700
+ rowSelector: "[data-kind='body-group'] > div[role='row']",
701
+ columnSelector: "div[role='gridcell']",
702
+ },
703
+ });
704
+ ```
705
+
672
706
  ### Header Row Options
673
707
 
674
708
  Control how headers are processed:
@@ -678,6 +712,8 @@ const headers = await table.getHeaderRows({
678
712
  cellContentType: CellContentType.TextContent, // Include hidden text
679
713
  emptyCellReplacement: true, // Replace "" with "{{Empty}}"
680
714
  duplicateSuffix: true, // "Name", "Name__D1", "Name__D2"
715
+ normalizeWhitespace: true, // "Account\nName" -> "Account Name"
716
+ stripIconGlyphs: true, // Removes private-use icon glyph chars from header text
681
717
  colspan: {
682
718
  enabled: true, // Process colspan attributes
683
719
  suffix: true, // "Col", "Col__C1", "Col__C2"
package/dist/index.d.mts CHANGED
@@ -61,6 +61,8 @@ type HeaderRowOptions = {
61
61
  enabled?: boolean;
62
62
  suffix?: boolean;
63
63
  };
64
+ normalizeWhitespace?: boolean;
65
+ stripIconGlyphs?: boolean;
64
66
  };
65
67
 
66
68
  /**
@@ -85,6 +87,8 @@ type TableOptions = {
85
87
  bodyRowOptions?: {
86
88
  cellContentType?: CellContentType;
87
89
  };
90
+ /** @deprecated Use `bodyRowOptions.cellContentType` instead. */
91
+ cellContentType?: CellContentType;
88
92
  };
89
93
  /**
90
94
  * Represents a table in Playwright, providing methods to interact with its headers, rows, and cells.
@@ -172,8 +176,10 @@ declare class PlaywrightTable {
172
176
  getMainHeaderRow(options?: HeaderRowOptions): Promise<HeaderRow>;
173
177
  /**
174
178
  * Retrieves all body rows of the table.
175
- * @param options - Optional configuration for body row parsing.
176
- * @param options.cellContentType - Content extraction type. See {@link CellContentType}. Defaults to InnerText.
179
+ * @param options - Optional table or body-row configuration.
180
+ * @param options.cellContentType - Legacy shortcut for body row content extraction. See {@link CellContentType}.
181
+ * @param options.bodyRowOptions - Body row parsing options.
182
+ * @param options.headerRowOptions - Header parsing options used when resolving row/colspan layouts.
177
183
  * @returns A promise that resolves to an array of body rows.
178
184
  * @throws Error if no body rows found after table loads.
179
185
  *
@@ -186,10 +192,15 @@ declare class PlaywrightTable {
186
192
  * @example
187
193
  * // Get body rows with default InnerText
188
194
  * const rows = await table.getBodyRows();
195
+ *
196
+ * @example
197
+ * // Use full table options (for example to sanitize noisy treegrid headers)
198
+ * const rows = await table.getBodyRows({
199
+ * bodyRowOptions: { cellContentType: CellContentType.InnerText },
200
+ * headerRowOptions: { normalizeWhitespace: true, stripIconGlyphs: true }
201
+ * });
189
202
  */
190
- getBodyRows(options?: {
191
- cellContentType?: CellContentType;
192
- }): Promise<BodyRow[]>;
203
+ getBodyRows(options?: TableOptions): Promise<BodyRow[]>;
193
204
  /**
194
205
  * Retrieves a specific cell locator in the body of the table by row and column index.
195
206
  * @param rowNumber - The row number (0-based index).
@@ -457,6 +468,8 @@ declare class PlaywrightTable {
457
468
  */
458
469
  findRowIndex(conditions: Record<string, string>, options?: TableOptions): Promise<number>;
459
470
  private getTable;
471
+ private normalizeGetBodyRowsOptions;
472
+ private resolveHeaderRowOptions;
460
473
  private mainHeaderRow;
461
474
  /**
462
475
  * Gets the index of a header in the main header row, throwing an error if not found.
package/dist/index.d.ts CHANGED
@@ -61,6 +61,8 @@ type HeaderRowOptions = {
61
61
  enabled?: boolean;
62
62
  suffix?: boolean;
63
63
  };
64
+ normalizeWhitespace?: boolean;
65
+ stripIconGlyphs?: boolean;
64
66
  };
65
67
 
66
68
  /**
@@ -85,6 +87,8 @@ type TableOptions = {
85
87
  bodyRowOptions?: {
86
88
  cellContentType?: CellContentType;
87
89
  };
90
+ /** @deprecated Use `bodyRowOptions.cellContentType` instead. */
91
+ cellContentType?: CellContentType;
88
92
  };
89
93
  /**
90
94
  * Represents a table in Playwright, providing methods to interact with its headers, rows, and cells.
@@ -172,8 +176,10 @@ declare class PlaywrightTable {
172
176
  getMainHeaderRow(options?: HeaderRowOptions): Promise<HeaderRow>;
173
177
  /**
174
178
  * Retrieves all body rows of the table.
175
- * @param options - Optional configuration for body row parsing.
176
- * @param options.cellContentType - Content extraction type. See {@link CellContentType}. Defaults to InnerText.
179
+ * @param options - Optional table or body-row configuration.
180
+ * @param options.cellContentType - Legacy shortcut for body row content extraction. See {@link CellContentType}.
181
+ * @param options.bodyRowOptions - Body row parsing options.
182
+ * @param options.headerRowOptions - Header parsing options used when resolving row/colspan layouts.
177
183
  * @returns A promise that resolves to an array of body rows.
178
184
  * @throws Error if no body rows found after table loads.
179
185
  *
@@ -186,10 +192,15 @@ declare class PlaywrightTable {
186
192
  * @example
187
193
  * // Get body rows with default InnerText
188
194
  * const rows = await table.getBodyRows();
195
+ *
196
+ * @example
197
+ * // Use full table options (for example to sanitize noisy treegrid headers)
198
+ * const rows = await table.getBodyRows({
199
+ * bodyRowOptions: { cellContentType: CellContentType.InnerText },
200
+ * headerRowOptions: { normalizeWhitespace: true, stripIconGlyphs: true }
201
+ * });
189
202
  */
190
- getBodyRows(options?: {
191
- cellContentType?: CellContentType;
192
- }): Promise<BodyRow[]>;
203
+ getBodyRows(options?: TableOptions): Promise<BodyRow[]>;
193
204
  /**
194
205
  * Retrieves a specific cell locator in the body of the table by row and column index.
195
206
  * @param rowNumber - The row number (0-based index).
@@ -457,6 +468,8 @@ declare class PlaywrightTable {
457
468
  */
458
469
  findRowIndex(conditions: Record<string, string>, options?: TableOptions): Promise<number>;
459
470
  private getTable;
471
+ private normalizeGetBodyRowsOptions;
472
+ private resolveHeaderRowOptions;
460
473
  private mainHeaderRow;
461
474
  /**
462
475
  * Gets the index of a header in the main header row, throwing an error if not found.
package/dist/index.js CHANGED
@@ -318,14 +318,16 @@ Error: ${error instanceof Error ? error.message : String(error)}`
318
318
  return headerRows;
319
319
  }
320
320
  static getDefaultOptions(options) {
321
- var _a, _b, _c, _d, _e, _f, _g;
321
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
322
322
  return {
323
323
  cellContentType: (_a = options == null ? void 0 : options.cellContentType) != null ? _a : "innerText" /* InnerText */,
324
324
  emptyCellReplacement: (_b = options == null ? void 0 : options.emptyCellReplacement) != null ? _b : false,
325
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,
326
328
  colspan: {
327
- enabled: (_e = (_d = options == null ? void 0 : options.colspan) == null ? void 0 : _d.enabled) != null ? _e : false,
328
- suffix: (_g = (_f = options == null ? void 0 : options.colspan) == null ? void 0 : _f.suffix) != null ? _g : false
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
329
331
  }
330
332
  };
331
333
  }
@@ -335,7 +337,11 @@ Error: ${error instanceof Error ? error.message : String(error)}`
335
337
  if (cells.length === 0) {
336
338
  return headerRow;
337
339
  }
338
- const cellDataArray = await Promise.all(cells.map((cell) => this.fetchHeaderCellData(cell, options.cellContentType)));
340
+ const cellDataArray = await Promise.all(
341
+ cells.map(
342
+ (cell) => this.fetchHeaderCellData(cell, options.cellContentType, options.normalizeWhitespace, options.stripIconGlyphs)
343
+ )
344
+ );
339
345
  let columnIndex = 0;
340
346
  for (let i = 0; i < cellDataArray.length; i++) {
341
347
  columnIndex = this.processHeaderCellData(
@@ -353,17 +359,27 @@ Error: ${error instanceof Error ? error.message : String(error)}`
353
359
  * Fetches header cell content and span attributes in parallel.
354
360
  * This method performs DOM operations that can be parallelized across multiple cells.
355
361
  */
356
- static async fetchHeaderCellData(cell, cellContentType) {
362
+ static async fetchHeaderCellData(cell, cellContentType, normalizeWhitespace, stripIconGlyphs) {
357
363
  const [text, spanAttributes] = await Promise.all([
358
364
  TableUtils.getCellContent(cell, cellContentType),
359
365
  TableUtils.parseSpanAttributes(cell)
360
366
  ]);
361
367
  return {
362
- text,
368
+ text: this.sanitizeHeaderText(text, normalizeWhitespace, stripIconGlyphs),
363
369
  rowspan: spanAttributes.rowspan,
364
370
  colspan: spanAttributes.colspan
365
371
  };
366
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
+ }
367
383
  /**
368
384
  * Processes pre-fetched header cell data and applies it to the header row.
369
385
  * This method is purely computational and doesn't perform any DOM operations.
@@ -446,6 +462,7 @@ TableHeader.EMPTY_CELL_PLACEHOLDER = "{{Empty}}";
446
462
  TableHeader.COLSPAN_SUFFIX_PREFIX = "__C";
447
463
  TableHeader.DUPLICATE_SUFFIX_PREFIX = "__D";
448
464
  TableHeader.COLSPAN_PATTERN = /__C\d+/;
465
+ TableHeader.ICON_GLYPH_PATTERN = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/gu;
449
466
 
450
467
  // src/playwright-table.ts
451
468
  var PlaywrightTable = class {
@@ -480,11 +497,18 @@ var PlaywrightTable = class {
480
497
  constructor(_tableLocator, _options) {
481
498
  this._tableLocator = _tableLocator;
482
499
  this._options = _options;
483
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
484
- this._headerRowLocator = this._tableLocator.locator((_c = (_b = (_a = this._options) == null ? void 0 : _a.header) == null ? void 0 : _b.rowSelector) != null ? _c : "thead>tr");
485
- this._headerColumnSelector = (_f = (_e = (_d = this._options) == null ? void 0 : _d.header) == null ? void 0 : _e.columnSelector) != null ? _f : "th";
486
- this._bodyRowLocator = this._tableLocator.locator((_i = (_h = (_g = this._options) == null ? void 0 : _g.row) == null ? void 0 : _h.rowSelector) != null ? _i : "tbody>tr");
487
- this._bodyRowColumnSelector = (_l = (_k = (_j = this._options) == null ? void 0 : _j.row) == null ? void 0 : _k.columnSelector) != null ? _l : "td";
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";
488
512
  }
489
513
  /**
490
514
  * Retrieves all header rows of the table.
@@ -533,8 +557,10 @@ var PlaywrightTable = class {
533
557
  }
534
558
  /**
535
559
  * Retrieves all body rows of the table.
536
- * @param options - Optional configuration for body row parsing.
537
- * @param options.cellContentType - Content extraction type. See {@link CellContentType}. Defaults to InnerText.
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.
538
564
  * @returns A promise that resolves to an array of body rows.
539
565
  * @throws Error if no body rows found after table loads.
540
566
  *
@@ -547,13 +573,21 @@ var PlaywrightTable = class {
547
573
  * @example
548
574
  * // Get body rows with default InnerText
549
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
+ * });
550
583
  */
551
584
  async getBodyRows(options) {
585
+ const tableOptions = this.normalizeGetBodyRowsOptions(options);
552
586
  const table = await this.getTable({
553
- bodyRowOptions: options,
554
- headerRowOptions: {
555
- colspan: { enabled: true }
556
- }
587
+ bodyRowOptions: tableOptions.bodyRowOptions,
588
+ headerRowOptions: this.resolveHeaderRowOptions(tableOptions.headerRowOptions, {
589
+ forceColspanEnabled: true
590
+ })
557
591
  });
558
592
  return table.bodyRows;
559
593
  }
@@ -740,17 +774,16 @@ Header row locator: ${this._headerRowLocator.toString()}`
740
774
  * });
741
775
  */
742
776
  async getJson(options) {
743
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
744
777
  const table = await this.getTable({
745
778
  bodyRowOptions: options == null ? void 0 : options.bodyRowOptions,
746
- headerRowOptions: {
747
- colspan: {
748
- enabled: (_c = (_b = (_a = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _a.colspan) == null ? void 0 : _b.enabled) != null ? _c : true,
749
- suffix: (_f = (_e = (_d = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _d.colspan) == null ? void 0 : _e.suffix) != null ? _f : true
750
- },
751
- duplicateSuffix: (_h = (_g = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _g.duplicateSuffix) != null ? _h : true,
752
- emptyCellReplacement: (_j = (_i = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _i.emptyCellReplacement) != null ? _j : true
753
- }
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
+ })
754
787
  });
755
788
  const headers = this.mainHeaderRow(table.headerRows);
756
789
  return table.bodyRows.map((row) => {
@@ -965,7 +998,9 @@ Body row locator: ${this._bodyRowLocator.toString()}`
965
998
  async findRowIndex(conditions, options) {
966
999
  var _a;
967
1000
  const table = await this.getTable({
968
- headerRowOptions: { colspan: { enabled: true }, ...options == null ? void 0 : options.headerRowOptions },
1001
+ headerRowOptions: this.resolveHeaderRowOptions(options == null ? void 0 : options.headerRowOptions, {
1002
+ forceColspanEnabled: true
1003
+ }),
969
1004
  bodyRowOptions: options == null ? void 0 : options.bodyRowOptions
970
1005
  });
971
1006
  const mainHeader = this.mainHeaderRow(table.headerRows);
@@ -1011,6 +1046,37 @@ Table locator: ${this._tableLocator.toString()}`
1011
1046
  }
1012
1047
  return { headerRows, bodyRows };
1013
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;
1064
+ }
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
+ }
1014
1080
  mainHeaderRow(headers) {
1015
1081
  var _a, _b, _c;
1016
1082
  if (headers.length === 0) {