@cerios/playwright-table 2.0.0 → 2.0.2
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 +37 -1
- package/dist/index.d.mts +20 -5
- package/dist/index.d.ts +20 -5
- package/dist/index.js +106 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +106 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/polling.ts","../src/cell-content-type.ts","../src/table-utils.ts","../src/table-body.ts","../src/table-header.ts","../src/playwright-table.ts"],"sourcesContent":["export * from \"./playwright-table\";\nexport * from \"./row\";\nexport { HeaderRowOptions } from \"./table-header\";\nexport { CellContentType } from \"./cell-content-type\";\nexport { PollingOptions } from \"./polling\";\n","export type PollingOptions = {\n\ttimeout?: number;\n\tinterval?: number;\n};\n\nexport async function pollTable(fn: () => Promise<void>, options?: PollingOptions): Promise<void> {\n\tif (!options) {\n\t\toptions = {};\n\t}\n\n\tconst timeout = options.timeout ?? 30_000;\n\tconst interval = options.interval ?? 100;\n\tconst startTime = Date.now();\n\tlet lastError: Error | undefined;\n\n\twhile (Date.now() - startTime < timeout) {\n\t\ttry {\n\t\t\tawait fn();\n\t\t\treturn;\n\t\t} catch (err) {\n\t\t\tlastError = err as Error;\n\n\t\t\tawait new Promise(resolve => setTimeout(resolve, interval));\n\t\t}\n\t}\n\n\tthrow lastError || new Error(\"Playwright Tables: Polling timeout reached\");\n}\n","/**\n * Enum representing the possible types of content that can be extracted from a table cell.\n *\n * - `TextContent`: The raw text content of the cell, including hidden elements and extra whitespace.\n * - `InnerText`: The rendered text content of the cell, as seen by the user, with whitespace collapsed and hidden elements excluded.\n */\nexport enum CellContentType {\n\tTextContent = \"textContent\",\n\tInnerText = \"innerText\",\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\n\n/**\n * Utility class for extracting table cell content and attributes.\n * Contains only static methods - cannot be instantiated.\n */\nexport class TableUtils {\n\tprivate constructor() {\n\t\t// Prevent instantiation - this class only has static methods\n\t}\n\t/**\n\t * Extracts text content from a table cell.\n\t * @param cell - The Playwright locator for the cell.\n\t * @param cellContentType - The type of content to extract (InnerText or TextContent).\n\t * @returns The trimmed text content of the cell, or empty string if no content.\n\t * @throws Error if the cell locator doesn't exist in the DOM.\n\t */\n\tstatic async getCellContent(cell: Locator, cellContentType: CellContentType): Promise<string> {\n\t\ttry {\n\t\t\tconst count = await cell.count();\n\t\t\tif (count === 0) {\n\t\t\t\tthrow new Error(\"Cell locator does not exist in the DOM\");\n\t\t\t}\n\n\t\t\tconst content = cellContentType === CellContentType.InnerText ? await cell.innerText() : await cell.textContent();\n\t\t\treturn content?.trim() || \"\";\n\t\t} catch (error) {\n\t\t\tconst locatorDescription = this.getLocatorDescription(cell);\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to get cell content.\\n` +\n\t\t\t\t\t`Locator: ${locatorDescription}\\n` +\n\t\t\t\t\t`Content type: ${cellContentType}\\n` +\n\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Parses rowspan and colspan attributes from a table cell.\n\t * Fetches both attributes in parallel for optimal performance.\n\t * @param cell - The Playwright locator for the cell.\n\t * @returns Object containing rowspan and colspan values (defaults to 1 if not present).\n\t * @throws Error if attributes contain invalid values (non-numeric or less than 1).\n\t */\n\tstatic async parseSpanAttributes(cell: Locator): Promise<{ rowspan: number; colspan: number }> {\n\t\ttry {\n\t\t\tconst [rowspanAttr, colspanAttr] = await Promise.all([\n\t\t\t\tcell.getAttribute(\"rowspan\"),\n\t\t\t\tcell.getAttribute(\"colspan\"),\n\t\t\t]);\n\n\t\t\tconst rowspan = parseInt(rowspanAttr || \"1\", 10);\n\t\t\tconst colspan = parseInt(colspanAttr || \"1\", 10);\n\n\t\t\tif (isNaN(rowspan) || rowspan < 1) {\n\t\t\t\tthrow new Error(`Invalid rowspan attribute: \"${rowspanAttr}\". Expected positive integer.`);\n\t\t\t}\n\t\t\tif (isNaN(colspan) || colspan < 1) {\n\t\t\t\tthrow new Error(`Invalid colspan attribute: \"${colspanAttr}\". Expected positive integer.`);\n\t\t\t}\n\n\t\t\treturn { rowspan, colspan };\n\t\t} catch (error) {\n\t\t\tconst locatorDescription = this.getLocatorDescription(cell);\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to parse span attributes.\\n` +\n\t\t\t\t\t`Locator: ${locatorDescription}\\n` +\n\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Extracts a human-readable locator description from a Playwright Locator.\n\t * This captures Playwright's internal selector chain for better error messages.\n\t * @param locator - The Playwright locator to describe.\n\t * @returns A string representation of the locator's selector chain.\n\t */\n\tprivate static getLocatorDescription(locator: Locator): string {\n\t\tconst locatorString = locator.toString();\n\t\t// Example: \"Locator@#root >> table >> tbody >> tr >> td\"\n\t\treturn locatorString;\n\t}\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\nimport { BodyRow, Cell } from \"./row\";\nimport { TableUtils } from \"./table-utils\";\n\n/**\n * Utility class for extracting and processing table body rows.\n * Handles rowspan and colspan attributes to properly structure table data.\n * Contains only static methods - cannot be instantiated.\n */\nexport class TableBody {\n\tprivate constructor() {\n\t\t// Prevent instantiation - this class only has static methods\n\t}\n\t/**\n\t * Extracts body rows from a table with proper handling of rowspan and colspan.\n\t *\n\t * @param rowLocator - The Playwright locator for body rows.\n\t * @param columnsSelector - CSS selector for locating columns within rows (e.g., \"td\").\n\t * @param bodyRowsOptions - Optional configuration for cell content extraction.\n\t * @param bodyRowsOptions.cellContentType - The type of content to extract (InnerText or TextContent).\n\t * @returns Promise resolving to an array of processed body rows.\n\t * @throws Error if columnsSelector is empty or if row processing fails.\n\t *\n\t * @example\n\t * const rows = await TableBody.getRows(\n\t * page.locator('tbody tr'),\n\t * 'td',\n\t * { cellContentType: CellContentType.TextContent }\n\t * );\n\t */\n\tstatic async getRows(\n\t\trowLocator: Locator,\n\t\tcolumnsSelector: string,\n\t\tbodyRowsOptions?: {\n\t\t\tcellContentType?: CellContentType;\n\t\t}\n\t): Promise<BodyRow[]> {\n\t\tif (!columnsSelector || columnsSelector.trim() === \"\") {\n\t\t\tthrow new Error(\"columnsSelector cannot be empty\");\n\t\t}\n\n\t\tconst locatorDescription = rowLocator.toString();\n\t\tconst rows: BodyRow[] = [];\n\t\tconst rowsCount = await rowLocator.count();\n\n\t\tif (rowsCount === 0) {\n\t\t\treturn rows;\n\t\t}\n\n\t\tconst spannedCells: Record<number, Cell[]> = {};\n\n\t\tfor (let rowIndex = 0; rowIndex < rowsCount; rowIndex++) {\n\t\t\ttry {\n\t\t\t\tconst row = rowLocator.nth(rowIndex);\n\t\t\t\tconst columns = await this.extractColumns(\n\t\t\t\t\trow,\n\t\t\t\t\tcolumnsSelector,\n\t\t\t\t\trowIndex,\n\t\t\t\t\tspannedCells,\n\t\t\t\t\tbodyRowsOptions?.cellContentType\n\t\t\t\t);\n\t\t\t\trows.push(columns as BodyRow);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to process body row at index ${rowIndex}.\\n` +\n\t\t\t\t\t\t`Body row locator: ${locatorDescription}\\n` +\n\t\t\t\t\t\t`Columns selector: \"${columnsSelector}\"\\n` +\n\t\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn rows;\n\t}\n\n\tprivate static async extractColumns(\n\t\trow: Locator,\n\t\tcolumnsSelector: string,\n\t\trowIndex: number,\n\t\tspannedCells: Record<number, Cell[]>,\n\t\tcellContentType?: CellContentType\n\t): Promise<Cell[]> {\n\t\tconst columns: Cell[] = [];\n\t\tconst columnLocators = row.locator(columnsSelector);\n\t\tconst columnCount = await columnLocators.count();\n\n\t\tif (columnCount === 0) {\n\t\t\t// Still apply any spanned cells even if this row has no DOM cells\n\t\t\tthis.applySpannedCells(spannedCells, rowIndex, columns);\n\t\t\treturn columns;\n\t\t}\n\n\t\tconst cellDataPromises = [];\n\t\tfor (let colIndex = 0; colIndex < columnCount; colIndex++) {\n\t\t\tcellDataPromises.push(this.fetchCellData(columnLocators.nth(colIndex), cellContentType));\n\t\t}\n\t\tconst cellDataArray = await Promise.all(cellDataPromises);\n\n\t\t// First, determine which column positions are occupied by rowspans from above\n\t\tconst spannedPositions = new Set<number>();\n\t\tif (spannedCells[rowIndex]) {\n\t\t\tspannedCells[rowIndex].forEach((_, colIndex) => {\n\t\t\t\tif (spannedCells[rowIndex][colIndex] !== undefined) {\n\t\t\t\t\tspannedPositions.add(colIndex);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// Process cells, accounting for spanned positions\n\t\tlet actualColIndex = 0;\n\t\tfor (let domCellIndex = 0; domCellIndex < columnCount; domCellIndex++) {\n\t\t\t// Skip columns that are occupied by rowspans from above\n\t\t\twhile (spannedPositions.has(actualColIndex)) {\n\t\t\t\tactualColIndex++;\n\t\t\t}\n\n\t\t\tconst cellData = cellDataArray[domCellIndex];\n\t\t\tthis.processCellData(cellData, actualColIndex, columns, rowIndex, spannedCells);\n\n\t\t\t// Move past this cell and any colspan it creates\n\t\t\tactualColIndex += cellData.colspan;\n\t\t}\n\n\t\tthis.applySpannedCells(spannedCells, rowIndex, columns);\n\n\t\treturn columns;\n\t}\n\n\t/**\n\t * Fetches cell content and span attributes in parallel.\n\t * This method performs DOM operations that can be parallelized across multiple cells.\n\t */\n\tprivate static async fetchCellData(\n\t\tcolumn: Locator,\n\t\tcellContentType: CellContentType = CellContentType.InnerText\n\t): Promise<{ content: string; rowspan: number; colspan: number }> {\n\t\tconst [content, spanAttributes] = await Promise.all([\n\t\t\tTableUtils.getCellContent(column, cellContentType),\n\t\t\tTableUtils.parseSpanAttributes(column),\n\t\t]);\n\n\t\treturn {\n\t\t\tcontent,\n\t\t\trowspan: spanAttributes.rowspan,\n\t\t\tcolspan: spanAttributes.colspan,\n\t\t};\n\t}\n\n\t/**\n\t * Processes pre-fetched cell data and applies it to the columns array.\n\t * This method is purely computational and doesn't perform any DOM operations.\n\t */\n\tprivate static processCellData(\n\t\tcellData: { content: string; rowspan: number; colspan: number },\n\t\tcolIndex: number,\n\t\tcolumns: Cell[],\n\t\trowIndex: number,\n\t\tspannedCells: Record<number, Cell[]>\n\t): void {\n\t\tconst { content, rowspan, colspan } = cellData;\n\n\t\tif (colIndex < 0) {\n\t\t\tthrow new Error(`Invalid column index: ${colIndex}. Must be non-negative.`);\n\t\t}\n\t\tif (rowIndex < 0) {\n\t\t\tthrow new Error(`Invalid row index: ${rowIndex}. Must be non-negative.`);\n\t\t}\n\n\t\tfor (let span = 0; span < colspan; span++) {\n\t\t\tcolumns[colIndex + span] = content;\n\t\t}\n\n\t\tif (rowspan > 1) {\n\t\t\tthis.storeSpannedCells(rowIndex, colIndex, rowspan, colspan, content, spannedCells);\n\t\t}\n\t}\n\n\tprivate static storeSpannedCells(\n\t\trowIndex: number,\n\t\tcolIndex: number,\n\t\trowspan: number,\n\t\tcolspan: number,\n\t\tcontent: Cell,\n\t\tspannedCells: Record<number, Cell[]>\n\t): void {\n\t\tfor (let rowSpan = 1; rowSpan < rowspan; rowSpan++) {\n\t\t\tif (!spannedCells[rowIndex + rowSpan]) {\n\t\t\t\tspannedCells[rowIndex + rowSpan] = [];\n\t\t\t}\n\t\t\t// Store the content for each column covered by colspan\n\t\t\tfor (let colSpan = 0; colSpan < colspan; colSpan++) {\n\t\t\t\tspannedCells[rowIndex + rowSpan][colIndex + colSpan] = content;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static applySpannedCells(spannedCells: Record<number, Cell[]>, rowIndex: number, columns: Cell[]): void {\n\t\tif (spannedCells[rowIndex]) {\n\t\t\tspannedCells[rowIndex].forEach((spannedContent, colIndex) => {\n\t\t\t\tif (spannedContent !== undefined) {\n\t\t\t\t\tcolumns[colIndex] = spannedContent;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\nimport { HeaderRow } from \"./row\";\nimport { TableUtils } from \"./table-utils\";\n\n/**\n * Options for configuring a table header row.\n *\n * @property cellContentType - The type of content allowed in the header cell.\n * @property emptyCellReplacement - If true, replaces empty header cells with a default value.\n * @property duplicateSuffix - If true, appends a suffix to duplicate header values.\n * @property colspan - Configuration for column spanning in the header row.\n * @property colspan.enabled - If true, enables column spanning.\n * @property colspan.suffix - If true, appends a suffix to spanned columns.\n */\nexport type HeaderRowOptions = {\n\tcellContentType?: CellContentType;\n\temptyCellReplacement?: boolean;\n\tduplicateSuffix?: boolean;\n\tcolspan?: { enabled?: boolean; suffix?: boolean };\n};\n\n/**\n * Utility class for extracting and processing table header rows.\n * Handles complex scenarios including colspan, rowspan, empty cells, and duplicate header names.\n * Contains only static methods - cannot be instantiated.\n */\nexport class TableHeader {\n\tprivate constructor() {\n\t\t// Prevent instantiation - this class only has static methods\n\t}\n\tprivate static readonly EMPTY_CELL_PLACEHOLDER = \"{{Empty}}\";\n\tprivate static readonly COLSPAN_SUFFIX_PREFIX = \"__C\";\n\tprivate static readonly DUPLICATE_SUFFIX_PREFIX = \"__D\";\n\tprivate static readonly COLSPAN_PATTERN = /__C\\d+/;\n\n\t/**\n\t * Extracts header rows from a table with optional processing for colspan, rowspan, and duplicates.\n\t *\n\t * @param headerRowLocator - The Playwright locator for header rows.\n\t * @param columnsSelector - CSS selector for locating header columns (e.g., \"th\").\n\t * @param headerRowOptions - Optional configuration for header processing.\n\t * @returns Promise resolving to an array of processed header rows.\n\t * @throws Error if columnsSelector is empty or if no header rows are found.\n\t *\n\t * @example\n\t * const headers = await TableHeader.getRows(\n\t * page.locator('thead tr'),\n\t * 'th',\n\t * { duplicateSuffix: true, colspan: { enabled: true } }\n\t * );\n\t */\n\tstatic async getRows(\n\t\theaderRowLocator: Locator,\n\t\tcolumnsSelector: string,\n\t\theaderRowOptions?: HeaderRowOptions\n\t): Promise<HeaderRow[]> {\n\t\tif (!columnsSelector || columnsSelector.trim() === \"\") {\n\t\t\tthrow new Error(\"columnsSelector cannot be empty\");\n\t\t}\n\n\t\tconst locatorDescription = headerRowLocator.toString();\n\t\tconst options = this.getDefaultOptions(headerRowOptions);\n\t\tconst rows = await headerRowLocator.all();\n\n\t\tconst headerRows: HeaderRow[] = [];\n\n\t\tif (rows.length === 0) {\n\t\t\treturn headerRows;\n\t\t}\n\n\t\tconst rowSpans: Map<number, string> = new Map();\n\n\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\ttry {\n\t\t\t\tconst headerRow = await this.processRow(rows[rowIndex], columnsSelector, rowSpans, options);\n\t\t\t\theaderRows.push(headerRow);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to process header row at index ${rowIndex}.\\n` +\n\t\t\t\t\t\t`Header row locator: ${locatorDescription}\\n` +\n\t\t\t\t\t\t`Columns selector: \"${columnsSelector}\"\\n` +\n\t\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.applyColspanOptions(headerRows, options);\n\t\tthis.applyDuplicateSuffix(headerRows, options);\n\n\t\treturn headerRows;\n\t}\n\n\tprivate static getDefaultOptions(options?: HeaderRowOptions): Required<HeaderRowOptions> {\n\t\treturn {\n\t\t\tcellContentType: options?.cellContentType ?? CellContentType.InnerText,\n\t\t\temptyCellReplacement: options?.emptyCellReplacement ?? false,\n\t\t\tduplicateSuffix: options?.duplicateSuffix ?? false,\n\t\t\tcolspan: {\n\t\t\t\tenabled: options?.colspan?.enabled ?? false,\n\t\t\t\tsuffix: options?.colspan?.suffix ?? false,\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate static async processRow(\n\t\trow: Locator,\n\t\tcolumnsSelector: string,\n\t\trowSpans: Map<number, string>,\n\t\toptions: Required<HeaderRowOptions>\n\t): Promise<HeaderRow> {\n\t\tconst headerRow: HeaderRow = [];\n\t\tconst cells = await row.locator(columnsSelector).all();\n\n\t\tif (cells.length === 0) {\n\t\t\treturn headerRow;\n\t\t}\n\n\t\tconst cellDataArray = await Promise.all(cells.map(cell => this.fetchHeaderCellData(cell, options.cellContentType)));\n\n\t\tlet columnIndex = 0;\n\t\tfor (let i = 0; i < cellDataArray.length; i++) {\n\t\t\tcolumnIndex = this.processHeaderCellData(\n\t\t\t\tcellDataArray[i],\n\t\t\t\theaderRow,\n\t\t\t\trowSpans,\n\t\t\t\tcolumnIndex,\n\t\t\t\toptions.emptyCellReplacement\n\t\t\t);\n\t\t}\n\n\t\tthis.addRemainingRowSpans(headerRow, rowSpans, columnIndex);\n\t\treturn headerRow;\n\t}\n\n\t/**\n\t * Fetches header cell content and span attributes in parallel.\n\t * This method performs DOM operations that can be parallelized across multiple cells.\n\t */\n\tprivate static async fetchHeaderCellData(\n\t\tcell: Locator,\n\t\tcellContentType: CellContentType\n\t): Promise<{ text: string; rowspan: number; colspan: number }> {\n\t\tconst [text, spanAttributes] = await Promise.all([\n\t\t\tTableUtils.getCellContent(cell, cellContentType),\n\t\t\tTableUtils.parseSpanAttributes(cell),\n\t\t]);\n\n\t\treturn {\n\t\t\ttext,\n\t\t\trowspan: spanAttributes.rowspan,\n\t\t\tcolspan: spanAttributes.colspan,\n\t\t};\n\t}\n\n\t/**\n\t * Processes pre-fetched header cell data and applies it to the header row.\n\t * This method is purely computational and doesn't perform any DOM operations.\n\t */\n\tprivate static processHeaderCellData(\n\t\tcellData: { text: string; rowspan: number; colspan: number },\n\t\theaderRow: HeaderRow,\n\t\trowSpans: Map<number, string>,\n\t\tcolumnIndex: number,\n\t\temptyCellReplacement: boolean\n\t): number {\n\t\twhile (rowSpans.has(columnIndex)) {\n\t\t\theaderRow.push(rowSpans.get(columnIndex)!);\n\t\t\trowSpans.delete(columnIndex);\n\t\t\tcolumnIndex++;\n\t\t}\n\n\t\tlet text = cellData.text;\n\t\tif (!text && emptyCellReplacement) {\n\t\t\ttext = this.EMPTY_CELL_PLACEHOLDER;\n\t\t}\n\n\t\tconst { colspan, rowspan } = cellData;\n\n\t\theaderRow.push(text);\n\t\tthis.handleColspan(headerRow, text, colspan);\n\t\tthis.handleRowspan(rowSpans, text, columnIndex, colspan, rowspan);\n\n\t\treturn columnIndex + colspan;\n\t}\n\n\tprivate static handleColspan(headerRow: HeaderRow, text: string, colspan: number): void {\n\t\tfor (let i = 1; i < colspan; i++) {\n\t\t\theaderRow.push(`${text}${this.COLSPAN_SUFFIX_PREFIX}${i}`);\n\t\t}\n\t}\n\n\tprivate static handleRowspan(\n\t\trowSpans: Map<number, string>,\n\t\ttext: string,\n\t\tcolumnIndex: number,\n\t\tcolspan: number,\n\t\trowspan: number\n\t): void {\n\t\tif (rowspan > 1) {\n\t\t\tfor (let i = 0; i < colspan; i++) {\n\t\t\t\trowSpans.set(columnIndex + i, text);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static addRemainingRowSpans(headerRow: HeaderRow, rowSpans: Map<number, string>, columnIndex: number): void {\n\t\twhile (rowSpans.has(columnIndex)) {\n\t\t\theaderRow.push(rowSpans.get(columnIndex)!);\n\t\t\trowSpans.delete(columnIndex);\n\t\t\tcolumnIndex++;\n\t\t}\n\t}\n\n\tprivate static applyColspanOptions(headerRows: HeaderRow[], options: Required<HeaderRowOptions>): void {\n\t\tif (!options.colspan.enabled) {\n\t\t\tthis.removeColspanCells(headerRows);\n\t\t}\n\n\t\tif (!options.colspan.suffix) {\n\t\t\tthis.removeColspanSuffix(headerRows);\n\t\t}\n\t}\n\n\tprivate static removeColspanCells(headerRows: HeaderRow[]): void {\n\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\theaderRows[i] = headerRows[i].filter(cell => !this.COLSPAN_PATTERN.test(cell));\n\t\t}\n\t}\n\n\tprivate static removeColspanSuffix(headerRows: HeaderRow[]): void {\n\t\tfor (const headerRow of headerRows) {\n\t\t\tfor (let i = 0; i < headerRow.length; i++) {\n\t\t\t\tif (this.COLSPAN_PATTERN.test(headerRow[i])) {\n\t\t\t\t\theaderRow[i] = headerRow[i].replace(this.COLSPAN_PATTERN, \"\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static applyDuplicateSuffix(headerRows: HeaderRow[], options: Required<HeaderRowOptions>): void {\n\t\tif (!options.duplicateSuffix) return;\n\n\t\tfor (const headerRow of headerRows) {\n\t\t\tconst headerCountMap: Map<string, number> = new Map();\n\t\t\tfor (let i = 0; i < headerRow.length; i++) {\n\t\t\t\tconst header = headerRow[i];\n\t\t\t\tconst currentCount = headerCountMap.get(header);\n\t\t\t\tif (currentCount !== undefined) {\n\t\t\t\t\theaderRow[i] = `${header}${this.DUPLICATE_SUFFIX_PREFIX}${currentCount}`;\n\t\t\t\t\theaderCountMap.set(header, currentCount + 1);\n\t\t\t\t} else {\n\t\t\t\t\theaderCountMap.set(header, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\nimport { pollTable, PollingOptions } from \"./polling\";\nimport { BodyRow, Cell, HeaderRow } from \"./row\";\nimport { TableBody } from \"./table-body\";\nimport { HeaderRowOptions, TableHeader } from \"./table-header\";\n\n/**\n * Options for loading table data, headers, and rows.\n *\n * @example\n * // Use custom cell content type for body rows and header options\n * const options: TableOptions = {\n * bodyRowOptions: { cellContentType: CellContentType.TextContent },\n * headerRowOptions: {\n * colspan: { enabled: true, suffix: true },\n * duplicateSuffix: true,\n * emptyCellReplacement: false,\n * },\n * };\n * await table.getBodyRows(options);\n */\nexport type TableOptions = {\n\t/** See {@link HeaderRowOptions} for available options. */\n\theaderRowOptions?: HeaderRowOptions;\n\t/** Options for parsing body rows. */\n\tbodyRowOptions?: { cellContentType?: CellContentType };\n};\n\n/**\n * Represents a table in Playwright, providing methods to interact with its headers, rows, and cells.\n */\nexport class PlaywrightTable {\n\tprivate _bodyRowLocator: Locator;\n\tprivate _bodyRowColumnSelector: string;\n\tprivate _headerRowLocator: Locator;\n\tprivate _headerColumnSelector: string;\n\n\t/**\n\t * Creates an instance of the table handler with customizable selectors for headers and rows.\n\t *\n\t * @param _tableLocator - The Playwright `Locator` pointing to the root table element.\n\t * @param _options - Optional configuration for customizing header and row selectors.\n\t * @param _options.header - Options for header row and columns.\n\t * @param _options.header.setMainHeaderRow - (Optional) Index of the main header row if multiple header rows exist.\n\t * @param _options.header.rowSelector - (Optional) CSS selector for locating header rows. Defaults to `\"thead>tr\"`.\n\t * @param _options.header.columnSelector - (Optional) CSS selector for locating header columns. Defaults to `\"th\"`.\n\t * @param _options.row - Options for body rows and columns.\n\t * @param _options.row.rowSelector - (Optional) CSS selector for locating body rows. Defaults to `\"tbody>tr\"`.\n\t * @param _options.row.columnSelector - (Optional) CSS selector for locating body columns. Defaults to `\"td\"`.\n\t *\n\t * @example\n\t * const table = new PlaywrightTable(\n\t * page.locator('table.my-table'),\n\t * {\n\t * header: {\n\t * rowSelector: 'thead tr.header',\n\t * columnSelector: 'th.header-cell'\n\t * },\n\t * row: {\n\t * rowSelector: 'tbody tr.data-row',\n\t * columnSelector: 'td.data-cell'\n\t * }\n\t * }\n\t * );\n\t */\n\tconstructor(\n\t\tprivate readonly _tableLocator: Locator,\n\t\tprivate readonly _options?: {\n\t\t\theader?: {\n\t\t\t\tsetMainHeaderRow?: number;\n\t\t\t\trowSelector?: string;\n\t\t\t\tcolumnSelector?: string;\n\t\t\t};\n\t\t\trow?: {\n\t\t\t\trowSelector?: string;\n\t\t\t\tcolumnSelector?: string;\n\t\t\t};\n\t\t}\n\t) {\n\t\tthis._headerRowLocator = this._tableLocator.locator(this._options?.header?.rowSelector ?? \"thead>tr\");\n\t\tthis._headerColumnSelector = this._options?.header?.columnSelector ?? \"th\";\n\t\tthis._bodyRowLocator = this._tableLocator.locator(this._options?.row?.rowSelector ?? \"tbody>tr\");\n\t\tthis._bodyRowColumnSelector = this._options?.row?.columnSelector ?? \"td\";\n\t}\n\n\t/**\n\t * Retrieves all header rows of the table.\n\t * @param options - Optional parameters for header row parsing configuration.\n\t * @returns A promise that resolves to an array of header rows.\n\t * @throws Error if no header rows found after table loads.\n\t *\n\t * @example\n\t * // Get headers with colspan enabled\n\t * const headers = await table.getHeaderRows({\n\t * colspan: { enabled: true, suffix: true },\n\t * duplicateSuffix: true\n\t * });\n\t *\n\t * @see {@link getMainHeaderRow} for getting only the main header row\n\t * @see {@link HeaderRowOptions} for all available configuration options\n\t */\n\tasync getHeaderRows(options?: HeaderRowOptions): Promise<HeaderRow[]> {\n\t\tconst table = await this.getTable({\n\t\t\theaderRowOptions: options,\n\t\t});\n\t\treturn table.headerRows;\n\t}\n\n\t/**\n\t * Retrieves the main header row of the table.\n\t * By default, uses the last header row if multiple exist. Can be customized via constructor.\n\t * @param options - Optional parameters for header row parsing configuration.\n\t * @returns A promise that resolves to the main header row.\n\t * @throws Error if no header rows available.\n\t * @throws Error if setMainHeaderRow index is out of bounds (negative or >= header row count).\n\t *\n\t * @example\n\t * // Get main header with custom index set in constructor\n\t * const table = new PlaywrightTable(page.locator('table'), {\n\t * header: { setMainHeaderRow: 0 } // Use first row as main header\n\t * });\n\t * const mainHeader = await table.getMainHeaderRow();\n\t *\n\t * @see {@link getHeaderRows} for getting all header rows\n\t */\n\tasync getMainHeaderRow(options?: HeaderRowOptions): Promise<HeaderRow> {\n\t\tconst table = await this.getTable({\n\t\t\theaderRowOptions: options,\n\t\t});\n\t\treturn this.mainHeaderRow(table.headerRows);\n\t}\n\n\t/**\n\t * Retrieves all body rows of the table.\n\t * @param options - Optional configuration for body row parsing.\n\t * @param options.cellContentType - Content extraction type. See {@link CellContentType}. Defaults to InnerText.\n\t * @returns A promise that resolves to an array of body rows.\n\t * @throws Error if no body rows found after table loads.\n\t *\n\t * @example\n\t * // Get body rows with TextContent (includes hidden elements)\n\t * const rows = await table.getBodyRows({\n\t * cellContentType: CellContentType.TextContent\n\t * });\n\t *\n\t * @example\n\t * // Get body rows with default InnerText\n\t * const rows = await table.getBodyRows();\n\t */\n\tasync getBodyRows(options?: { cellContentType?: CellContentType }): Promise<BodyRow[]> {\n\t\tconst table = await this.getTable({\n\t\t\tbodyRowOptions: options,\n\t\t\theaderRowOptions: {\n\t\t\t\tcolspan: { enabled: true },\n\t\t\t},\n\t\t});\n\t\treturn table.bodyRows;\n\t}\n\n\t/**\n\t * Retrieves a specific cell locator in the body of the table by row and column index.\n\t * @param rowNumber - The row number (0-based index).\n\t * @param headerPosition - The column/header position (0-based index).\n\t * @returns A Playwright Locator for the specified cell.\n\t *\n\t * @example\n\t * // Get cell at row 1, column 2\n\t * const cellLocator = table.getBodyCellLocator(1, 2);\n\t * await expect(cellLocator).toHaveText('Expected Text');\n\t *\n\t * @example\n\t * // Get first cell in first row\n\t * const firstCell = table.getBodyCellLocator(0, 0);\n\t * await firstCell.click();\n\t *\n\t * @see {@link getBodyCellLocatorByRowConditions} for finding cells by content matching\n\t * @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting all cells in a column\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells under a header\n\t */\n\tgetBodyCellLocator(rowNumber: number, headerPosition: number): Locator {\n\t\treturn this._bodyRowLocator.nth(rowNumber).locator(this._bodyRowColumnSelector).nth(headerPosition);\n\t}\n\n\t/**\n\t * Retrieves a cell locator in the body of the table based on row conditions and a target header.\n\t * Finds the first row where all conditions match, then returns the cell in the target column.\n\t * @param conditions - An object where keys are header names and values are expected cell contents, or a single [header, value] tuple.\n\t * @param targetHeader - The header name of the column containing the cell to retrieve.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to a Playwright Locator for the matching cell.\n\t * @throws Error if no row matches all specified conditions.\n\t * @throws Error if any condition header or target header is not found in the table.\n\t *\n\t * @example\n\t * // Find cell with multiple conditions\n\t * const emailLocator = await table.getBodyCellLocatorByRowConditions(\n\t * { \"First name\": \"John\", \"Status\": \"Active\" },\n\t * \"Email\"\n\t * );\n\t * await expect(emailLocator).toContainText('@example.com');\n\t *\n\t * @example\n\t * // Find cell with single condition using tuple syntax\n\t * const actionCell = await table.getBodyCellLocatorByRowConditions(\n\t * [\"Username\", \"john.doe\"],\n\t * \"Actions\"\n\t * );\n\t * await actionCell.locator('button').click();\n\t *\n\t * @example\n\t * // Find cell with single condition using object syntax\n\t * const statusCell = await table.getBodyCellLocatorByRowConditions(\n\t * { \"Username\": \"john.doe\" },\n\t * \"Status\"\n\t * );\n\t *\n\t * @see {@link getBodyCellLocator} for getting cells by direct index\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells in a column\n\t */\n\tasync getBodyCellLocatorByRowConditions(\n\t\tconditions: Record<string, string> | [string, string],\n\t\ttargetHeader: string,\n\t\toptions?: TableOptions\n\t): Promise<Locator> {\n\t\tconst table = await this.getTable(options);\n\t\tconst mainHeader = this.mainHeaderRow(table.headerRows);\n\n\t\tconst targetHeaderIndex = this.getHeaderIndex(targetHeader, mainHeader);\n\n\t\t// Normalize conditions to object format\n\t\tconst conditionsObj = Array.isArray(conditions) ? { [conditions[0]]: conditions[1] } : conditions;\n\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst row = table.bodyRows[rowIndex];\n\t\t\tlet matches = true;\n\n\t\t\tfor (const [header, value] of Object.entries(conditionsObj)) {\n\t\t\t\tconst headerIndex = this.getHeaderIndex(header, mainHeader);\n\t\t\t\tconst cellValue = row[headerIndex] ?? \"\";\n\t\t\t\tif (cellValue !== value) {\n\t\t\t\t\tmatches = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matches) {\n\t\t\t\treturn this.getBodyCellLocator(rowIndex, targetHeaderIndex);\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No row found matching conditions: ${JSON.stringify(conditionsObj)}\\n` +\n\t\t\t\t`Target header: \"${targetHeader}\"\\n` +\n\t\t\t\t`Searched ${table.bodyRows.length} rows\\n` +\n\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t);\n\t}\n\n\t/**\n\t * Retrieves all cell locators in the body of the table for a specific header name.\n\t * Returns locators for every cell in the column identified by the header.\n\t * @param header - The header name identifying the column.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to an array of Playwright Locators for all cells in the column.\n\t * @throws Error if the header is not found in the table.\n\t *\n\t * @example\n\t * // Get all cells in the \"Status\" column\n\t * const statusCells = await table.getAllBodyCellLocatorsByHeaderName(\"Status\");\n\t * for (const cell of statusCells) {\n\t * await expect(cell).toHaveText(/Active|Inactive/);\n\t * }\n\t *\n\t * @example\n\t * // Get text from all cells in \"Name\" column\n\t * const nameCells = await table.getAllBodyCellLocatorsByHeaderName(\"Name\");\n\t * const names = await Promise.all(nameCells.map(cell => cell.textContent()));\n\t *\n\t * @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting cells by column index\n\t * @see {@link getBodyCellLocatorByRowConditions} for finding specific cells by content\n\t */\n\tasync getAllBodyCellLocatorsByHeaderName(header: string, options?: TableOptions): Promise<Locator[]> {\n\t\tconst table = await this.getTable(options);\n\t\tconst headers = this.mainHeaderRow(table.headerRows);\n\t\tconst headerIndex = this.getHeaderIndex(header, headers);\n\t\tconst locators: Locator[] = [];\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst cellLocator = this.getBodyCellLocator(rowIndex, headerIndex);\n\t\t\tlocators.push(cellLocator);\n\t\t}\n\t\treturn locators;\n\t}\n\n\t/**\n\t * Retrieves all cell locators in the body of the table for a specific header index.\n\t * Returns locators for every cell in the column at the specified position.\n\t * @param headerIndex - The header/column index (0-based).\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to an array of Playwright Locators for all cells in the column.\n\t * @throws Error if the header index is out of bounds (negative or >= column count).\n\t *\n\t * @example\n\t * // Get all cells in the third column (index 2)\n\t * const thirdColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(2);\n\t * for (const cell of thirdColumnCells) {\n\t * await expect(cell).toBeVisible();\n\t * }\n\t *\n\t * @example\n\t * // Get all cells in first column\n\t * const firstColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(0);\n\t * const values = await Promise.all(firstColumnCells.map(c => c.textContent()));\n\t *\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting cells by header name\n\t * @see {@link getBodyCellLocator} for getting a specific cell by row and column index\n\t */\n\tasync getAllBodyCellLocatorsByHeaderIndex(headerIndex: number, options?: TableOptions): Promise<Locator[]> {\n\t\tconst table = await this.getTable(options);\n\t\tconst columnCount = this.mainHeaderRow(table.headerRows).length;\n\t\tif (headerIndex < 0 || headerIndex >= columnCount) {\n\t\t\tthrow new Error(\n\t\t\t\t`Header index ${headerIndex} out of bounds. Table has ${columnCount} columns.\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\t\tconst locators: Locator[] = [];\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst cellLocator = this.getBodyCellLocator(rowIndex, headerIndex);\n\t\t\tlocators.push(cellLocator);\n\t\t}\n\t\treturn locators;\n\t}\n\n\t/**\n\t * Converts the table data into a JSON array where each object represents a row.\n\t * Keys are header names, values are cell contents.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to an array of objects representing table rows.\n\t * @throws Error if no header rows or body rows found.\n\t *\n\t * @example\n\t * // Convert table to JSON with default options\n\t * const data = await table.getJson();\n\t * // Result: [{\"Name\": \"John\", \"Age\": \"30\"}, {\"Name\": \"Jane\", \"Age\": \"25\"}]\n\t *\n\t * @example\n\t * // Convert with colspan enabled and duplicate suffix\n\t * const data = await table.getJson({\n\t * headerRowOptions: {\n\t * colspan: { enabled: true, suffix: true },\n\t * duplicateSuffix: true,\n\t * emptyCellReplacement: true\n\t * }\n\t * });\n\t */\n\tasync getJson(options?: TableOptions): Promise<Record<string, string>[]> {\n\t\tconst table = await this.getTable({\n\t\t\tbodyRowOptions: options?.bodyRowOptions,\n\t\t\theaderRowOptions: {\n\t\t\t\tcolspan: {\n\t\t\t\t\tenabled: options?.headerRowOptions?.colspan?.enabled ?? true,\n\t\t\t\t\tsuffix: options?.headerRowOptions?.colspan?.suffix ?? true,\n\t\t\t\t},\n\t\t\t\tduplicateSuffix: options?.headerRowOptions?.duplicateSuffix ?? true,\n\t\t\t\temptyCellReplacement: options?.headerRowOptions?.emptyCellReplacement ?? true,\n\t\t\t},\n\t\t});\n\n\t\tconst headers = this.mainHeaderRow(table.headerRows);\n\t\treturn table.bodyRows.map(row => {\n\t\t\tconst rowObj: Record<string, Cell> = {};\n\t\t\theaders.forEach((header, index) => {\n\t\t\t\trowObj[header] = row[index] || \"\";\n\t\t\t});\n\t\t\treturn rowObj;\n\t\t});\n\t}\n\n\t/**\n\t * Waits for the table body to be empty (no body rows).\n\t * @param options - Polling options (timeout, interval, retries).\n\t * @returns A promise that resolves when table body is empty.\n\t * @throws Error if table is not empty within timeout period.\n\t *\n\t * @example\n\t * // Wait for table to clear after delete operation\n\t * await table.waitForEmpty({ timeout: 5000 });\n\t *\n\t * @see {@link waitForNonEmpty} for waiting until table has data\n\t *\n\t * @remarks\n\t * **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:\n\t * ```ts\n\t * await expect.poll(async () => (await table.getBodyRows()).length).toBe(0);\n\t * ```\n\t */\n\tasync waitForEmpty(options?: PollingOptions): Promise<void> {\n\t\tawait pollTable(async () => {\n\t\t\tconst rows = await this._bodyRowLocator.all();\n\t\t\tif (rows.length > 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected table to be empty, but found ${rows.length} body rows.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\t\t}, options);\n\t}\n\n\t/**\n\t * Waits for the table body to be non-empty (at least one body row with content).\n\t * A row is considered valid if it has at least one cell containing text.\n\t * @param options - Polling options (timeout, interval, retries).\n\t * @returns A promise that resolves when table has at least one body row with content.\n\t * @throws Error if table remains empty or has no rows with content within timeout period.\n\t *\n\t * @example\n\t * // Wait for table to populate after search\n\t * await table.waitForNonEmpty({ timeout: 5000 });\n\t *\n\t * @see {@link waitForEmpty} for waiting until table is cleared\n\t *\n\t * @remarks\n\t * **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:\n\t * ```ts\n\t * await expect.poll(async () => (await table.getBodyRows()).length).toBeGreaterThan(0);\n\t * ```\n\t */\n\tasync waitForNonEmpty(options?: PollingOptions): Promise<void> {\n\t\tawait pollTable(async () => {\n\t\t\tconst rows = await this._bodyRowLocator.all();\n\t\t\tif (rows.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected table to be non-empty, but found 0 body rows.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Check if at least one row has content in at least one cell\n\t\t\tlet hasRowWithContent = false;\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cells = await row.locator(this._bodyRowColumnSelector).all();\n\t\t\t\tfor (const cell of cells) {\n\t\t\t\t\tconst content = await cell.innerText();\n\t\t\t\t\tif (content.trim().length > 0) {\n\t\t\t\t\t\thasRowWithContent = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (hasRowWithContent) break;\n\t\t\t}\n\n\t\t\tif (!hasRowWithContent) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected table to have at least one row with content, but all ${rows.length} rows are empty.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\t\t}, options);\n\t}\n\n\t/**\n\t * Waits for the table body to have exactly N rows.\n\t * @param count - The exact number of body rows expected.\n\t * @param options - Polling options (timeout, interval, retries).\n\t * @returns A promise that resolves when the table has exactly the specified number of rows.\n\t * @throws Error if row count doesn't match within timeout period.\n\t *\n\t * @example\n\t * // Wait for exactly 10 rows after pagination\n\t * await table.waitForExactRowCount(10, { timeout: 5000 });\n\t *\n\t * @example\n\t * // Verify table has exactly 0 rows (alternative to waitForEmpty)\n\t * await table.waitForExactRowCount(0);\n\t *\n\t * @see {@link getRowCount} for getting current row counts\n\t *\n\t * @remarks\n\t * **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:\n\t * ```ts\n\t * await expect.poll(async () => (await table.getBodyRows()).length).toBe(10);\n\t * ```\n\t */\n\tasync waitForExactRowCount(count: number, options?: PollingOptions): Promise<void> {\n\t\tif (count < 0 || !Number.isInteger(count)) {\n\t\t\tthrow new Error(`Row count must be a non-negative integer, got: ${count}`);\n\t\t}\n\n\t\tawait pollTable(async () => {\n\t\t\tconst rows = await this._bodyRowLocator.all();\n\t\t\tif (rows.length !== count) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected exactly ${count} rows, but found ${rows.length}.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\t\t}, options);\n\t}\n\n\t/**\n\t * Gets the count of header and body rows in the table.\n\t * Lightweight method that doesn't fetch cell data, only counts rows.\n\t * @returns A promise that resolves to an object with header and body row counts.\n\t *\n\t * @example\n\t * // Get current row counts\n\t * const counts = await table.getRowCount();\n\t * console.log(`Headers: ${counts.header}, Body: ${counts.body}`);\n\t *\n\t * @example\n\t * // Assert specific row count\n\t * const { body } = await table.getRowCount();\n\t * expect(body).toBe(10);\n\t */\n\tasync getRowCount(): Promise<{ header: number; body: number }> {\n\t\tconst [headerRows, bodyRows] = await Promise.all([this._headerRowLocator.all(), this._bodyRowLocator.all()]);\n\t\treturn {\n\t\t\theader: headerRows.length,\n\t\t\tbody: bodyRows.length,\n\t\t};\n\t}\n\n\t/**\n\t * Gets all distinct (unique) values from a specific column.\n\t * Returns a sorted array of unique string values, excluding empty values.\n\t * @param headerName - The header name of the column to extract distinct values from.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to a sorted array of distinct values.\n\t * @throws Error if the specified header is not found in the table.\n\t *\n\t * @example\n\t * // Get all unique status values\n\t * const statuses = await table.getDistinctColumnValues(\"Status\");\n\t * // Result: [\"Active\", \"Inactive\", \"Pending\"]\n\t *\n\t * @example\n\t * // Get distinct countries for filtering\n\t * const countries = await table.getDistinctColumnValues(\"Country\");\n\t * console.log(`Available countries: ${countries.join(\", \")}`);\n\t *\n\t * @example\n\t * // Verify expected values exist\n\t * const roles = await table.getDistinctColumnValues(\"Role\");\n\t * expect(roles).toContain(\"Admin\");\n\t * expect(roles).toHaveLength(3);\n\t *\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cell locators in a column\n\t * @see {@link getJson} for getting full table data\n\t */\n\tasync getDistinctColumnValues(headerName: string, options?: TableOptions): Promise<string[]> {\n\t\tconst table = await this.getTable(options);\n\t\tconst mainHeader = this.mainHeaderRow(table.headerRows);\n\t\tconst headerIndex = this.getHeaderIndex(headerName, mainHeader);\n\n\t\tconst values = new Set<string>();\n\t\tfor (const row of table.bodyRows) {\n\t\t\tconst value = String(row[headerIndex] ?? \"\").trim();\n\t\t\tif (value) values.add(value);\n\t\t}\n\n\t\treturn Array.from(values).sort();\n\t}\n\n\t/**\n\t * Finds the index of the first body row matching the specified conditions.\n\t * Returns -1 if no matching row is found.\n\t * @param conditions - Record of header names and expected cell values to match.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to the 0-based index of the first matching row, or -1 if not found.\n\t * @throws Error if specified headers don't exist in the table.\n\t *\n\t * @example\n\t * // Find index of row where Status is \"Active\"\n\t * const index = await table.findRowIndex({ Status: \"Active\" });\n\t * if (index >= 0) {\n\t * console.log(`Found at row ${index}`);\n\t * }\n\t *\n\t * @example\n\t * // Find row and interact with it\n\t * const rowIndex = await table.findRowIndex({ Username: \"john.doe\" });\n\t * if (rowIndex >= 0) {\n\t * const cell = table.getBodyCellLocator(rowIndex, 0);\n\t * await cell.click();\n\t * }\n\t *\n\t * @see {@link getBodyCellLocatorByRowConditions} for getting cell locators from matching rows\n\t * @see {@link waitForRowByConditions} for waiting until matching row appears\n\t */\n\tasync findRowIndex(conditions: Record<string, string>, options?: TableOptions): Promise<number> {\n\t\tconst table = await this.getTable({\n\t\t\theaderRowOptions: { colspan: { enabled: true }, ...options?.headerRowOptions },\n\t\t\tbodyRowOptions: options?.bodyRowOptions,\n\t\t});\n\t\tconst mainHeader = this.mainHeaderRow(table.headerRows);\n\n\t\t// Validate all condition headers exist and get their indices\n\t\tconst headerIndices = new Map<string, number>();\n\t\tfor (const header of Object.keys(conditions)) {\n\t\t\theaderIndices.set(header, this.getHeaderIndex(header, mainHeader));\n\t\t}\n\n\t\t// Find first matching row\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst row = table.bodyRows[rowIndex];\n\t\t\tlet rowMatches = true;\n\n\t\t\tfor (const [header, expectedValue] of Object.entries(conditions)) {\n\t\t\t\tconst headerIndex = headerIndices.get(header)!;\n\t\t\t\tconst cellValue = String(row[headerIndex] ?? \"\");\n\t\t\t\tif (cellValue !== expectedValue) {\n\t\t\t\t\trowMatches = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rowMatches) {\n\t\t\t\treturn rowIndex;\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t}\n\n\tprivate async getTable(options?: TableOptions): Promise<{ headerRows: HeaderRow[]; bodyRows: BodyRow[] }> {\n\t\tconst [headerRows, bodyRows] = await Promise.all([\n\t\t\tTableHeader.getRows(this._headerRowLocator, this._headerColumnSelector, options?.headerRowOptions),\n\t\t\tTableBody.getRows(this._bodyRowLocator, this._bodyRowColumnSelector, options?.bodyRowOptions),\n\t\t]);\n\n\t\tif (headerRows.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No header rows found, is table loaded completely?\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}\\n` +\n\t\t\t\t\t`Table locator: ${this._tableLocator.toString()}`\n\t\t\t);\n\t\t}\n\t\tif (bodyRows.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No body rows found, is table loaded completely?\\n` +\n\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}\\n` +\n\t\t\t\t\t`Table locator: ${this._tableLocator.toString()}`\n\t\t\t);\n\t\t}\n\n\t\treturn { headerRows, bodyRows };\n\t}\n\n\tprivate mainHeaderRow(headers: HeaderRow[]): HeaderRow {\n\t\tif (headers.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No header rows available. Ensure table has been loaded.\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\n\t\tconst headerIndex = this._options?.header?.setMainHeaderRow ?? headers.length - 1;\n\t\tif (headerIndex < 0 || headerIndex >= headers.length) {\n\t\t\tthrow new Error(\n\t\t\t\t`Header row index ${headerIndex} out of bounds. Table has ${headers.length} header rows.\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\n\t\treturn headers[headerIndex];\n\t}\n\n\t/**\n\t * Gets the index of a header in the main header row, throwing an error if not found.\n\t * @param header - The header name to find.\n\t * @param mainHeader - The main header row to search in.\n\t * @returns The 0-based index of the header.\n\t * @throws Error if the header is not found.\n\t */\n\tprivate getHeaderIndex(header: string, mainHeader: HeaderRow): number {\n\t\tconst index = mainHeader.indexOf(header);\n\t\tif (index === -1) {\n\t\t\tthrow new Error(\n\t\t\t\t`Header \"${header}\" not found.\\n` +\n\t\t\t\t\t`Available headers: [${mainHeader.join(\", \")}]\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\t\treturn index;\n\t}\n}\n"],"mappings":"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","names":["CellContentType"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/polling.ts","../src/cell-content-type.ts","../src/table-utils.ts","../src/table-body.ts","../src/table-header.ts","../src/playwright-table.ts"],"sourcesContent":["export * from \"./playwright-table\";\nexport * from \"./row\";\nexport { HeaderRowOptions } from \"./table-header\";\nexport { CellContentType } from \"./cell-content-type\";\nexport { PollingOptions } from \"./polling\";\n","export type PollingOptions = {\n\ttimeout?: number;\n\tinterval?: number;\n};\n\nexport async function pollTable(fn: () => Promise<void>, options?: PollingOptions): Promise<void> {\n\tif (!options) {\n\t\toptions = {};\n\t}\n\n\tconst timeout = options.timeout ?? 30_000;\n\tconst interval = options.interval ?? 100;\n\tconst startTime = Date.now();\n\tlet lastError: Error | undefined;\n\n\twhile (Date.now() - startTime < timeout) {\n\t\ttry {\n\t\t\tawait fn();\n\t\t\treturn;\n\t\t} catch (err) {\n\t\t\tlastError = err as Error;\n\n\t\t\tawait new Promise(resolve => setTimeout(resolve, interval));\n\t\t}\n\t}\n\n\tthrow lastError || new Error(\"Playwright Tables: Polling timeout reached\");\n}\n","/**\n * Enum representing the possible types of content that can be extracted from a table cell.\n *\n * - `TextContent`: The raw text content of the cell, including hidden elements and extra whitespace.\n * - `InnerText`: The rendered text content of the cell, as seen by the user, with whitespace collapsed and hidden elements excluded.\n */\nexport enum CellContentType {\n\tTextContent = \"textContent\",\n\tInnerText = \"innerText\",\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\n\n/**\n * Utility class for extracting table cell content and attributes.\n * Contains only static methods - cannot be instantiated.\n */\nexport class TableUtils {\n\tprivate constructor() {\n\t\t// Prevent instantiation - this class only has static methods\n\t}\n\t/**\n\t * Extracts text content from a table cell.\n\t * @param cell - The Playwright locator for the cell.\n\t * @param cellContentType - The type of content to extract (InnerText or TextContent).\n\t * @returns The trimmed text content of the cell, or empty string if no content.\n\t * @throws Error if the cell locator doesn't exist in the DOM.\n\t */\n\tstatic async getCellContent(cell: Locator, cellContentType: CellContentType): Promise<string> {\n\t\ttry {\n\t\t\tconst count = await cell.count();\n\t\t\tif (count === 0) {\n\t\t\t\tthrow new Error(\"Cell locator does not exist in the DOM\");\n\t\t\t}\n\n\t\t\tconst content = cellContentType === CellContentType.InnerText ? await cell.innerText() : await cell.textContent();\n\t\t\treturn content?.trim() || \"\";\n\t\t} catch (error) {\n\t\t\tconst locatorDescription = this.getLocatorDescription(cell);\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to get cell content.\\n` +\n\t\t\t\t\t`Locator: ${locatorDescription}\\n` +\n\t\t\t\t\t`Content type: ${cellContentType}\\n` +\n\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Parses rowspan and colspan attributes from a table cell.\n\t * Fetches both attributes in parallel for optimal performance.\n\t * @param cell - The Playwright locator for the cell.\n\t * @returns Object containing rowspan and colspan values (defaults to 1 if not present).\n\t * @throws Error if attributes contain invalid values (non-numeric or less than 1).\n\t */\n\tstatic async parseSpanAttributes(cell: Locator): Promise<{ rowspan: number; colspan: number }> {\n\t\ttry {\n\t\t\tconst [rowspanAttr, colspanAttr] = await Promise.all([\n\t\t\t\tcell.getAttribute(\"rowspan\"),\n\t\t\t\tcell.getAttribute(\"colspan\"),\n\t\t\t]);\n\n\t\t\tconst rowspan = parseInt(rowspanAttr || \"1\", 10);\n\t\t\tconst colspan = parseInt(colspanAttr || \"1\", 10);\n\n\t\t\tif (isNaN(rowspan) || rowspan < 1) {\n\t\t\t\tthrow new Error(`Invalid rowspan attribute: \"${rowspanAttr}\". Expected positive integer.`);\n\t\t\t}\n\t\t\tif (isNaN(colspan) || colspan < 1) {\n\t\t\t\tthrow new Error(`Invalid colspan attribute: \"${colspanAttr}\". Expected positive integer.`);\n\t\t\t}\n\n\t\t\treturn { rowspan, colspan };\n\t\t} catch (error) {\n\t\t\tconst locatorDescription = this.getLocatorDescription(cell);\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to parse span attributes.\\n` +\n\t\t\t\t\t`Locator: ${locatorDescription}\\n` +\n\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Extracts a human-readable locator description from a Playwright Locator.\n\t * This captures Playwright's internal selector chain for better error messages.\n\t * @param locator - The Playwright locator to describe.\n\t * @returns A string representation of the locator's selector chain.\n\t */\n\tprivate static getLocatorDescription(locator: Locator): string {\n\t\tconst locatorString = locator.toString();\n\t\t// Example: \"Locator@#root >> table >> tbody >> tr >> td\"\n\t\treturn locatorString;\n\t}\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\nimport { BodyRow, Cell } from \"./row\";\nimport { TableUtils } from \"./table-utils\";\n\n/**\n * Utility class for extracting and processing table body rows.\n * Handles rowspan and colspan attributes to properly structure table data.\n * Contains only static methods - cannot be instantiated.\n */\nexport class TableBody {\n\tprivate constructor() {\n\t\t// Prevent instantiation - this class only has static methods\n\t}\n\t/**\n\t * Extracts body rows from a table with proper handling of rowspan and colspan.\n\t *\n\t * @param rowLocator - The Playwright locator for body rows.\n\t * @param columnsSelector - CSS selector for locating columns within rows (e.g., \"td\").\n\t * @param bodyRowsOptions - Optional configuration for cell content extraction.\n\t * @param bodyRowsOptions.cellContentType - The type of content to extract (InnerText or TextContent).\n\t * @returns Promise resolving to an array of processed body rows.\n\t * @throws Error if columnsSelector is empty or if row processing fails.\n\t *\n\t * @example\n\t * const rows = await TableBody.getRows(\n\t * page.locator('tbody tr'),\n\t * 'td',\n\t * { cellContentType: CellContentType.TextContent }\n\t * );\n\t */\n\tstatic async getRows(\n\t\trowLocator: Locator,\n\t\tcolumnsSelector: string,\n\t\tbodyRowsOptions?: {\n\t\t\tcellContentType?: CellContentType;\n\t\t}\n\t): Promise<BodyRow[]> {\n\t\tif (!columnsSelector || columnsSelector.trim() === \"\") {\n\t\t\tthrow new Error(\"columnsSelector cannot be empty\");\n\t\t}\n\n\t\tconst locatorDescription = rowLocator.toString();\n\t\tconst rows: BodyRow[] = [];\n\t\tconst rowsCount = await rowLocator.count();\n\n\t\tif (rowsCount === 0) {\n\t\t\treturn rows;\n\t\t}\n\n\t\tconst spannedCells: Record<number, Cell[]> = {};\n\n\t\tfor (let rowIndex = 0; rowIndex < rowsCount; rowIndex++) {\n\t\t\ttry {\n\t\t\t\tconst row = rowLocator.nth(rowIndex);\n\t\t\t\tconst columns = await this.extractColumns(\n\t\t\t\t\trow,\n\t\t\t\t\tcolumnsSelector,\n\t\t\t\t\trowIndex,\n\t\t\t\t\tspannedCells,\n\t\t\t\t\tbodyRowsOptions?.cellContentType\n\t\t\t\t);\n\t\t\t\trows.push(columns as BodyRow);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to process body row at index ${rowIndex}.\\n` +\n\t\t\t\t\t\t`Body row locator: ${locatorDescription}\\n` +\n\t\t\t\t\t\t`Columns selector: \"${columnsSelector}\"\\n` +\n\t\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn rows;\n\t}\n\n\tprivate static async extractColumns(\n\t\trow: Locator,\n\t\tcolumnsSelector: string,\n\t\trowIndex: number,\n\t\tspannedCells: Record<number, Cell[]>,\n\t\tcellContentType?: CellContentType\n\t): Promise<Cell[]> {\n\t\tconst columns: Cell[] = [];\n\t\tconst columnLocators = row.locator(columnsSelector);\n\t\tconst columnCount = await columnLocators.count();\n\n\t\tif (columnCount === 0) {\n\t\t\t// Still apply any spanned cells even if this row has no DOM cells\n\t\t\tthis.applySpannedCells(spannedCells, rowIndex, columns);\n\t\t\treturn columns;\n\t\t}\n\n\t\tconst cellDataPromises = [];\n\t\tfor (let colIndex = 0; colIndex < columnCount; colIndex++) {\n\t\t\tcellDataPromises.push(this.fetchCellData(columnLocators.nth(colIndex), cellContentType));\n\t\t}\n\t\tconst cellDataArray = await Promise.all(cellDataPromises);\n\n\t\t// First, determine which column positions are occupied by rowspans from above\n\t\tconst spannedPositions = new Set<number>();\n\t\tif (spannedCells[rowIndex]) {\n\t\t\tspannedCells[rowIndex].forEach((_, colIndex) => {\n\t\t\t\tif (spannedCells[rowIndex][colIndex] !== undefined) {\n\t\t\t\t\tspannedPositions.add(colIndex);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// Process cells, accounting for spanned positions\n\t\tlet actualColIndex = 0;\n\t\tfor (let domCellIndex = 0; domCellIndex < columnCount; domCellIndex++) {\n\t\t\t// Skip columns that are occupied by rowspans from above\n\t\t\twhile (spannedPositions.has(actualColIndex)) {\n\t\t\t\tactualColIndex++;\n\t\t\t}\n\n\t\t\tconst cellData = cellDataArray[domCellIndex];\n\t\t\tthis.processCellData(cellData, actualColIndex, columns, rowIndex, spannedCells);\n\n\t\t\t// Move past this cell and any colspan it creates\n\t\t\tactualColIndex += cellData.colspan;\n\t\t}\n\n\t\tthis.applySpannedCells(spannedCells, rowIndex, columns);\n\n\t\treturn columns;\n\t}\n\n\t/**\n\t * Fetches cell content and span attributes in parallel.\n\t * This method performs DOM operations that can be parallelized across multiple cells.\n\t */\n\tprivate static async fetchCellData(\n\t\tcolumn: Locator,\n\t\tcellContentType: CellContentType = CellContentType.InnerText\n\t): Promise<{ content: string; rowspan: number; colspan: number }> {\n\t\tconst [content, spanAttributes] = await Promise.all([\n\t\t\tTableUtils.getCellContent(column, cellContentType),\n\t\t\tTableUtils.parseSpanAttributes(column),\n\t\t]);\n\n\t\treturn {\n\t\t\tcontent,\n\t\t\trowspan: spanAttributes.rowspan,\n\t\t\tcolspan: spanAttributes.colspan,\n\t\t};\n\t}\n\n\t/**\n\t * Processes pre-fetched cell data and applies it to the columns array.\n\t * This method is purely computational and doesn't perform any DOM operations.\n\t */\n\tprivate static processCellData(\n\t\tcellData: { content: string; rowspan: number; colspan: number },\n\t\tcolIndex: number,\n\t\tcolumns: Cell[],\n\t\trowIndex: number,\n\t\tspannedCells: Record<number, Cell[]>\n\t): void {\n\t\tconst { content, rowspan, colspan } = cellData;\n\n\t\tif (colIndex < 0) {\n\t\t\tthrow new Error(`Invalid column index: ${colIndex}. Must be non-negative.`);\n\t\t}\n\t\tif (rowIndex < 0) {\n\t\t\tthrow new Error(`Invalid row index: ${rowIndex}. Must be non-negative.`);\n\t\t}\n\n\t\tfor (let span = 0; span < colspan; span++) {\n\t\t\tcolumns[colIndex + span] = content;\n\t\t}\n\n\t\tif (rowspan > 1) {\n\t\t\tthis.storeSpannedCells(rowIndex, colIndex, rowspan, colspan, content, spannedCells);\n\t\t}\n\t}\n\n\tprivate static storeSpannedCells(\n\t\trowIndex: number,\n\t\tcolIndex: number,\n\t\trowspan: number,\n\t\tcolspan: number,\n\t\tcontent: Cell,\n\t\tspannedCells: Record<number, Cell[]>\n\t): void {\n\t\tfor (let rowSpan = 1; rowSpan < rowspan; rowSpan++) {\n\t\t\tif (!spannedCells[rowIndex + rowSpan]) {\n\t\t\t\tspannedCells[rowIndex + rowSpan] = [];\n\t\t\t}\n\t\t\t// Store the content for each column covered by colspan\n\t\t\tfor (let colSpan = 0; colSpan < colspan; colSpan++) {\n\t\t\t\tspannedCells[rowIndex + rowSpan][colIndex + colSpan] = content;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static applySpannedCells(spannedCells: Record<number, Cell[]>, rowIndex: number, columns: Cell[]): void {\n\t\tif (spannedCells[rowIndex]) {\n\t\t\tspannedCells[rowIndex].forEach((spannedContent, colIndex) => {\n\t\t\t\tif (spannedContent !== undefined) {\n\t\t\t\t\tcolumns[colIndex] = spannedContent;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\nimport { HeaderRow } from \"./row\";\nimport { TableUtils } from \"./table-utils\";\n\n/**\n * Options for configuring a table header row.\n *\n * @property cellContentType - The type of content allowed in the header cell.\n * @property emptyCellReplacement - If true, replaces empty header cells with a default value.\n * @property duplicateSuffix - If true, appends a suffix to duplicate header values.\n * @property colspan - Configuration for column spanning in the header row.\n * @property colspan.enabled - If true, enables column spanning.\n * @property colspan.suffix - If true, appends a suffix to spanned columns.\n */\nexport type HeaderRowOptions = {\n\tcellContentType?: CellContentType;\n\temptyCellReplacement?: boolean;\n\tduplicateSuffix?: boolean;\n\tcolspan?: { enabled?: boolean; suffix?: boolean };\n\tnormalizeWhitespace?: boolean;\n\tstripIconGlyphs?: boolean;\n};\n\n/**\n * Utility class for extracting and processing table header rows.\n * Handles complex scenarios including colspan, rowspan, empty cells, and duplicate header names.\n * Contains only static methods - cannot be instantiated.\n */\nexport class TableHeader {\n\tprivate constructor() {\n\t\t// Prevent instantiation - this class only has static methods\n\t}\n\tprivate static readonly EMPTY_CELL_PLACEHOLDER = \"{{Empty}}\";\n\tprivate static readonly COLSPAN_SUFFIX_PREFIX = \"__C\";\n\tprivate static readonly DUPLICATE_SUFFIX_PREFIX = \"__D\";\n\tprivate static readonly COLSPAN_PATTERN = /__C\\d+/;\n\tprivate static readonly ICON_GLYPH_PATTERN = /[\\uE000-\\uF8FF\\u{F0000}-\\u{FFFFD}\\u{100000}-\\u{10FFFD}]/gu;\n\tprivate static readonly CONTROL_WHITESPACE_PATTERN = /[\\p{Cc}\\p{Zl}\\p{Zp}]+/gu;\n\tprivate static readonly WHITESPACE_PATTERN = /\\s+/g;\n\n\t/**\n\t * Extracts header rows from a table with optional processing for colspan, rowspan, and duplicates.\n\t *\n\t * @param headerRowLocator - The Playwright locator for header rows.\n\t * @param columnsSelector - CSS selector for locating header columns (e.g., \"th\").\n\t * @param headerRowOptions - Optional configuration for header processing.\n\t * @returns Promise resolving to an array of processed header rows.\n\t * @throws Error if columnsSelector is empty or if no header rows are found.\n\t *\n\t * @example\n\t * const headers = await TableHeader.getRows(\n\t * page.locator('thead tr'),\n\t * 'th',\n\t * { duplicateSuffix: true, colspan: { enabled: true } }\n\t * );\n\t */\n\tstatic async getRows(\n\t\theaderRowLocator: Locator,\n\t\tcolumnsSelector: string,\n\t\theaderRowOptions?: HeaderRowOptions\n\t): Promise<HeaderRow[]> {\n\t\tif (!columnsSelector || columnsSelector.trim() === \"\") {\n\t\t\tthrow new Error(\"columnsSelector cannot be empty\");\n\t\t}\n\n\t\tconst locatorDescription = headerRowLocator.toString();\n\t\tconst options = this.getDefaultOptions(headerRowOptions);\n\t\tconst rows = await headerRowLocator.all();\n\n\t\tconst headerRows: HeaderRow[] = [];\n\n\t\tif (rows.length === 0) {\n\t\t\treturn headerRows;\n\t\t}\n\n\t\tconst rowSpans: Map<number, string> = new Map();\n\n\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\ttry {\n\t\t\t\tconst headerRow = await this.processRow(rows[rowIndex], columnsSelector, rowSpans, options);\n\t\t\t\theaderRows.push(headerRow);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to process header row at index ${rowIndex}.\\n` +\n\t\t\t\t\t\t`Header row locator: ${locatorDescription}\\n` +\n\t\t\t\t\t\t`Columns selector: \"${columnsSelector}\"\\n` +\n\t\t\t\t\t\t`Error: ${error instanceof Error ? error.message : String(error)}`\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.applyColspanOptions(headerRows, options);\n\t\tthis.applyDuplicateSuffix(headerRows, options);\n\n\t\treturn headerRows;\n\t}\n\n\tprivate static getDefaultOptions(options?: HeaderRowOptions): Required<HeaderRowOptions> {\n\t\treturn {\n\t\t\tcellContentType: options?.cellContentType ?? CellContentType.InnerText,\n\t\t\temptyCellReplacement: options?.emptyCellReplacement ?? false,\n\t\t\tduplicateSuffix: options?.duplicateSuffix ?? false,\n\t\t\tnormalizeWhitespace: options?.normalizeWhitespace ?? false,\n\t\t\tstripIconGlyphs: options?.stripIconGlyphs ?? false,\n\t\t\tcolspan: {\n\t\t\t\tenabled: options?.colspan?.enabled ?? false,\n\t\t\t\tsuffix: options?.colspan?.suffix ?? false,\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate static async processRow(\n\t\trow: Locator,\n\t\tcolumnsSelector: string,\n\t\trowSpans: Map<number, string>,\n\t\toptions: Required<HeaderRowOptions>\n\t): Promise<HeaderRow> {\n\t\tconst headerRow: HeaderRow = [];\n\t\tconst cells = await row.locator(columnsSelector).all();\n\n\t\tif (cells.length === 0) {\n\t\t\treturn headerRow;\n\t\t}\n\n\t\tconst cellDataArray = await Promise.all(\n\t\t\tcells.map(cell =>\n\t\t\t\tthis.fetchHeaderCellData(cell, options.cellContentType, options.normalizeWhitespace, options.stripIconGlyphs)\n\t\t\t)\n\t\t);\n\n\t\tlet columnIndex = 0;\n\t\tfor (let i = 0; i < cellDataArray.length; i++) {\n\t\t\tcolumnIndex = this.processHeaderCellData(\n\t\t\t\tcellDataArray[i],\n\t\t\t\theaderRow,\n\t\t\t\trowSpans,\n\t\t\t\tcolumnIndex,\n\t\t\t\toptions.emptyCellReplacement\n\t\t\t);\n\t\t}\n\n\t\tthis.addRemainingRowSpans(headerRow, rowSpans, columnIndex);\n\t\treturn headerRow;\n\t}\n\n\t/**\n\t * Fetches header cell content and span attributes in parallel.\n\t * This method performs DOM operations that can be parallelized across multiple cells.\n\t */\n\tprivate static async fetchHeaderCellData(\n\t\tcell: Locator,\n\t\tcellContentType: CellContentType,\n\t\tnormalizeWhitespace: boolean,\n\t\tstripIconGlyphs: boolean\n\t): Promise<{ text: string; rowspan: number; colspan: number }> {\n\t\tconst [text, spanAttributes] = await Promise.all([\n\t\t\tTableUtils.getCellContent(cell, cellContentType),\n\t\t\tTableUtils.parseSpanAttributes(cell),\n\t\t]);\n\n\t\treturn {\n\t\t\ttext: this.sanitizeHeaderText(text, normalizeWhitespace, stripIconGlyphs),\n\t\t\trowspan: spanAttributes.rowspan,\n\t\t\tcolspan: spanAttributes.colspan,\n\t\t};\n\t}\n\n\tprivate static sanitizeHeaderText(text: string, normalizeWhitespace: boolean, stripIconGlyphs: boolean): string {\n\t\tlet result = text;\n\n\t\tif (stripIconGlyphs) {\n\t\t\tresult = result.replace(this.ICON_GLYPH_PATTERN, \"\");\n\t\t}\n\n\t\tif (normalizeWhitespace) {\n\t\t\tresult = result.replace(this.CONTROL_WHITESPACE_PATTERN, \" \").replace(this.WHITESPACE_PATTERN, \" \");\n\t\t}\n\n\t\treturn result.trim();\n\t}\n\n\t/**\n\t * Processes pre-fetched header cell data and applies it to the header row.\n\t * This method is purely computational and doesn't perform any DOM operations.\n\t */\n\tprivate static processHeaderCellData(\n\t\tcellData: { text: string; rowspan: number; colspan: number },\n\t\theaderRow: HeaderRow,\n\t\trowSpans: Map<number, string>,\n\t\tcolumnIndex: number,\n\t\temptyCellReplacement: boolean\n\t): number {\n\t\twhile (rowSpans.has(columnIndex)) {\n\t\t\theaderRow.push(rowSpans.get(columnIndex)!);\n\t\t\trowSpans.delete(columnIndex);\n\t\t\tcolumnIndex++;\n\t\t}\n\n\t\tlet text = cellData.text;\n\t\tif (!text && emptyCellReplacement) {\n\t\t\ttext = this.EMPTY_CELL_PLACEHOLDER;\n\t\t}\n\n\t\tconst { colspan, rowspan } = cellData;\n\n\t\theaderRow.push(text);\n\t\tthis.handleColspan(headerRow, text, colspan);\n\t\tthis.handleRowspan(rowSpans, text, columnIndex, colspan, rowspan);\n\n\t\treturn columnIndex + colspan;\n\t}\n\n\tprivate static handleColspan(headerRow: HeaderRow, text: string, colspan: number): void {\n\t\tfor (let i = 1; i < colspan; i++) {\n\t\t\theaderRow.push(`${text}${this.COLSPAN_SUFFIX_PREFIX}${i}`);\n\t\t}\n\t}\n\n\tprivate static handleRowspan(\n\t\trowSpans: Map<number, string>,\n\t\ttext: string,\n\t\tcolumnIndex: number,\n\t\tcolspan: number,\n\t\trowspan: number\n\t): void {\n\t\tif (rowspan > 1) {\n\t\t\tfor (let i = 0; i < colspan; i++) {\n\t\t\t\trowSpans.set(columnIndex + i, text);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static addRemainingRowSpans(headerRow: HeaderRow, rowSpans: Map<number, string>, columnIndex: number): void {\n\t\twhile (rowSpans.has(columnIndex)) {\n\t\t\theaderRow.push(rowSpans.get(columnIndex)!);\n\t\t\trowSpans.delete(columnIndex);\n\t\t\tcolumnIndex++;\n\t\t}\n\t}\n\n\tprivate static applyColspanOptions(headerRows: HeaderRow[], options: Required<HeaderRowOptions>): void {\n\t\tif (!options.colspan.enabled) {\n\t\t\tthis.removeColspanCells(headerRows);\n\t\t}\n\n\t\tif (!options.colspan.suffix) {\n\t\t\tthis.removeColspanSuffix(headerRows);\n\t\t}\n\t}\n\n\tprivate static removeColspanCells(headerRows: HeaderRow[]): void {\n\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\theaderRows[i] = headerRows[i].filter(cell => !this.COLSPAN_PATTERN.test(cell));\n\t\t}\n\t}\n\n\tprivate static removeColspanSuffix(headerRows: HeaderRow[]): void {\n\t\tfor (const headerRow of headerRows) {\n\t\t\tfor (let i = 0; i < headerRow.length; i++) {\n\t\t\t\tif (this.COLSPAN_PATTERN.test(headerRow[i])) {\n\t\t\t\t\theaderRow[i] = headerRow[i].replace(this.COLSPAN_PATTERN, \"\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate static applyDuplicateSuffix(headerRows: HeaderRow[], options: Required<HeaderRowOptions>): void {\n\t\tif (!options.duplicateSuffix) return;\n\n\t\tfor (const headerRow of headerRows) {\n\t\t\tconst headerCountMap: Map<string, number> = new Map();\n\t\t\tfor (let i = 0; i < headerRow.length; i++) {\n\t\t\t\tconst header = headerRow[i];\n\t\t\t\tconst currentCount = headerCountMap.get(header);\n\t\t\t\tif (currentCount !== undefined) {\n\t\t\t\t\theaderRow[i] = `${header}${this.DUPLICATE_SUFFIX_PREFIX}${currentCount}`;\n\t\t\t\t\theaderCountMap.set(header, currentCount + 1);\n\t\t\t\t} else {\n\t\t\t\t\theaderCountMap.set(header, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","import { Locator } from \"@playwright/test\";\n\nimport { CellContentType } from \"./cell-content-type\";\nimport { pollTable, PollingOptions } from \"./polling\";\nimport { BodyRow, Cell, HeaderRow } from \"./row\";\nimport { TableBody } from \"./table-body\";\nimport { HeaderRowOptions, TableHeader } from \"./table-header\";\n\n/**\n * Options for loading table data, headers, and rows.\n *\n * @example\n * // Use custom cell content type for body rows and header options\n * const options: TableOptions = {\n * bodyRowOptions: { cellContentType: CellContentType.TextContent },\n * headerRowOptions: {\n * colspan: { enabled: true, suffix: true },\n * duplicateSuffix: true,\n * emptyCellReplacement: false,\n * },\n * };\n * await table.getBodyRows(options);\n */\nexport type TableOptions = {\n\t/** See {@link HeaderRowOptions} for available options. */\n\theaderRowOptions?: HeaderRowOptions;\n\t/** Options for parsing body rows. */\n\tbodyRowOptions?: { cellContentType?: CellContentType };\n\t/** @deprecated Use `bodyRowOptions.cellContentType` instead. */\n\tcellContentType?: CellContentType;\n};\n\n/**\n * Represents a table in Playwright, providing methods to interact with its headers, rows, and cells.\n */\nexport class PlaywrightTable {\n\tprivate _bodyRowLocator: Locator;\n\tprivate _bodyRowColumnSelector: string;\n\tprivate _headerRowLocator: Locator;\n\tprivate _headerColumnSelector: string;\n\n\t/**\n\t * Creates an instance of the table handler with customizable selectors for headers and rows.\n\t *\n\t * @param _tableLocator - The Playwright `Locator` pointing to the root table element.\n\t * @param _options - Optional configuration for customizing header and row selectors.\n\t * @param _options.header - Options for header row and columns.\n\t * @param _options.header.setMainHeaderRow - (Optional) Index of the main header row if multiple header rows exist.\n\t * @param _options.header.rowSelector - (Optional) CSS selector for locating header rows. Defaults to `\"thead>tr\"`.\n\t * @param _options.header.columnSelector - (Optional) CSS selector for locating header columns. Defaults to `\"th\"`.\n\t * @param _options.row - Options for body rows and columns.\n\t * @param _options.row.rowSelector - (Optional) CSS selector for locating body rows. Defaults to `\"tbody>tr\"`.\n\t * @param _options.row.columnSelector - (Optional) CSS selector for locating body columns. Defaults to `\"td\"`.\n\t *\n\t * @example\n\t * const table = new PlaywrightTable(\n\t * page.locator('table.my-table'),\n\t * {\n\t * header: {\n\t * rowSelector: 'thead tr.header',\n\t * columnSelector: 'th.header-cell'\n\t * },\n\t * row: {\n\t * rowSelector: 'tbody tr.data-row',\n\t * columnSelector: 'td.data-cell'\n\t * }\n\t * }\n\t * );\n\t */\n\tconstructor(\n\t\tprivate readonly _tableLocator: Locator,\n\t\tprivate readonly _options?: {\n\t\t\theader?: {\n\t\t\t\tsetMainHeaderRow?: number;\n\t\t\t\trowSelector?: string;\n\t\t\t\tcolumnSelector?: string;\n\t\t\t};\n\t\t\trow?: {\n\t\t\t\trowSelector?: string;\n\t\t\t\tcolumnSelector?: string;\n\t\t\t};\n\t\t}\n\t) {\n\t\tconst headerRowSelector = this._options?.header?.rowSelector;\n\t\tconst bodyRowSelector = this._options?.row?.rowSelector;\n\t\tif (headerRowSelector && bodyRowSelector && headerRowSelector === bodyRowSelector) {\n\t\t\tconsole.warn(\n\t\t\t\t`PlaywrightTable: header.rowSelector and row.rowSelector are identical (\"${headerRowSelector}\"). ` +\n\t\t\t\t\t`This can cause header rows to be included in body rows. Consider scoping selectors to separate row groups.`\n\t\t\t);\n\t\t}\n\n\t\tthis._headerRowLocator = this._tableLocator.locator(this._options?.header?.rowSelector ?? \"thead>tr\");\n\t\tthis._headerColumnSelector = this._options?.header?.columnSelector ?? \"th\";\n\t\tthis._bodyRowLocator = this._tableLocator.locator(this._options?.row?.rowSelector ?? \"tbody>tr\");\n\t\tthis._bodyRowColumnSelector = this._options?.row?.columnSelector ?? \"td\";\n\t}\n\n\t/**\n\t * Retrieves all header rows of the table.\n\t * @param options - Optional parameters for header row parsing configuration.\n\t * @returns A promise that resolves to an array of header rows.\n\t * @throws Error if no header rows found after table loads.\n\t *\n\t * @example\n\t * // Get headers with colspan enabled\n\t * const headers = await table.getHeaderRows({\n\t * colspan: { enabled: true, suffix: true },\n\t * duplicateSuffix: true\n\t * });\n\t *\n\t * @see {@link getMainHeaderRow} for getting only the main header row\n\t * @see {@link HeaderRowOptions} for all available configuration options\n\t */\n\tasync getHeaderRows(options?: HeaderRowOptions): Promise<HeaderRow[]> {\n\t\tconst table = await this.getTable({\n\t\t\theaderRowOptions: options,\n\t\t});\n\t\treturn table.headerRows;\n\t}\n\n\t/**\n\t * Retrieves the main header row of the table.\n\t * By default, uses the last header row if multiple exist. Can be customized via constructor.\n\t * @param options - Optional parameters for header row parsing configuration.\n\t * @returns A promise that resolves to the main header row.\n\t * @throws Error if no header rows available.\n\t * @throws Error if setMainHeaderRow index is out of bounds (negative or >= header row count).\n\t *\n\t * @example\n\t * // Get main header with custom index set in constructor\n\t * const table = new PlaywrightTable(page.locator('table'), {\n\t * header: { setMainHeaderRow: 0 } // Use first row as main header\n\t * });\n\t * const mainHeader = await table.getMainHeaderRow();\n\t *\n\t * @see {@link getHeaderRows} for getting all header rows\n\t */\n\tasync getMainHeaderRow(options?: HeaderRowOptions): Promise<HeaderRow> {\n\t\tconst table = await this.getTable({\n\t\t\theaderRowOptions: options,\n\t\t});\n\t\treturn this.mainHeaderRow(table.headerRows);\n\t}\n\n\t/**\n\t * Retrieves all body rows of the table.\n\t * @param options - Optional table or body-row configuration.\n\t * @param options.cellContentType - Legacy shortcut for body row content extraction. See {@link CellContentType}.\n\t * @param options.bodyRowOptions - Body row parsing options.\n\t * @param options.headerRowOptions - Header parsing options used when resolving row/colspan layouts.\n\t * @returns A promise that resolves to an array of body rows.\n\t * @throws Error if no body rows found after table loads.\n\t *\n\t * @example\n\t * // Get body rows with TextContent (includes hidden elements)\n\t * const rows = await table.getBodyRows({\n\t * cellContentType: CellContentType.TextContent\n\t * });\n\t *\n\t * @example\n\t * // Get body rows with default InnerText\n\t * const rows = await table.getBodyRows();\n\t *\n\t * @example\n\t * // Use full table options (for example to sanitize noisy treegrid headers)\n\t * const rows = await table.getBodyRows({\n\t * bodyRowOptions: { cellContentType: CellContentType.InnerText },\n\t * headerRowOptions: { normalizeWhitespace: true, stripIconGlyphs: true }\n\t * });\n\t */\n\tasync getBodyRows(options?: TableOptions): Promise<BodyRow[]> {\n\t\tconst tableOptions = this.normalizeGetBodyRowsOptions(options);\n\n\t\tconst table = await this.getTable({\n\t\t\tbodyRowOptions: tableOptions.bodyRowOptions,\n\t\t\theaderRowOptions: this.resolveHeaderRowOptions(tableOptions.headerRowOptions, {\n\t\t\t\tforceColspanEnabled: true,\n\t\t\t}),\n\t\t});\n\t\treturn table.bodyRows;\n\t}\n\n\t/**\n\t * Retrieves a specific cell locator in the body of the table by row and column index.\n\t * @param rowNumber - The row number (0-based index).\n\t * @param headerPosition - The column/header position (0-based index).\n\t * @returns A Playwright Locator for the specified cell.\n\t *\n\t * @example\n\t * // Get cell at row 1, column 2\n\t * const cellLocator = table.getBodyCellLocator(1, 2);\n\t * await expect(cellLocator).toHaveText('Expected Text');\n\t *\n\t * @example\n\t * // Get first cell in first row\n\t * const firstCell = table.getBodyCellLocator(0, 0);\n\t * await firstCell.click();\n\t *\n\t * @see {@link getBodyCellLocatorByRowConditions} for finding cells by content matching\n\t * @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting all cells in a column\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells under a header\n\t */\n\tgetBodyCellLocator(rowNumber: number, headerPosition: number): Locator {\n\t\treturn this._bodyRowLocator.nth(rowNumber).locator(this._bodyRowColumnSelector).nth(headerPosition);\n\t}\n\n\t/**\n\t * Retrieves a cell locator in the body of the table based on row conditions and a target header.\n\t * Finds the first row where all conditions match, then returns the cell in the target column.\n\t * @param conditions - An object where keys are header names and values are expected cell contents, or a single [header, value] tuple.\n\t * @param targetHeader - The header name of the column containing the cell to retrieve.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to a Playwright Locator for the matching cell.\n\t * @throws Error if no row matches all specified conditions.\n\t * @throws Error if any condition header or target header is not found in the table.\n\t *\n\t * @example\n\t * // Find cell with multiple conditions\n\t * const emailLocator = await table.getBodyCellLocatorByRowConditions(\n\t * { \"First name\": \"John\", \"Status\": \"Active\" },\n\t * \"Email\"\n\t * );\n\t * await expect(emailLocator).toContainText('@example.com');\n\t *\n\t * @example\n\t * // Find cell with single condition using tuple syntax\n\t * const actionCell = await table.getBodyCellLocatorByRowConditions(\n\t * [\"Username\", \"john.doe\"],\n\t * \"Actions\"\n\t * );\n\t * await actionCell.locator('button').click();\n\t *\n\t * @example\n\t * // Find cell with single condition using object syntax\n\t * const statusCell = await table.getBodyCellLocatorByRowConditions(\n\t * { \"Username\": \"john.doe\" },\n\t * \"Status\"\n\t * );\n\t *\n\t * @see {@link getBodyCellLocator} for getting cells by direct index\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells in a column\n\t */\n\tasync getBodyCellLocatorByRowConditions(\n\t\tconditions: Record<string, string> | [string, string],\n\t\ttargetHeader: string,\n\t\toptions?: TableOptions\n\t): Promise<Locator> {\n\t\tconst table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));\n\t\tconst mainHeader = this.mainHeaderRow(table.headerRows);\n\n\t\tconst targetHeaderIndex = this.getHeaderIndex(targetHeader, mainHeader);\n\n\t\t// Normalize conditions to object format\n\t\tconst conditionsObj = Array.isArray(conditions) ? { [conditions[0]]: conditions[1] } : conditions;\n\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst row = table.bodyRows[rowIndex];\n\t\t\tlet matches = true;\n\n\t\t\tfor (const [header, value] of Object.entries(conditionsObj)) {\n\t\t\t\tconst headerIndex = this.getHeaderIndex(header, mainHeader);\n\t\t\t\tconst cellValue = row[headerIndex] ?? \"\";\n\t\t\t\tif (cellValue !== value) {\n\t\t\t\t\tmatches = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (matches) {\n\t\t\t\treturn this.getBodyCellLocator(rowIndex, targetHeaderIndex);\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t`No row found matching conditions: ${JSON.stringify(conditionsObj)}\\n` +\n\t\t\t\t`Target header: \"${targetHeader}\"\\n` +\n\t\t\t\t`Searched ${table.bodyRows.length} rows\\n` +\n\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t);\n\t}\n\n\t/**\n\t * Retrieves all cell locators in the body of the table for a specific header name.\n\t * Returns locators for every cell in the column identified by the header.\n\t * @param header - The header name identifying the column.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to an array of Playwright Locators for all cells in the column.\n\t * @throws Error if the header is not found in the table.\n\t *\n\t * @example\n\t * // Get all cells in the \"Status\" column\n\t * const statusCells = await table.getAllBodyCellLocatorsByHeaderName(\"Status\");\n\t * for (const cell of statusCells) {\n\t * await expect(cell).toHaveText(/Active|Inactive/);\n\t * }\n\t *\n\t * @example\n\t * // Get text from all cells in \"Name\" column\n\t * const nameCells = await table.getAllBodyCellLocatorsByHeaderName(\"Name\");\n\t * const names = await Promise.all(nameCells.map(cell => cell.textContent()));\n\t *\n\t * @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting cells by column index\n\t * @see {@link getBodyCellLocatorByRowConditions} for finding specific cells by content\n\t */\n\tasync getAllBodyCellLocatorsByHeaderName(header: string, options?: TableOptions): Promise<Locator[]> {\n\t\tconst table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));\n\t\tconst headers = this.mainHeaderRow(table.headerRows);\n\t\tconst headerIndex = this.getHeaderIndex(header, headers);\n\t\tconst locators: Locator[] = [];\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst cellLocator = this.getBodyCellLocator(rowIndex, headerIndex);\n\t\t\tlocators.push(cellLocator);\n\t\t}\n\t\treturn locators;\n\t}\n\n\t/**\n\t * Retrieves all cell locators in the body of the table for a specific header index.\n\t * Returns locators for every cell in the column at the specified position.\n\t * @param headerIndex - The header/column index (0-based).\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to an array of Playwright Locators for all cells in the column.\n\t * @throws Error if the header index is out of bounds (negative or >= column count).\n\t *\n\t * @example\n\t * // Get all cells in the third column (index 2)\n\t * const thirdColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(2);\n\t * for (const cell of thirdColumnCells) {\n\t * await expect(cell).toBeVisible();\n\t * }\n\t *\n\t * @example\n\t * // Get all cells in first column\n\t * const firstColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(0);\n\t * const values = await Promise.all(firstColumnCells.map(c => c.textContent()));\n\t *\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting cells by header name\n\t * @see {@link getBodyCellLocator} for getting a specific cell by row and column index\n\t */\n\tasync getAllBodyCellLocatorsByHeaderIndex(headerIndex: number, options?: TableOptions): Promise<Locator[]> {\n\t\tconst table = await this.getTable(options);\n\t\tconst columnCount = this.mainHeaderRow(table.headerRows).length;\n\t\tif (headerIndex < 0 || headerIndex >= columnCount) {\n\t\t\tthrow new Error(\n\t\t\t\t`Header index ${headerIndex} out of bounds. Table has ${columnCount} columns.\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\t\tconst locators: Locator[] = [];\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst cellLocator = this.getBodyCellLocator(rowIndex, headerIndex);\n\t\t\tlocators.push(cellLocator);\n\t\t}\n\t\treturn locators;\n\t}\n\n\t/**\n\t * Converts the table data into a JSON array where each object represents a row.\n\t * Keys are header names, values are cell contents.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to an array of objects representing table rows.\n\t * @throws Error if no header rows or body rows found.\n\t *\n\t * @example\n\t * // Convert table to JSON with default options\n\t * const data = await table.getJson();\n\t * // Result: [{\"Name\": \"John\", \"Age\": \"30\"}, {\"Name\": \"Jane\", \"Age\": \"25\"}]\n\t *\n\t * @example\n\t * // Convert with colspan enabled and duplicate suffix\n\t * const data = await table.getJson({\n\t * headerRowOptions: {\n\t * colspan: { enabled: true, suffix: true },\n\t * duplicateSuffix: true,\n\t * emptyCellReplacement: true\n\t * }\n\t * });\n\t */\n\tasync getJson(options?: TableOptions): Promise<Record<string, string>[]> {\n\t\tconst table = await this.getTable({\n\t\t\tbodyRowOptions: options?.bodyRowOptions,\n\t\t\theaderRowOptions: this.resolveHeaderRowOptionsForJsonKeys(options?.headerRowOptions),\n\t\t});\n\n\t\tconst headers = this.mainHeaderRow(table.headerRows);\n\t\treturn table.bodyRows.map(row => {\n\t\t\tconst rowObj: Record<string, Cell> = {};\n\t\t\theaders.forEach((header, index) => {\n\t\t\t\trowObj[header] = row[index] || \"\";\n\t\t\t});\n\t\t\treturn rowObj;\n\t\t});\n\t}\n\n\t/**\n\t * Waits for the table body to be empty (no body rows).\n\t * @param options - Polling options (timeout, interval, retries).\n\t * @returns A promise that resolves when table body is empty.\n\t * @throws Error if table is not empty within timeout period.\n\t *\n\t * @example\n\t * // Wait for table to clear after delete operation\n\t * await table.waitForEmpty({ timeout: 5000 });\n\t *\n\t * @see {@link waitForNonEmpty} for waiting until table has data\n\t *\n\t * @remarks\n\t * **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:\n\t * ```ts\n\t * await expect.poll(async () => (await table.getBodyRows()).length).toBe(0);\n\t * ```\n\t */\n\tasync waitForEmpty(options?: PollingOptions): Promise<void> {\n\t\tawait pollTable(async () => {\n\t\t\tconst rows = await this._bodyRowLocator.all();\n\t\t\tif (rows.length > 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected table to be empty, but found ${rows.length} body rows.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\t\t}, options);\n\t}\n\n\t/**\n\t * Waits for the table body to be non-empty (at least one body row with content).\n\t * A row is considered valid if it has at least one cell containing text.\n\t * @param options - Polling options (timeout, interval, retries).\n\t * @returns A promise that resolves when table has at least one body row with content.\n\t * @throws Error if table remains empty or has no rows with content within timeout period.\n\t *\n\t * @example\n\t * // Wait for table to populate after search\n\t * await table.waitForNonEmpty({ timeout: 5000 });\n\t *\n\t * @see {@link waitForEmpty} for waiting until table is cleared\n\t *\n\t * @remarks\n\t * **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:\n\t * ```ts\n\t * await expect.poll(async () => (await table.getBodyRows()).length).toBeGreaterThan(0);\n\t * ```\n\t */\n\tasync waitForNonEmpty(options?: PollingOptions): Promise<void> {\n\t\tawait pollTable(async () => {\n\t\t\tconst rows = await this._bodyRowLocator.all();\n\t\t\tif (rows.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected table to be non-empty, but found 0 body rows.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Check if at least one row has content in at least one cell\n\t\t\tlet hasRowWithContent = false;\n\t\t\tfor (const row of rows) {\n\t\t\t\tconst cells = await row.locator(this._bodyRowColumnSelector).all();\n\t\t\t\tfor (const cell of cells) {\n\t\t\t\t\tconst content = await cell.innerText();\n\t\t\t\t\tif (content.trim().length > 0) {\n\t\t\t\t\t\thasRowWithContent = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (hasRowWithContent) break;\n\t\t\t}\n\n\t\t\tif (!hasRowWithContent) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected table to have at least one row with content, but all ${rows.length} rows are empty.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\t\t}, options);\n\t}\n\n\t/**\n\t * Waits for the table body to have exactly N rows.\n\t * @param count - The exact number of body rows expected.\n\t * @param options - Polling options (timeout, interval, retries).\n\t * @returns A promise that resolves when the table has exactly the specified number of rows.\n\t * @throws Error if row count doesn't match within timeout period.\n\t *\n\t * @example\n\t * // Wait for exactly 10 rows after pagination\n\t * await table.waitForExactRowCount(10, { timeout: 5000 });\n\t *\n\t * @example\n\t * // Verify table has exactly 0 rows (alternative to waitForEmpty)\n\t * await table.waitForExactRowCount(0);\n\t *\n\t * @see {@link getRowCount} for getting current row counts\n\t *\n\t * @remarks\n\t * **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:\n\t * ```ts\n\t * await expect.poll(async () => (await table.getBodyRows()).length).toBe(10);\n\t * ```\n\t */\n\tasync waitForExactRowCount(count: number, options?: PollingOptions): Promise<void> {\n\t\tif (count < 0 || !Number.isInteger(count)) {\n\t\t\tthrow new Error(`Row count must be a non-negative integer, got: ${count}`);\n\t\t}\n\n\t\tawait pollTable(async () => {\n\t\t\tconst rows = await this._bodyRowLocator.all();\n\t\t\tif (rows.length !== count) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Expected exactly ${count} rows, but found ${rows.length}.\\n` +\n\t\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}`\n\t\t\t\t);\n\t\t\t}\n\t\t}, options);\n\t}\n\n\t/**\n\t * Gets the count of header and body rows in the table.\n\t * Lightweight method that doesn't fetch cell data, only counts rows.\n\t * @returns A promise that resolves to an object with header and body row counts.\n\t *\n\t * @example\n\t * // Get current row counts\n\t * const counts = await table.getRowCount();\n\t * console.log(`Headers: ${counts.header}, Body: ${counts.body}`);\n\t *\n\t * @example\n\t * // Assert specific row count\n\t * const { body } = await table.getRowCount();\n\t * expect(body).toBe(10);\n\t */\n\tasync getRowCount(): Promise<{ header: number; body: number }> {\n\t\tconst [headerRows, bodyRows] = await Promise.all([this._headerRowLocator.all(), this._bodyRowLocator.all()]);\n\t\treturn {\n\t\t\theader: headerRows.length,\n\t\t\tbody: bodyRows.length,\n\t\t};\n\t}\n\n\t/**\n\t * Gets all distinct (unique) values from a specific column.\n\t * Returns a sorted array of unique string values, excluding empty values.\n\t * @param headerName - The header name of the column to extract distinct values from.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to a sorted array of distinct values.\n\t * @throws Error if the specified header is not found in the table.\n\t *\n\t * @example\n\t * // Get all unique status values\n\t * const statuses = await table.getDistinctColumnValues(\"Status\");\n\t * // Result: [\"Active\", \"Inactive\", \"Pending\"]\n\t *\n\t * @example\n\t * // Get distinct countries for filtering\n\t * const countries = await table.getDistinctColumnValues(\"Country\");\n\t * console.log(`Available countries: ${countries.join(\", \")}`);\n\t *\n\t * @example\n\t * // Verify expected values exist\n\t * const roles = await table.getDistinctColumnValues(\"Role\");\n\t * expect(roles).toContain(\"Admin\");\n\t * expect(roles).toHaveLength(3);\n\t *\n\t * @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cell locators in a column\n\t * @see {@link getJson} for getting full table data\n\t */\n\tasync getDistinctColumnValues(headerName: string, options?: TableOptions): Promise<string[]> {\n\t\tconst table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));\n\t\tconst mainHeader = this.mainHeaderRow(table.headerRows);\n\t\tconst headerIndex = this.getHeaderIndex(headerName, mainHeader);\n\n\t\tconst values = new Set<string>();\n\t\tfor (const row of table.bodyRows) {\n\t\t\tconst value = String(row[headerIndex] ?? \"\").trim();\n\t\t\tif (value) values.add(value);\n\t\t}\n\n\t\treturn Array.from(values).sort();\n\t}\n\n\t/**\n\t * Finds the index of the first body row matching the specified conditions.\n\t * Returns -1 if no matching row is found.\n\t * @param conditions - Record of header names and expected cell values to match.\n\t * @param options - Optional table loading options. See {@link TableOptions}.\n\t * @returns A promise that resolves to the 0-based index of the first matching row, or -1 if not found.\n\t * @throws Error if specified headers don't exist in the table.\n\t *\n\t * @example\n\t * // Find index of row where Status is \"Active\"\n\t * const index = await table.findRowIndex({ Status: \"Active\" });\n\t * if (index >= 0) {\n\t * console.log(`Found at row ${index}`);\n\t * }\n\t *\n\t * @example\n\t * // Find row and interact with it\n\t * const rowIndex = await table.findRowIndex({ Username: \"john.doe\" });\n\t * if (rowIndex >= 0) {\n\t * const cell = table.getBodyCellLocator(rowIndex, 0);\n\t * await cell.click();\n\t * }\n\t *\n\t * @see {@link getBodyCellLocatorByRowConditions} for getting cell locators from matching rows\n\t * @see {@link waitForRowByConditions} for waiting until matching row appears\n\t */\n\tasync findRowIndex(conditions: Record<string, string>, options?: TableOptions): Promise<number> {\n\t\tconst table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));\n\t\tconst mainHeader = this.mainHeaderRow(table.headerRows);\n\n\t\t// Validate all condition headers exist and get their indices\n\t\tconst headerIndices = new Map<string, number>();\n\t\tfor (const header of Object.keys(conditions)) {\n\t\t\theaderIndices.set(header, this.getHeaderIndex(header, mainHeader));\n\t\t}\n\n\t\t// Find first matching row\n\t\tfor (let rowIndex = 0; rowIndex < table.bodyRows.length; rowIndex++) {\n\t\t\tconst row = table.bodyRows[rowIndex];\n\t\t\tlet rowMatches = true;\n\n\t\t\tfor (const [header, expectedValue] of Object.entries(conditions)) {\n\t\t\t\tconst headerIndex = headerIndices.get(header)!;\n\t\t\t\tconst cellValue = String(row[headerIndex] ?? \"\");\n\t\t\t\tif (cellValue !== expectedValue) {\n\t\t\t\t\trowMatches = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rowMatches) {\n\t\t\t\treturn rowIndex;\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t}\n\n\tprivate async getTable(options?: TableOptions): Promise<{ headerRows: HeaderRow[]; bodyRows: BodyRow[] }> {\n\t\tconst [headerRows, bodyRows] = await Promise.all([\n\t\t\tTableHeader.getRows(this._headerRowLocator, this._headerColumnSelector, options?.headerRowOptions),\n\t\t\tTableBody.getRows(this._bodyRowLocator, this._bodyRowColumnSelector, options?.bodyRowOptions),\n\t\t]);\n\n\t\tif (headerRows.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No header rows found, is table loaded completely?\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}\\n` +\n\t\t\t\t\t`Table locator: ${this._tableLocator.toString()}`\n\t\t\t);\n\t\t}\n\t\tif (bodyRows.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No body rows found, is table loaded completely?\\n` +\n\t\t\t\t\t`Body row locator: ${this._bodyRowLocator.toString()}\\n` +\n\t\t\t\t\t`Table locator: ${this._tableLocator.toString()}`\n\t\t\t);\n\t\t}\n\n\t\treturn { headerRows, bodyRows };\n\t}\n\n\tprivate normalizeGetBodyRowsOptions(options?: TableOptions): TableOptions {\n\t\tif (!options) {\n\t\t\treturn {};\n\t\t}\n\n\t\tif (options.bodyRowOptions || options.headerRowOptions) {\n\t\t\treturn options;\n\t\t}\n\n\t\tif (options.cellContentType !== undefined) {\n\t\t\treturn {\n\t\t\t\tbodyRowOptions: {\n\t\t\t\t\tcellContentType: options.cellContentType,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn options;\n\t}\n\n\tprivate resolveHeaderRowOptionsForJsonKeys(headerOptions?: HeaderRowOptions): HeaderRowOptions {\n\t\treturn this.resolveHeaderRowOptions(headerOptions, {\n\t\t\tdefaultColspanEnabled: true,\n\t\t\tdefaultColspanSuffix: true,\n\t\t\tdefaultDuplicateSuffix: true,\n\t\t\tdefaultEmptyCellReplacement: true,\n\t\t\tdefaultNormalizeWhitespace: false,\n\t\t\tdefaultStripIconGlyphs: false,\n\t\t});\n\t}\n\n\tprivate resolveTableOptionsForHeaderKeyMethods(options?: TableOptions): TableOptions {\n\t\tconst tableOptions = this.normalizeGetBodyRowsOptions(options);\n\t\treturn {\n\t\t\tbodyRowOptions: tableOptions.bodyRowOptions,\n\t\t\theaderRowOptions: this.resolveHeaderRowOptionsForJsonKeys(tableOptions.headerRowOptions),\n\t\t};\n\t}\n\n\tprivate resolveHeaderRowOptions(\n\t\theaderOptions: HeaderRowOptions | undefined,\n\t\tdefaults?: {\n\t\t\tdefaultColspanEnabled?: boolean;\n\t\t\tdefaultColspanSuffix?: boolean;\n\t\t\tdefaultDuplicateSuffix?: boolean;\n\t\t\tdefaultEmptyCellReplacement?: boolean;\n\t\t\tdefaultNormalizeWhitespace?: boolean;\n\t\t\tdefaultStripIconGlyphs?: boolean;\n\t\t\tforceColspanEnabled?: boolean;\n\t\t}\n\t): HeaderRowOptions {\n\t\tconst resolvedColspanEnabled = defaults?.forceColspanEnabled\n\t\t\t? true\n\t\t\t: (headerOptions?.colspan?.enabled ?? defaults?.defaultColspanEnabled);\n\n\t\treturn {\n\t\t\tcellContentType: headerOptions?.cellContentType,\n\t\t\temptyCellReplacement: headerOptions?.emptyCellReplacement ?? defaults?.defaultEmptyCellReplacement,\n\t\t\tduplicateSuffix: headerOptions?.duplicateSuffix ?? defaults?.defaultDuplicateSuffix,\n\t\t\tnormalizeWhitespace: headerOptions?.normalizeWhitespace ?? defaults?.defaultNormalizeWhitespace,\n\t\t\tstripIconGlyphs: headerOptions?.stripIconGlyphs ?? defaults?.defaultStripIconGlyphs,\n\t\t\tcolspan: {\n\t\t\t\tenabled: resolvedColspanEnabled,\n\t\t\t\tsuffix: headerOptions?.colspan?.suffix ?? defaults?.defaultColspanSuffix,\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate mainHeaderRow(headers: HeaderRow[]): HeaderRow {\n\t\tif (headers.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No header rows available. Ensure table has been loaded.\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\n\t\tconst headerIndex = this._options?.header?.setMainHeaderRow ?? headers.length - 1;\n\t\tif (headerIndex < 0 || headerIndex >= headers.length) {\n\t\t\tthrow new Error(\n\t\t\t\t`Header row index ${headerIndex} out of bounds. Table has ${headers.length} header rows.\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\n\t\treturn headers[headerIndex];\n\t}\n\n\t/**\n\t * Gets the index of a header in the main header row, throwing an error if not found.\n\t * @param header - The header name to find.\n\t * @param mainHeader - The main header row to search in.\n\t * @returns The 0-based index of the header.\n\t * @throws Error if the header is not found.\n\t */\n\tprivate getHeaderIndex(header: string, mainHeader: HeaderRow): number {\n\t\tconst index = mainHeader.indexOf(header);\n\t\tif (index === -1) {\n\t\t\tthrow new Error(\n\t\t\t\t`Header \"${header}\" not found.\\n` +\n\t\t\t\t\t`Available headers: [${mainHeader.join(\", \")}]\\n` +\n\t\t\t\t\t`Header row locator: ${this._headerRowLocator.toString()}`\n\t\t\t);\n\t\t}\n\t\treturn index;\n\t}\n}\n"],"mappings":"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","names":["CellContentType"]}
|
package/dist/index.mjs
CHANGED
|
@@ -291,14 +291,16 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
291
291
|
return headerRows;
|
|
292
292
|
}
|
|
293
293
|
static getDefaultOptions(options) {
|
|
294
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
294
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
295
295
|
return {
|
|
296
296
|
cellContentType: (_a = options == null ? void 0 : options.cellContentType) != null ? _a : "innerText" /* InnerText */,
|
|
297
297
|
emptyCellReplacement: (_b = options == null ? void 0 : options.emptyCellReplacement) != null ? _b : false,
|
|
298
298
|
duplicateSuffix: (_c = options == null ? void 0 : options.duplicateSuffix) != null ? _c : false,
|
|
299
|
+
normalizeWhitespace: (_d = options == null ? void 0 : options.normalizeWhitespace) != null ? _d : false,
|
|
300
|
+
stripIconGlyphs: (_e = options == null ? void 0 : options.stripIconGlyphs) != null ? _e : false,
|
|
299
301
|
colspan: {
|
|
300
|
-
enabled: (
|
|
301
|
-
suffix: (
|
|
302
|
+
enabled: (_g = (_f = options == null ? void 0 : options.colspan) == null ? void 0 : _f.enabled) != null ? _g : false,
|
|
303
|
+
suffix: (_i = (_h = options == null ? void 0 : options.colspan) == null ? void 0 : _h.suffix) != null ? _i : false
|
|
302
304
|
}
|
|
303
305
|
};
|
|
304
306
|
}
|
|
@@ -308,7 +310,11 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
308
310
|
if (cells.length === 0) {
|
|
309
311
|
return headerRow;
|
|
310
312
|
}
|
|
311
|
-
const cellDataArray = await Promise.all(
|
|
313
|
+
const cellDataArray = await Promise.all(
|
|
314
|
+
cells.map(
|
|
315
|
+
(cell) => this.fetchHeaderCellData(cell, options.cellContentType, options.normalizeWhitespace, options.stripIconGlyphs)
|
|
316
|
+
)
|
|
317
|
+
);
|
|
312
318
|
let columnIndex = 0;
|
|
313
319
|
for (let i = 0; i < cellDataArray.length; i++) {
|
|
314
320
|
columnIndex = this.processHeaderCellData(
|
|
@@ -326,17 +332,27 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
326
332
|
* Fetches header cell content and span attributes in parallel.
|
|
327
333
|
* This method performs DOM operations that can be parallelized across multiple cells.
|
|
328
334
|
*/
|
|
329
|
-
static async fetchHeaderCellData(cell, cellContentType) {
|
|
335
|
+
static async fetchHeaderCellData(cell, cellContentType, normalizeWhitespace, stripIconGlyphs) {
|
|
330
336
|
const [text, spanAttributes] = await Promise.all([
|
|
331
337
|
TableUtils.getCellContent(cell, cellContentType),
|
|
332
338
|
TableUtils.parseSpanAttributes(cell)
|
|
333
339
|
]);
|
|
334
340
|
return {
|
|
335
|
-
text,
|
|
341
|
+
text: this.sanitizeHeaderText(text, normalizeWhitespace, stripIconGlyphs),
|
|
336
342
|
rowspan: spanAttributes.rowspan,
|
|
337
343
|
colspan: spanAttributes.colspan
|
|
338
344
|
};
|
|
339
345
|
}
|
|
346
|
+
static sanitizeHeaderText(text, normalizeWhitespace, stripIconGlyphs) {
|
|
347
|
+
let result = text;
|
|
348
|
+
if (stripIconGlyphs) {
|
|
349
|
+
result = result.replace(this.ICON_GLYPH_PATTERN, "");
|
|
350
|
+
}
|
|
351
|
+
if (normalizeWhitespace) {
|
|
352
|
+
result = result.replace(this.CONTROL_WHITESPACE_PATTERN, " ").replace(this.WHITESPACE_PATTERN, " ");
|
|
353
|
+
}
|
|
354
|
+
return result.trim();
|
|
355
|
+
}
|
|
340
356
|
/**
|
|
341
357
|
* Processes pre-fetched header cell data and applies it to the header row.
|
|
342
358
|
* This method is purely computational and doesn't perform any DOM operations.
|
|
@@ -419,6 +435,9 @@ TableHeader.EMPTY_CELL_PLACEHOLDER = "{{Empty}}";
|
|
|
419
435
|
TableHeader.COLSPAN_SUFFIX_PREFIX = "__C";
|
|
420
436
|
TableHeader.DUPLICATE_SUFFIX_PREFIX = "__D";
|
|
421
437
|
TableHeader.COLSPAN_PATTERN = /__C\d+/;
|
|
438
|
+
TableHeader.ICON_GLYPH_PATTERN = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/gu;
|
|
439
|
+
TableHeader.CONTROL_WHITESPACE_PATTERN = /[\p{Cc}\p{Zl}\p{Zp}]+/gu;
|
|
440
|
+
TableHeader.WHITESPACE_PATTERN = /\s+/g;
|
|
422
441
|
|
|
423
442
|
// src/playwright-table.ts
|
|
424
443
|
var PlaywrightTable = class {
|
|
@@ -453,11 +472,18 @@ var PlaywrightTable = class {
|
|
|
453
472
|
constructor(_tableLocator, _options) {
|
|
454
473
|
this._tableLocator = _tableLocator;
|
|
455
474
|
this._options = _options;
|
|
456
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
475
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
|
|
476
|
+
const headerRowSelector = (_b = (_a = this._options) == null ? void 0 : _a.header) == null ? void 0 : _b.rowSelector;
|
|
477
|
+
const bodyRowSelector = (_d = (_c = this._options) == null ? void 0 : _c.row) == null ? void 0 : _d.rowSelector;
|
|
478
|
+
if (headerRowSelector && bodyRowSelector && headerRowSelector === bodyRowSelector) {
|
|
479
|
+
console.warn(
|
|
480
|
+
`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.`
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
this._headerRowLocator = this._tableLocator.locator((_g = (_f = (_e = this._options) == null ? void 0 : _e.header) == null ? void 0 : _f.rowSelector) != null ? _g : "thead>tr");
|
|
484
|
+
this._headerColumnSelector = (_j = (_i = (_h = this._options) == null ? void 0 : _h.header) == null ? void 0 : _i.columnSelector) != null ? _j : "th";
|
|
485
|
+
this._bodyRowLocator = this._tableLocator.locator((_m = (_l = (_k = this._options) == null ? void 0 : _k.row) == null ? void 0 : _l.rowSelector) != null ? _m : "tbody>tr");
|
|
486
|
+
this._bodyRowColumnSelector = (_p = (_o = (_n = this._options) == null ? void 0 : _n.row) == null ? void 0 : _o.columnSelector) != null ? _p : "td";
|
|
461
487
|
}
|
|
462
488
|
/**
|
|
463
489
|
* Retrieves all header rows of the table.
|
|
@@ -506,8 +532,10 @@ var PlaywrightTable = class {
|
|
|
506
532
|
}
|
|
507
533
|
/**
|
|
508
534
|
* Retrieves all body rows of the table.
|
|
509
|
-
* @param options - Optional
|
|
510
|
-
* @param options.cellContentType -
|
|
535
|
+
* @param options - Optional table or body-row configuration.
|
|
536
|
+
* @param options.cellContentType - Legacy shortcut for body row content extraction. See {@link CellContentType}.
|
|
537
|
+
* @param options.bodyRowOptions - Body row parsing options.
|
|
538
|
+
* @param options.headerRowOptions - Header parsing options used when resolving row/colspan layouts.
|
|
511
539
|
* @returns A promise that resolves to an array of body rows.
|
|
512
540
|
* @throws Error if no body rows found after table loads.
|
|
513
541
|
*
|
|
@@ -520,13 +548,21 @@ var PlaywrightTable = class {
|
|
|
520
548
|
* @example
|
|
521
549
|
* // Get body rows with default InnerText
|
|
522
550
|
* const rows = await table.getBodyRows();
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* // Use full table options (for example to sanitize noisy treegrid headers)
|
|
554
|
+
* const rows = await table.getBodyRows({
|
|
555
|
+
* bodyRowOptions: { cellContentType: CellContentType.InnerText },
|
|
556
|
+
* headerRowOptions: { normalizeWhitespace: true, stripIconGlyphs: true }
|
|
557
|
+
* });
|
|
523
558
|
*/
|
|
524
559
|
async getBodyRows(options) {
|
|
560
|
+
const tableOptions = this.normalizeGetBodyRowsOptions(options);
|
|
525
561
|
const table = await this.getTable({
|
|
526
|
-
bodyRowOptions:
|
|
527
|
-
headerRowOptions: {
|
|
528
|
-
|
|
529
|
-
}
|
|
562
|
+
bodyRowOptions: tableOptions.bodyRowOptions,
|
|
563
|
+
headerRowOptions: this.resolveHeaderRowOptions(tableOptions.headerRowOptions, {
|
|
564
|
+
forceColspanEnabled: true
|
|
565
|
+
})
|
|
530
566
|
});
|
|
531
567
|
return table.bodyRows;
|
|
532
568
|
}
|
|
@@ -591,7 +627,7 @@ var PlaywrightTable = class {
|
|
|
591
627
|
*/
|
|
592
628
|
async getBodyCellLocatorByRowConditions(conditions, targetHeader, options) {
|
|
593
629
|
var _a;
|
|
594
|
-
const table = await this.getTable(options);
|
|
630
|
+
const table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));
|
|
595
631
|
const mainHeader = this.mainHeaderRow(table.headerRows);
|
|
596
632
|
const targetHeaderIndex = this.getHeaderIndex(targetHeader, mainHeader);
|
|
597
633
|
const conditionsObj = Array.isArray(conditions) ? { [conditions[0]]: conditions[1] } : conditions;
|
|
@@ -641,7 +677,7 @@ Body row locator: ${this._bodyRowLocator.toString()}`
|
|
|
641
677
|
* @see {@link getBodyCellLocatorByRowConditions} for finding specific cells by content
|
|
642
678
|
*/
|
|
643
679
|
async getAllBodyCellLocatorsByHeaderName(header, options) {
|
|
644
|
-
const table = await this.getTable(options);
|
|
680
|
+
const table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));
|
|
645
681
|
const headers = this.mainHeaderRow(table.headerRows);
|
|
646
682
|
const headerIndex = this.getHeaderIndex(header, headers);
|
|
647
683
|
const locators = [];
|
|
@@ -713,17 +749,9 @@ Header row locator: ${this._headerRowLocator.toString()}`
|
|
|
713
749
|
* });
|
|
714
750
|
*/
|
|
715
751
|
async getJson(options) {
|
|
716
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
717
752
|
const table = await this.getTable({
|
|
718
753
|
bodyRowOptions: options == null ? void 0 : options.bodyRowOptions,
|
|
719
|
-
headerRowOptions:
|
|
720
|
-
colspan: {
|
|
721
|
-
enabled: (_c = (_b = (_a = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _a.colspan) == null ? void 0 : _b.enabled) != null ? _c : true,
|
|
722
|
-
suffix: (_f = (_e = (_d = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _d.colspan) == null ? void 0 : _e.suffix) != null ? _f : true
|
|
723
|
-
},
|
|
724
|
-
duplicateSuffix: (_h = (_g = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _g.duplicateSuffix) != null ? _h : true,
|
|
725
|
-
emptyCellReplacement: (_j = (_i = options == null ? void 0 : options.headerRowOptions) == null ? void 0 : _i.emptyCellReplacement) != null ? _j : true
|
|
726
|
-
}
|
|
754
|
+
headerRowOptions: this.resolveHeaderRowOptionsForJsonKeys(options == null ? void 0 : options.headerRowOptions)
|
|
727
755
|
});
|
|
728
756
|
const headers = this.mainHeaderRow(table.headerRows);
|
|
729
757
|
return table.bodyRows.map((row) => {
|
|
@@ -899,7 +927,7 @@ Body row locator: ${this._bodyRowLocator.toString()}`
|
|
|
899
927
|
*/
|
|
900
928
|
async getDistinctColumnValues(headerName, options) {
|
|
901
929
|
var _a;
|
|
902
|
-
const table = await this.getTable(options);
|
|
930
|
+
const table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));
|
|
903
931
|
const mainHeader = this.mainHeaderRow(table.headerRows);
|
|
904
932
|
const headerIndex = this.getHeaderIndex(headerName, mainHeader);
|
|
905
933
|
const values = /* @__PURE__ */ new Set();
|
|
@@ -937,10 +965,7 @@ Body row locator: ${this._bodyRowLocator.toString()}`
|
|
|
937
965
|
*/
|
|
938
966
|
async findRowIndex(conditions, options) {
|
|
939
967
|
var _a;
|
|
940
|
-
const table = await this.getTable(
|
|
941
|
-
headerRowOptions: { colspan: { enabled: true }, ...options == null ? void 0 : options.headerRowOptions },
|
|
942
|
-
bodyRowOptions: options == null ? void 0 : options.bodyRowOptions
|
|
943
|
-
});
|
|
968
|
+
const table = await this.getTable(this.resolveTableOptionsForHeaderKeyMethods(options));
|
|
944
969
|
const mainHeader = this.mainHeaderRow(table.headerRows);
|
|
945
970
|
const headerIndices = /* @__PURE__ */ new Map();
|
|
946
971
|
for (const header of Object.keys(conditions)) {
|
|
@@ -984,6 +1009,54 @@ Table locator: ${this._tableLocator.toString()}`
|
|
|
984
1009
|
}
|
|
985
1010
|
return { headerRows, bodyRows };
|
|
986
1011
|
}
|
|
1012
|
+
normalizeGetBodyRowsOptions(options) {
|
|
1013
|
+
if (!options) {
|
|
1014
|
+
return {};
|
|
1015
|
+
}
|
|
1016
|
+
if (options.bodyRowOptions || options.headerRowOptions) {
|
|
1017
|
+
return options;
|
|
1018
|
+
}
|
|
1019
|
+
if (options.cellContentType !== void 0) {
|
|
1020
|
+
return {
|
|
1021
|
+
bodyRowOptions: {
|
|
1022
|
+
cellContentType: options.cellContentType
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
return options;
|
|
1027
|
+
}
|
|
1028
|
+
resolveHeaderRowOptionsForJsonKeys(headerOptions) {
|
|
1029
|
+
return this.resolveHeaderRowOptions(headerOptions, {
|
|
1030
|
+
defaultColspanEnabled: true,
|
|
1031
|
+
defaultColspanSuffix: true,
|
|
1032
|
+
defaultDuplicateSuffix: true,
|
|
1033
|
+
defaultEmptyCellReplacement: true,
|
|
1034
|
+
defaultNormalizeWhitespace: false,
|
|
1035
|
+
defaultStripIconGlyphs: false
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
resolveTableOptionsForHeaderKeyMethods(options) {
|
|
1039
|
+
const tableOptions = this.normalizeGetBodyRowsOptions(options);
|
|
1040
|
+
return {
|
|
1041
|
+
bodyRowOptions: tableOptions.bodyRowOptions,
|
|
1042
|
+
headerRowOptions: this.resolveHeaderRowOptionsForJsonKeys(tableOptions.headerRowOptions)
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
resolveHeaderRowOptions(headerOptions, defaults) {
|
|
1046
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1047
|
+
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;
|
|
1048
|
+
return {
|
|
1049
|
+
cellContentType: headerOptions == null ? void 0 : headerOptions.cellContentType,
|
|
1050
|
+
emptyCellReplacement: (_c = headerOptions == null ? void 0 : headerOptions.emptyCellReplacement) != null ? _c : defaults == null ? void 0 : defaults.defaultEmptyCellReplacement,
|
|
1051
|
+
duplicateSuffix: (_d = headerOptions == null ? void 0 : headerOptions.duplicateSuffix) != null ? _d : defaults == null ? void 0 : defaults.defaultDuplicateSuffix,
|
|
1052
|
+
normalizeWhitespace: (_e = headerOptions == null ? void 0 : headerOptions.normalizeWhitespace) != null ? _e : defaults == null ? void 0 : defaults.defaultNormalizeWhitespace,
|
|
1053
|
+
stripIconGlyphs: (_f = headerOptions == null ? void 0 : headerOptions.stripIconGlyphs) != null ? _f : defaults == null ? void 0 : defaults.defaultStripIconGlyphs,
|
|
1054
|
+
colspan: {
|
|
1055
|
+
enabled: resolvedColspanEnabled,
|
|
1056
|
+
suffix: (_h = (_g = headerOptions == null ? void 0 : headerOptions.colspan) == null ? void 0 : _g.suffix) != null ? _h : defaults == null ? void 0 : defaults.defaultColspanSuffix
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
987
1060
|
mainHeaderRow(headers) {
|
|
988
1061
|
var _a, _b, _c;
|
|
989
1062
|
if (headers.length === 0) {
|