@rickcedwhat/playwright-smart-table 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Production-ready table testing for Playwright with smart column-aware locators.**
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@rickcedwhat/playwright-smart-table.svg)](https://www.npmjs.com/package/@rickcedwhat/playwright-smart-table)
5
+ [![npm version](https://img.shields.io/github/package-json/v/rickcedwhat/playwright-smart-table?label=npm&color=blue&t=2)](https://www.npmjs.com/package/@rickcedwhat/playwright-smart-table)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
8
  ---
@@ -84,7 +84,7 @@ const allActive = await table.findRows({ Status: 'Active' });
84
84
  - 📄 **Auto-Pagination** - Search across all pages automatically
85
85
  - 🔍 **Column-Aware Access** - Access cells by column name
86
86
  - 🛠️ **Debug Mode** - Visual debugging with slow motion and logging
87
- - 🔌 **Extensible Strategies** - Support any table implementation
87
+ - 🔌 **[Extensible Strategies](docs/concepts/strategies.md)** - Support any table implementation
88
88
  - 💪 **Type-Safe** - Full TypeScript support
89
89
  - 🚀 **Production-Ready** - Battle-tested in real-world applications
90
90
 
@@ -1,5 +1,5 @@
1
1
  import type { Locator, Page } from '@playwright/test';
2
- import { FinalTableConfig, Selector, SmartRow } from '../types';
2
+ import { FinalTableConfig, Selector, SmartRow, FilterValue } from '../types';
3
3
  import { FilterEngine } from '../filterEngine';
4
4
  import { TableMapper } from './tableMapper';
5
5
  import { SmartRowArray } from '../utils/smartRowArray';
@@ -12,15 +12,16 @@ export declare class RowFinder<T = any> {
12
12
  private resolve;
13
13
  constructor(rootLocator: Locator, config: FinalTableConfig, resolve: (item: Selector, parent: Locator | Page) => Locator, filterEngine: FilterEngine, tableMapper: TableMapper, makeSmartRow: (loc: Locator, map: Map<string, number>, index: number) => SmartRow<T>);
14
14
  private log;
15
- findRow(filters: Record<string, string | RegExp | number>, options?: {
15
+ findRow(filters: Record<string, FilterValue>, options?: {
16
16
  exact?: boolean;
17
17
  maxPages?: number;
18
18
  }): Promise<SmartRow<T>>;
19
- findRows<R extends {
20
- asJSON?: boolean;
21
- }>(filters: Partial<T> | Record<string, string | RegExp | number>, options?: {
19
+ findRows(filtersOrOptions?: (Partial<T> | Record<string, FilterValue>) & ({
22
20
  exact?: boolean;
23
21
  maxPages?: number;
24
- } & R): Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRowArray<T>>;
22
+ }), legacyOptions?: {
23
+ exact?: boolean;
24
+ maxPages?: number;
25
+ }): Promise<SmartRowArray<T>>;
25
26
  private findRowLocator;
26
27
  }
@@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
11
22
  Object.defineProperty(exports, "__esModule", { value: true });
12
23
  exports.RowFinder = void 0;
13
24
  const debugUtils_1 = require("../utils/debugUtils");
@@ -42,21 +53,48 @@ class RowFinder {
42
53
  return this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
43
54
  });
44
55
  }
45
- findRows(filters, options) {
56
+ findRows(filtersOrOptions,
57
+ // Deprecated: verify legacy usage pattern support
58
+ legacyOptions) {
46
59
  return __awaiter(this, void 0, void 0, function* () {
60
+ // Detect argument pattern:
61
+ // Pattern A: findRows({ Name: 'Alice' }, { maxPages: 5 })
62
+ // Pattern B: findRows({ maxPages: 5 }) <-- No filters, just options
63
+ // Pattern C: findRows({ Name: 'Alice' }) <-- Only filters
47
64
  var _a, _b;
65
+ let filters = {};
66
+ let options = {};
67
+ if (legacyOptions) {
68
+ // Pattern A
69
+ filters = filtersOrOptions;
70
+ options = legacyOptions;
71
+ }
72
+ else {
73
+ // Pattern B or C
74
+ // We need to separate unknown keys (filters) from known options (exact, maxPages)
75
+ // However, filtersOrOptions can be null/undefined
76
+ if (filtersOrOptions) {
77
+ const _c = filtersOrOptions, { exact, maxPages } = _c, rest = __rest(_c, ["exact", "maxPages"]);
78
+ options = { exact, maxPages };
79
+ filters = rest;
80
+ }
81
+ }
48
82
  const map = yield this.tableMapper.getMap();
49
83
  const allRows = [];
50
- const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
84
+ const effectiveMaxPages = (_b = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
51
85
  let pageCount = 0;
52
86
  const collectMatches = () => __awaiter(this, void 0, void 0, function* () {
53
87
  var _a, _b;
88
+ // ... logic ...
54
89
  let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
55
- rowLocators = this.filterEngine.applyFilters(rowLocators, filters, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
90
+ // Only apply filters if we have them
91
+ if (Object.keys(filters).length > 0) {
92
+ rowLocators = this.filterEngine.applyFilters(rowLocators, filters, map, (_a = options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
93
+ }
56
94
  const currentRows = yield rowLocators.all();
57
95
  const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
58
96
  for (let i = 0; i < currentRows.length; i++) {
59
- const smartRow = this.makeSmartRow(currentRows[i], map, i);
97
+ const smartRow = this.makeSmartRow(currentRows[i], map, allRows.length + i);
60
98
  if (isRowLoading && (yield isRowLoading(smartRow)))
61
99
  continue;
62
100
  allRows.push(smartRow);
@@ -64,31 +102,25 @@ class RowFinder {
64
102
  });
65
103
  // Scan first page
66
104
  yield collectMatches();
67
- // Pagination Loop
68
- while (pageCount < effectiveMaxPages && this.config.strategies.pagination) {
69
- // Check if pagination needed? findRows assumes we want ALL matches across maxPages.
70
- // If explicit maxPages is set, we paginate. If global maxPages is 1 (default), we stop.
71
- // Wait, loop condition `pageCount < effectiveMaxPages`. If maxPages=1, 0 < 1 is true.
72
- // We paginate AFTER first scan.
73
- // If maxPages=1, we should NOT paginate.
74
- if (effectiveMaxPages <= 1)
75
- break;
105
+ // Pagination Loop - Corrected logic
106
+ // We always scan at least 1 page.
107
+ // If maxPages > 1, and we have a pagination strategy, we try to go next.
108
+ while (pageCount < effectiveMaxPages - 1 && this.config.strategies.pagination) {
76
109
  const context = {
77
110
  root: this.rootLocator,
78
111
  config: this.config,
79
112
  resolve: this.resolve,
80
113
  page: this.rootLocator.page()
81
114
  };
115
+ // Check if we should stop? (e.g. if we found enough rows? No, findRows finds ALL)
82
116
  const paginationResult = yield this.config.strategies.pagination(context);
83
- const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
117
+ const didPaginate = yield (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
84
118
  if (!didPaginate)
85
119
  break;
86
120
  pageCount++;
121
+ // Wait for reload logic if needed? Usually pagination handles it.
87
122
  yield collectMatches();
88
123
  }
89
- if (options === null || options === void 0 ? void 0 : options.asJSON) {
90
- return Promise.all(allRows.map(r => r.toJSON()));
91
- }
92
124
  return (0, smartRowArray_1.createSmartRowArray)(allRows);
93
125
  });
94
126
  }
@@ -1,5 +1,5 @@
1
1
  import { Locator, Page } from "@playwright/test";
2
- import { FinalTableConfig } from "./types";
2
+ import { FinalTableConfig, FilterValue } from "./types";
3
3
  export declare class FilterEngine {
4
4
  private config;
5
5
  private resolve;
@@ -7,5 +7,5 @@ export declare class FilterEngine {
7
7
  /**
8
8
  * Applies filters to a set of rows.
9
9
  */
10
- applyFilters(baseRows: Locator, filters: Record<string, string | RegExp | number>, map: Map<string, number>, exact: boolean, page: Page): Locator;
10
+ applyFilters(baseRows: Locator, filters: Record<string, FilterValue>, map: Map<string, number>, exact: boolean, page: Page): Locator;
11
11
  }
@@ -20,7 +20,7 @@ class FilterEngine {
20
20
  if (colIndex === undefined) {
21
21
  throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
22
22
  }
23
- const filterVal = typeof value === 'number' ? String(value) : value;
23
+ const filterVal = value;
24
24
  // Use strategy if provided (For future: configured filter strategies)
25
25
  // But for now, we implement the default logic or use custom if we add it to config later
26
26
  // Default Filter Logic
@@ -29,9 +29,20 @@ class FilterEngine {
29
29
  // filter({ has: ... }) checks if the row *contains* the matching cell.
30
30
  // But we need to be specific about WHICH cell.
31
31
  // Locator filtering by `has: locator.nth(index)` works if `locator` search is relative to the row.
32
- filtered = filtered.filter({
33
- has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
34
- });
32
+ const targetCell = cellTemplate.nth(colIndex);
33
+ if (typeof filterVal === 'function') {
34
+ // Locator-based filter: (cell) => cell.locator(...)
35
+ filtered = filtered.filter({
36
+ has: filterVal(targetCell)
37
+ });
38
+ }
39
+ else {
40
+ // Text-based filter
41
+ const textVal = typeof filterVal === 'number' ? String(filterVal) : filterVal;
42
+ filtered = filtered.filter({
43
+ has: targetCell.getByText(textVal, { exact }),
44
+ });
45
+ }
35
46
  }
36
47
  return filtered;
37
48
  }
@@ -4,4 +4,4 @@ import { SmartRow as SmartRowType, FinalTableConfig, TableResult } from './types
4
4
  * Factory to create a SmartRow by extending a Playwright Locator.
5
5
  * We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
6
6
  */
7
- export declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null) => SmartRowType<T>;
7
+ export declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null) => SmartRowType<T>;
package/dist/smartRow.js CHANGED
@@ -39,13 +39,23 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
39
39
  return resolve(config.cellSelector, rowLocator).nth(idx);
40
40
  };
41
41
  smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
42
+ var _a;
42
43
  const result = {};
43
44
  const page = rootLocator.page();
44
45
  for (const [col, idx] of map.entries()) {
45
46
  if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(col)) {
46
47
  continue;
47
48
  }
48
- // Get the cell locator
49
+ // Check if we have a data mapper for this column
50
+ const mapper = (_a = config.dataMapper) === null || _a === void 0 ? void 0 : _a[col];
51
+ if (mapper) {
52
+ // Use custom mapper
53
+ // Ensure we have the cell first (same navigation logic)
54
+ // ... wait, the navigation logic below assumes we need to navigate.
55
+ // If we have a mapper, we still need the cell locator.
56
+ // Let's reuse the navigation logic to get targetCell
57
+ }
58
+ // --- Navigation Logic Start ---
49
59
  const cell = config.strategies.getCellLocator
50
60
  ? config.strategies.getCellLocator({
51
61
  row: rowLocator,
@@ -56,7 +66,6 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
56
66
  })
57
67
  : resolve(config.cellSelector, rowLocator).nth(idx);
58
68
  let targetCell = cell;
59
- // Check if cell exists
60
69
  const count = yield cell.count();
61
70
  if (count === 0) {
62
71
  // Optimization: Check if we are ALREADY at the target cell
@@ -68,47 +77,59 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
68
77
  resolve
69
78
  });
70
79
  if (active && active.rowIndex === rowIndex && active.columnIndex === idx) {
71
- if (config.debug)
72
- console.log(`[SmartRow] Already at target cell (r:${active.rowIndex}, c:${active.columnIndex}), skipping navigation.`);
73
80
  targetCell = active.locator;
74
- // Skip navigation and go to reading text
75
- const text = yield targetCell.innerText();
76
- result[col] = (text || '').trim();
77
- continue;
81
+ // Skip navigation
82
+ }
83
+ else {
84
+ // Cell doesn't exist - navigate to it
85
+ yield config.strategies.cellNavigation({
86
+ config: config,
87
+ root: rootLocator,
88
+ page: page,
89
+ resolve: resolve,
90
+ column: col,
91
+ index: idx,
92
+ rowLocator: rowLocator,
93
+ rowIndex: rowIndex
94
+ });
95
+ // Update targetCell after navigation if needed (e.g. active cell changed)
96
+ if (config.strategies.getActiveCell) {
97
+ const activeCell = yield config.strategies.getActiveCell({
98
+ config,
99
+ root: rootLocator,
100
+ page,
101
+ resolve
102
+ });
103
+ if (activeCell)
104
+ targetCell = activeCell.locator;
105
+ }
78
106
  }
79
107
  }
80
- // Cell doesn't exist - navigate to it
81
- if (config.debug) {
82
- console.log(`[SmartRow.toJSON] Cell not found for column "${col}" (index ${idx}), navigating...`);
83
- }
84
- yield config.strategies.cellNavigation({
85
- config: config,
86
- root: rootLocator,
87
- page: page,
88
- resolve: resolve,
89
- column: col,
90
- index: idx,
91
- rowLocator: rowLocator,
92
- rowIndex: rowIndex
93
- });
94
- // Optimization: check if we can get the active cell directly
95
- if (config.strategies.getActiveCell) {
96
- const activeCell = yield config.strategies.getActiveCell({
97
- config,
108
+ else {
109
+ // Fallback navigation without active cell check
110
+ yield config.strategies.cellNavigation({
111
+ config: config,
98
112
  root: rootLocator,
99
- page,
100
- resolve
113
+ page: page,
114
+ resolve: resolve,
115
+ column: col,
116
+ index: idx,
117
+ rowLocator: rowLocator,
118
+ rowIndex: rowIndex
101
119
  });
102
- if (activeCell) {
103
- if (config.debug) {
104
- console.log(`[SmartRow.toJSON] switching to active cell locator (r:${activeCell.rowIndex}, c:${activeCell.columnIndex})`);
105
- }
106
- targetCell = activeCell.locator;
107
- }
108
120
  }
109
121
  }
110
- const text = yield targetCell.innerText();
111
- result[col] = (text || '').trim();
122
+ // --- Navigation Logic End ---
123
+ if (mapper) {
124
+ // Apply mapper
125
+ const mappedValue = yield mapper(targetCell);
126
+ result[col] = mappedValue;
127
+ }
128
+ else {
129
+ // Default string extraction
130
+ const text = yield targetCell.innerText();
131
+ result[col] = (text || '').trim();
132
+ }
112
133
  }
113
134
  return result;
114
135
  });
@@ -3,4 +3,4 @@
3
3
  * This file is generated by scripts/embed-types.js
4
4
  * It contains the raw text of types.ts to provide context for LLM prompts.
5
5
  */
6
- export declare const TYPE_CONTEXT = "\n/**\n * Flexible selector type - can be a CSS string, function returning a Locator, or Locator itself.\n * @example\n * // String selector\n * rowSelector: 'tbody tr'\n * \n * // Function selector\n * rowSelector: (root) => root.locator('[role=\"row\"]')\n */\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\n/**\n * Function to get a cell locator given row, column info.\n * Replaces the old cellResolver.\n */\nexport type GetCellLocatorFn = (args: {\n row: Locator;\n columnName: string;\n columnIndex: number;\n rowIndex?: number;\n page: Page;\n}) => Locator;\n\n/**\n * Function to get the currently active/focused cell.\n * Returns null if no cell is active.\n */\nexport type GetActiveCellFn = (args: TableContext) => Promise<{\n rowIndex: number;\n columnIndex: number;\n columnName?: string;\n locator: Locator;\n} | null>;\n\n\n/**\n * SmartRow - A Playwright Locator with table-aware methods.\n * \n * Extends all standard Locator methods (click, isVisible, etc.) with table-specific functionality.\n * \n * @example\n * const row = table.getRow({ Name: 'John Doe' });\n * await row.click(); // Standard Locator method\n * const email = row.getCell('Email'); // Table-aware method\n * const data = await row.toJSON(); // Extract all row data\n * await row.smartFill({ Name: 'Jane', Status: 'Active' }); // Fill form fields\n */\nexport type SmartRow<T = any> = Locator & {\n /** Optional row index (0-based) if known */\n rowIndex?: number;\n\n /**\n * Get a cell locator by column name.\n * @param column - Column name (case-sensitive)\n * @returns Locator for the cell\n * @example\n * const emailCell = row.getCell('Email');\n * await expect(emailCell).toHaveText('john@example.com');\n */\n getCell(column: string): Locator;\n\n /**\n * Extract all cell data as a key-value object.\n * @param options - Optional configuration\n * @param options.columns - Specific columns to extract (extracts all if not specified)\n * @returns Promise resolving to row data\n * @example\n * const data = await row.toJSON();\n * // { Name: 'John', Email: 'john@example.com', ... }\n * \n * const partial = await row.toJSON({ columns: ['Name', 'Email'] });\n * // { Name: 'John', Email: 'john@example.com' }\n */\n toJSON(options?: { columns?: string[] }): Promise<T>;\n\n /**\n * Scrolls/paginates to bring this row into view.\n * Only works if rowIndex is known (e.g., from getRowByIndex).\n * @throws Error if rowIndex is unknown\n */\n bringIntoView(): Promise<void>;\n\n /**\n * Intelligently fills form fields in the row.\n * Automatically detects input types (text, select, checkbox, contenteditable).\n * \n * @param data - Column-value pairs to fill\n * @param options - Optional configuration\n * @param options.inputMappers - Custom input selectors per column\n * @example\n * // Auto-detection\n * await row.smartFill({ Name: 'John', Status: 'Active', Subscribe: true });\n * \n * // Custom input mappers\n * await row.smartFill(\n * { Name: 'John' },\n * { inputMappers: { Name: (cell) => cell.locator('.custom-input') } }\n * );\n */\n smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;\n};\n\nexport type StrategyContext = TableContext & { rowLocator?: Locator; rowIndex?: number };\n\n/**\n * Defines the contract for a sorting strategy.\n */\nexport interface SortingStrategy {\n /**\n * Performs the sort action on a column.\n */\n doSort(options: {\n columnName: string;\n direction: 'asc' | 'desc';\n context: StrategyContext;\n }): Promise<void>;\n\n /**\n * Retrieves the current sort state of a column.\n */\n getSortState(options: {\n columnName: string;\n context: StrategyContext;\n }): Promise<'asc' | 'desc' | 'none'>;\n}\n\n/**\n * Debug configuration for development and troubleshooting\n */\nexport type DebugConfig = {\n /**\n * Slow down operations for debugging\n * - number: Apply same delay to all operations (ms)\n * - object: Granular delays per operation type\n */\n slow?: number | {\n pagination?: number;\n getCell?: number;\n findRow?: number;\n default?: number;\n };\n /**\n * Log level for debug output\n * - 'verbose': All logs (verbose, info, error)\n * - 'info': Info and error logs only\n * - 'error': Error logs only\n * - 'none': No logs\n */\n logLevel?: 'verbose' | 'info' | 'error' | 'none';\n};\n\nexport interface TableContext {\n root: Locator;\n config: FinalTableConfig;\n page: Page;\n resolve: (selector: Selector, parent: Locator | Page) => Locator;\n}\n\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;\n\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n includeTypes?: boolean;\n}\n\nexport type FillStrategy = (options: {\n row: SmartRow;\n columnName: string;\n value: any;\n index: number;\n page: Page;\n rootLocator: Locator;\n table: TableResult; // The parent table instance\n fillOptions?: FillOptions;\n}) => Promise<void>;\n\nexport type { HeaderStrategy } from './strategies/headers';\nexport type { CellNavigationStrategy } from './strategies/columns';\n\n/**\n * Strategy to resolve column names (string or regex) to their index.\n */\nexport type { ColumnResolutionStrategy } from './strategies/resolution';\n\n/**\n * Strategy to filter rows based on criteria.\n */\nexport interface FilterStrategy {\n apply(options: {\n rows: Locator;\n filter: { column: string, value: string | RegExp | number };\n colIndex: number;\n tableContext: TableContext;\n }): Locator;\n}\n\n/**\n * Strategy to check if the table or rows are loading.\n */\nexport interface LoadingStrategy {\n isTableLoading?: (context: TableContext) => Promise<boolean>;\n isRowLoading?: (row: SmartRow) => Promise<boolean>;\n isHeaderLoading?: (context: TableContext) => Promise<boolean>;\n}\n\n/**\n * Organized container for all table interaction strategies.\n */\nexport interface TableStrategies {\n /** Strategy for discovering/scanning headers */\n header?: HeaderStrategy;\n /** Strategy for navigating to specific cells (row + column) */\n cellNavigation?: CellNavigationStrategy;\n /** Strategy for filling form inputs */\n fill?: FillStrategy;\n /** Strategy for paginating through data */\n pagination?: PaginationStrategy;\n /** Strategy for sorting columns */\n sorting?: SortingStrategy;\n /** Strategy for deduplicating rows during iteration/scrolling */\n dedupe?: DedupeStrategy;\n /** Function to get a cell locator */\n getCellLocator?: GetCellLocatorFn;\n /** Function to get the currently active/focused cell */\n getActiveCell?: GetActiveCellFn;\n /** Custom helper to check if a table is fully loaded/ready */\n isTableLoaded?: (args: TableContext) => Promise<boolean>;\n /** Custom helper to check if a row is fully loaded/ready */\n isRowLoaded?: (args: { row: Locator, index: number }) => Promise<boolean>;\n /** Custom helper to check if a cell is fully loaded/ready (e.g. for editing) */\n isCellLoaded?: (args: { cell: Locator, column: string, row: Locator }) => Promise<boolean>;\n /** Strategy for detecting loading states */\n loading?: LoadingStrategy;\n}\n\n/**\n * Configuration options for useTable.\n */\nexport interface TableConfig {\n /** Selector for the table headers */\n headerSelector?: string;\n /** Selector for the table rows */\n rowSelector?: string;\n /** Selector for the cells within a row */\n cellSelector?: string;\n /** Number of pages to scan for verification */\n maxPages?: number;\n /** Hook to rename columns dynamically */\n headerTransformer?: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n /** Automatically scroll to table on init */\n autoScroll?: boolean;\n /** Debug options for development and troubleshooting */\n debug?: DebugConfig;\n /** Reset hook */\n onReset?: (context: TableContext) => Promise<void>;\n /** All interaction strategies */\n strategies?: TableStrategies;\n}\n\nexport interface FinalTableConfig extends TableConfig {\n headerSelector: string;\n rowSelector: string;\n cellSelector: string;\n maxPages: number;\n autoScroll: boolean;\n debug?: TableConfig['debug'];\n headerTransformer: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n onReset: (context: TableContext) => Promise<void>;\n strategies: TableStrategies;\n}\n\n\nexport interface FillOptions {\n /**\n * Custom input mappers for specific columns.\n * Maps column names to functions that return the input locator for that cell.\n */\n inputMappers?: Record<string, (cell: Locator) => Locator>;\n}\n\n/**\n * Options for generateConfigPrompt\n */\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n /**\n * Include TypeScript type definitions in the prompt\n * @default true\n */\n includeTypes?: boolean;\n}\n\nexport interface TableResult<T = any> {\n /**\n * Initializes the table by resolving headers. Must be called before using sync methods.\n * @param options Optional timeout for header resolution (default: 3000ms)\n */\n init(options?: { timeout?: number }): Promise<TableResult>;\n\n /**\n * SYNC: Checks if the table has been initialized.\n * @returns true if init() has been called and completed, false otherwise\n */\n isInitialized(): boolean;\n\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n /**\n * Finds a row by filters on the current page only. Returns immediately (sync).\n * Throws error if table is not initialized.\n */\n getRow: (\n filters: Record<string, string | RegExp | number>,\n options?: { exact?: boolean }\n ) => SmartRow;\n\n /**\n * Gets a row by 1-based index on the current page.\n * Throws error if table is not initialized.\n * @param index 1-based row index\n * @param options Optional settings including bringIntoView\n */\n getRowByIndex: (\n index: number,\n options?: { bringIntoView?: boolean }\n ) => SmartRow;\n\n /**\n * ASYNC: Searches for a single row across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match and max pages\n */\n findRow: (\n filters: Record<string, string | RegExp | number>,\n options?: { exact?: boolean, maxPages?: number }\n ) => Promise<SmartRow>;\n\n /**\n * ASYNC: Searches for all matching rows across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match, max pages, and asJSON\n */\n findRows: <R extends { asJSON?: boolean }>(\n filters: Record<string, string | RegExp | number>,\n options?: { exact?: boolean, maxPages?: number } & R\n ) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRowArray>;\n\n /**\n * Navigates to a specific column using the configured CellNavigationStrategy.\n */\n scrollToColumn: (columnName: string) => Promise<void>;\n\n /**\n * Gets all rows on the current page only (does not paginate).\n * Auto-initializes the table if not already initialized.\n * Returns a SmartRowArray which extends Array with a toJSON() helper method.\n * @param options - Filter options\n */\n getRows: (options?: { filter?: Record<string, any>, exact?: boolean }) => Promise<SmartRowArray>;\n\n /**\n * Resets the table state (clears cache, flags) and invokes the onReset strategy.\n */\n reset: () => Promise<void>;\n\n /**\n * Revalidates the table's structure (headers, columns) without resetting pagination or state.\n * Useful when columns change visibility or order dynamically.\n */\n revalidate: () => Promise<void>;\n\n /**\n * Scans a specific column across all pages and returns the values.\n */\n getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;\n\n /**\n * Provides access to sorting actions and assertions.\n */\n sorting: {\n /**\n * Applies the configured sorting strategy to the specified column.\n * @param columnName The name of the column to sort.\n * @param direction The direction to sort ('asc' or 'desc').\n */\n apply(columnName: string, direction: 'asc' | 'desc'): Promise<void>;\n /**\n * Gets the current sort state of a column using the configured sorting strategy.\n * @param columnName The name of the column to check.\n * @returns A promise that resolves to 'asc', 'desc', or 'none'.\n */\n getState(columnName: string): Promise<'asc' | 'desc' | 'none'>;\n };\n\n /**\n * Iterates through paginated table data, calling the callback for each iteration.\n * Callback return values are automatically appended to allData, which is returned.\n */\n iterateThroughTable: <T = any>(\n callback: (context: {\n index: number;\n isFirst: boolean;\n isLast: boolean;\n rows: SmartRowArray;\n allData: T[];\n table: RestrictedTableResult;\n batchInfo?: {\n startIndex: number;\n endIndex: number;\n size: number;\n };\n\n }) => T | T[] | Promise<T | T[]>,\n options?: {\n pagination?: PaginationStrategy;\n dedupeStrategy?: DedupeStrategy;\n maxIterations?: number;\n batchSize?: number;\n getIsFirst?: (context: { index: number }) => boolean;\n getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;\n beforeFirst?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n afterLast?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n /**\n * If true, flattens array results from callback into the main data array.\n * If false (default), pushes the return value as-is (preserves batching/arrays).\n */\n autoFlatten?: boolean;\n }\n ) => Promise<T[]>;\n\n /**\n * Generate an AI-friendly configuration prompt for debugging.\n * Outputs table HTML and TypeScript definitions to help AI assistants generate config.\n */\n generateConfigPrompt: (options?: PromptOptions) => Promise<void>;\n}\n\n/**\n * Restricted table result that excludes methods that shouldn't be called during iteration.\n */\nexport type RestrictedTableResult<T = any> = Omit<TableResult<T>, 'searchForRow' | 'iterateThroughTable' | 'reset'>;\n";
6
+ export declare const TYPE_CONTEXT = "\n/**\n * Flexible selector type - can be a CSS string, function returning a Locator, or Locator itself.\n * @example\n * // String selector\n * rowSelector: 'tbody tr'\n * \n * // Function selector\n * rowSelector: (root) => root.locator('[role=\"row\"]')\n */\nexport type Selector = string | ((root: Locator | Page) => Locator);\n\n/**\n * Value used to filter rows.\n * - string/number/RegExp: filter by text content of the cell.\n * - function: filter by custom locator logic within the cell.\n * @example\n * // Text filter\n * { Name: 'John' }\n * \n * // Custom locator filter (e.g. checkbox is checked)\n * { Status: (cell) => cell.locator('input:checked') }\n */\nexport type FilterValue = string | RegExp | number | ((cell: Locator) => Locator);\n\n/**\n * Function to get a cell locator given row, column info.\n * Replaces the old cellResolver.\n */\nexport type GetCellLocatorFn = (args: {\n row: Locator;\n columnName: string;\n columnIndex: number;\n rowIndex?: number;\n page: Page;\n}) => Locator;\n\n/**\n * Function to get the currently active/focused cell.\n * Returns null if no cell is active.\n */\nexport type GetActiveCellFn = (args: TableContext) => Promise<{\n rowIndex: number;\n columnIndex: number;\n columnName?: string;\n locator: Locator;\n} | null>;\n\n\n/**\n * SmartRow - A Playwright Locator with table-aware methods.\n * \n * Extends all standard Locator methods (click, isVisible, etc.) with table-specific functionality.\n * \n * @example\n * const row = table.getRow({ Name: 'John Doe' });\n * await row.click(); // Standard Locator method\n * const email = row.getCell('Email'); // Table-aware method\n * const data = await row.toJSON(); // Extract all row data\n * await row.smartFill({ Name: 'Jane', Status: 'Active' }); // Fill form fields\n */\nexport type SmartRow<T = any> = Locator & {\n /** Optional row index (0-based) if known */\n rowIndex?: number;\n\n /**\n * Get a cell locator by column name.\n * @param column - Column name (case-sensitive)\n * @returns Locator for the cell\n * @example\n * const emailCell = row.getCell('Email');\n * await expect(emailCell).toHaveText('john@example.com');\n */\n getCell(column: string): Locator;\n\n /**\n * Extract all cell data as a key-value object.\n * @param options - Optional configuration\n * @param options.columns - Specific columns to extract (extracts all if not specified)\n * @returns Promise resolving to row data\n * @example\n * const data = await row.toJSON();\n * // { Name: 'John', Email: 'john@example.com', ... }\n * \n * const partial = await row.toJSON({ columns: ['Name', 'Email'] });\n * // { Name: 'John', Email: 'john@example.com' }\n */\n toJSON(options?: { columns?: string[] }): Promise<T>;\n\n /**\n * Scrolls/paginates to bring this row into view.\n * Only works if rowIndex is known (e.g., from getRowByIndex).\n * @throws Error if rowIndex is unknown\n */\n bringIntoView(): Promise<void>;\n\n /**\n * Intelligently fills form fields in the row.\n * Automatically detects input types (text, select, checkbox, contenteditable).\n * \n * @param data - Column-value pairs to fill\n * @param options - Optional configuration\n * @param options.inputMappers - Custom input selectors per column\n * @example\n * // Auto-detection\n * await row.smartFill({ Name: 'John', Status: 'Active', Subscribe: true });\n * \n * // Custom input mappers\n * await row.smartFill(\n * { Name: 'John' },\n * { inputMappers: { Name: (cell) => cell.locator('.custom-input') } }\n * );\n */\n smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;\n};\n\nexport type StrategyContext = TableContext & { rowLocator?: Locator; rowIndex?: number };\n\n/**\n * Defines the contract for a sorting strategy.\n */\nexport interface SortingStrategy {\n /**\n * Performs the sort action on a column.\n */\n doSort(options: {\n columnName: string;\n direction: 'asc' | 'desc';\n context: StrategyContext;\n }): Promise<void>;\n\n /**\n * Retrieves the current sort state of a column.\n */\n getSortState(options: {\n columnName: string;\n context: StrategyContext;\n }): Promise<'asc' | 'desc' | 'none'>;\n}\n\n/**\n * Debug configuration for development and troubleshooting\n */\nexport type DebugConfig = {\n /**\n * Slow down operations for debugging\n * - number: Apply same delay to all operations (ms)\n * - object: Granular delays per operation type\n */\n slow?: number | {\n pagination?: number;\n getCell?: number;\n findRow?: number;\n default?: number;\n };\n /**\n * Log level for debug output\n * - 'verbose': All logs (verbose, info, error)\n * - 'info': Info and error logs only\n * - 'error': Error logs only\n * - 'none': No logs\n */\n logLevel?: 'verbose' | 'info' | 'error' | 'none';\n};\n\nexport interface TableContext<T = any> {\n root: Locator;\n config: FinalTableConfig<T>;\n page: Page;\n resolve: (selector: Selector, parent: Locator | Page) => Locator;\n}\n\nexport type PaginationStrategy = (context: TableContext) => Promise<boolean>;\n\nexport type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;\n\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n includeTypes?: boolean;\n}\n\nexport type FillStrategy = (options: {\n row: SmartRow;\n columnName: string;\n value: any;\n index: number;\n page: Page;\n rootLocator: Locator;\n table: TableResult; // The parent table instance\n fillOptions?: FillOptions;\n}) => Promise<void>;\n\nexport type { HeaderStrategy } from './strategies/headers';\nexport type { CellNavigationStrategy } from './strategies/columns';\n\n/**\n * Strategy to resolve column names (string or regex) to their index.\n */\nexport type { ColumnResolutionStrategy } from './strategies/resolution';\n\n/**\n * Strategy to filter rows based on criteria.\n */\nexport interface FilterStrategy {\n apply(options: {\n rows: Locator;\n filter: { column: string, value: FilterValue };\n colIndex: number;\n tableContext: TableContext;\n }): Locator;\n}\n\n/**\n * Strategy to check if the table or rows are loading.\n */\nexport interface LoadingStrategy {\n isTableLoading?: (context: TableContext) => Promise<boolean>;\n isRowLoading?: (row: SmartRow) => Promise<boolean>;\n isHeaderLoading?: (context: TableContext) => Promise<boolean>;\n}\n\n/**\n * Organized container for all table interaction strategies.\n */\nexport interface TableStrategies {\n /** Strategy for discovering/scanning headers */\n header?: HeaderStrategy;\n /** Strategy for navigating to specific cells (row + column) */\n cellNavigation?: CellNavigationStrategy;\n /** Strategy for filling form inputs */\n fill?: FillStrategy;\n /** Strategy for paginating through data */\n pagination?: PaginationStrategy;\n /** Strategy for sorting columns */\n sorting?: SortingStrategy;\n /** Strategy for deduplicating rows during iteration/scrolling */\n dedupe?: DedupeStrategy;\n /** Function to get a cell locator */\n getCellLocator?: GetCellLocatorFn;\n /** Function to get the currently active/focused cell */\n getActiveCell?: GetActiveCellFn;\n /** Custom helper to check if a table is fully loaded/ready */\n isTableLoaded?: (args: TableContext) => Promise<boolean>;\n /** Custom helper to check if a row is fully loaded/ready */\n isRowLoaded?: (args: { row: Locator, index: number }) => Promise<boolean>;\n /** Custom helper to check if a cell is fully loaded/ready (e.g. for editing) */\n isCellLoaded?: (args: { cell: Locator, column: string, row: Locator }) => Promise<boolean>;\n /** Strategy for detecting loading states */\n loading?: LoadingStrategy;\n}\n\n/**\n * Configuration options for useTable.\n */\n/**\n * Configuration options for useTable.\n */\nexport interface TableConfig<T = any> {\n /** Selector for the table headers */\n headerSelector?: string;\n /** Selector for the table rows */\n rowSelector?: string;\n /** Selector for the cells within a row */\n cellSelector?: string;\n /** Number of pages to scan for verification */\n maxPages?: number;\n /** Hook to rename columns dynamically */\n headerTransformer?: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n /** Automatically scroll to table on init */\n autoScroll?: boolean;\n /** Debug options for development and troubleshooting */\n debug?: DebugConfig;\n /** Reset hook */\n onReset?: (context: TableContext) => Promise<void>;\n /** All interaction strategies */\n strategies?: TableStrategies;\n /**\n * Custom data mappers for specific columns.\n * Allows extracting complex data types (boolean, number) instead of just string.\n */\n dataMapper?: Partial<Record<keyof T, (cell: Locator) => Promise<T[keyof T]> | T[keyof T]>>;\n}\n\nexport interface FinalTableConfigLike<T = any> extends TableConfig<T> {\n headerSelector: string;\n rowSelector: string;\n cellSelector: string;\n maxPages: number;\n autoScroll: boolean;\n debug?: TableConfig['debug'];\n headerTransformer: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>;\n onReset: (context: TableContext) => Promise<void>;\n strategies: TableStrategies;\n}\n// Alias for backward compatibility if needed, or update usages\nexport type FinalTableConfig<T = any> = FinalTableConfigLike<T>;\n\n\nexport interface FillOptions {\n /**\n * Custom input mappers for specific columns.\n * Maps column names to functions that return the input locator for that cell.\n */\n inputMappers?: Record<string, (cell: Locator) => Locator>;\n}\n\n/**\n * Options for generateConfigPrompt\n */\nexport interface PromptOptions {\n /**\n * Output Strategy:\n * - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).\n * - 'console': Standard console logs (Default).\n */\n output?: 'console' | 'error';\n /**\n * Include TypeScript type definitions in the prompt\n * @default true\n */\n includeTypes?: boolean;\n}\n\nexport interface TableResult<T = any> {\n /**\n * Initializes the table by resolving headers. Must be called before using sync methods.\n * @param options Optional timeout for header resolution (default: 3000ms)\n */\n init(options?: { timeout?: number }): Promise<TableResult>;\n\n /**\n * SYNC: Checks if the table has been initialized.\n * @returns true if init() has been called and completed, false otherwise\n */\n isInitialized(): boolean;\n\n getHeaders: () => Promise<string[]>;\n getHeaderCell: (columnName: string) => Promise<Locator>;\n\n /**\n * Finds a row by filters on the current page only. Returns immediately (sync).\n * Throws error if table is not initialized.\n */\n getRow: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean }\n ) => SmartRow;\n\n /**\n * Gets a row by 1-based index on the current page.\n * Throws error if table is not initialized.\n * @param index 1-based row index\n * @param options Optional settings including bringIntoView\n */\n getRowByIndex: (\n index: number,\n options?: { bringIntoView?: boolean }\n ) => SmartRow;\n\n /**\n * ASYNC: Searches for a single row across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match and max pages\n */\n findRow: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean, maxPages?: number }\n ) => Promise<SmartRow>;\n\n /**\n * ASYNC: Searches for all matching rows across pages using pagination.\n * Auto-initializes the table if not already initialized.\n * @param filters - The filter criteria to match\n * @param options - Search options including exact match and max pages\n */\n findRows: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean, maxPages?: number }\n ) => Promise<SmartRowArray<T>>;\n\n /**\n * Navigates to a specific column using the configured CellNavigationStrategy.\n */\n scrollToColumn: (columnName: string) => Promise<void>;\n\n /**\n * Gets all rows on the current page only (does not paginate).\n * Auto-initializes the table if not already initialized.\n * Returns a SmartRowArray which extends Array with a toJSON() helper method.\n * @param options - Filter options\n */\n getRows: (options?: { filter?: Record<string, any>, exact?: boolean }) => Promise<SmartRowArray>;\n\n /**\n * Resets the table state (clears cache, flags) and invokes the onReset strategy.\n */\n reset: () => Promise<void>;\n\n /**\n * Revalidates the table's structure (headers, columns) without resetting pagination or state.\n * Useful when columns change visibility or order dynamically.\n */\n revalidate: () => Promise<void>;\n\n /**\n * Scans a specific column across all pages and returns the values.\n */\n getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;\n\n /**\n * Provides access to sorting actions and assertions.\n */\n sorting: {\n /**\n * Applies the configured sorting strategy to the specified column.\n * @param columnName The name of the column to sort.\n * @param direction The direction to sort ('asc' or 'desc').\n */\n apply(columnName: string, direction: 'asc' | 'desc'): Promise<void>;\n /**\n * Gets the current sort state of a column using the configured sorting strategy.\n * @param columnName The name of the column to check.\n * @returns A promise that resolves to 'asc', 'desc', or 'none'.\n */\n getState(columnName: string): Promise<'asc' | 'desc' | 'none'>;\n };\n\n /**\n * Iterates through paginated table data, calling the callback for each iteration.\n * Callback return values are automatically appended to allData, which is returned.\n */\n iterateThroughTable: <T = any>(\n callback: (context: {\n index: number;\n isFirst: boolean;\n isLast: boolean;\n rows: SmartRowArray;\n allData: T[];\n table: RestrictedTableResult;\n batchInfo?: {\n startIndex: number;\n endIndex: number;\n size: number;\n };\n\n }) => T | T[] | Promise<T | T[]>,\n options?: {\n pagination?: PaginationStrategy;\n dedupeStrategy?: DedupeStrategy;\n maxIterations?: number;\n batchSize?: number;\n getIsFirst?: (context: { index: number }) => boolean;\n getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;\n beforeFirst?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n afterLast?: (context: { index: number, rows: SmartRowArray, allData: any[] }) => void | Promise<void>;\n /**\n * If true, flattens array results from callback into the main data array.\n * If false (default), pushes the return value as-is (preserves batching/arrays).\n */\n autoFlatten?: boolean;\n }\n ) => Promise<T[]>;\n\n /**\n * Generate an AI-friendly configuration prompt for debugging.\n * Outputs table HTML and TypeScript definitions to help AI assistants generate config.\n */\n generateConfigPrompt: (options?: PromptOptions) => Promise<void>;\n}\n\n/**\n * Restricted table result that excludes methods that shouldn't be called during iteration.\n */\nexport type RestrictedTableResult<T = any> = Omit<TableResult<T>, 'searchForRow' | 'iterateThroughTable' | 'reset'>;\n";
@@ -18,6 +18,19 @@ exports.TYPE_CONTEXT = `
18
18
  */
19
19
  export type Selector = string | ((root: Locator | Page) => Locator);
20
20
 
21
+ /**
22
+ * Value used to filter rows.
23
+ * - string/number/RegExp: filter by text content of the cell.
24
+ * - function: filter by custom locator logic within the cell.
25
+ * @example
26
+ * // Text filter
27
+ * { Name: 'John' }
28
+ *
29
+ * // Custom locator filter (e.g. checkbox is checked)
30
+ * { Status: (cell) => cell.locator('input:checked') }
31
+ */
32
+ export type FilterValue = string | RegExp | number | ((cell: Locator) => Locator);
33
+
21
34
  /**
22
35
  * Function to get a cell locator given row, column info.
23
36
  * Replaces the old cellResolver.
@@ -158,9 +171,9 @@ export type DebugConfig = {
158
171
  logLevel?: 'verbose' | 'info' | 'error' | 'none';
159
172
  };
160
173
 
161
- export interface TableContext {
174
+ export interface TableContext<T = any> {
162
175
  root: Locator;
163
- config: FinalTableConfig;
176
+ config: FinalTableConfig<T>;
164
177
  page: Page;
165
178
  resolve: (selector: Selector, parent: Locator | Page) => Locator;
166
179
  }
@@ -204,7 +217,7 @@ export type { ColumnResolutionStrategy } from './strategies/resolution';
204
217
  export interface FilterStrategy {
205
218
  apply(options: {
206
219
  rows: Locator;
207
- filter: { column: string, value: string | RegExp | number };
220
+ filter: { column: string, value: FilterValue };
208
221
  colIndex: number;
209
222
  tableContext: TableContext;
210
223
  }): Locator;
@@ -252,7 +265,10 @@ export interface TableStrategies {
252
265
  /**
253
266
  * Configuration options for useTable.
254
267
  */
255
- export interface TableConfig {
268
+ /**
269
+ * Configuration options for useTable.
270
+ */
271
+ export interface TableConfig<T = any> {
256
272
  /** Selector for the table headers */
257
273
  headerSelector?: string;
258
274
  /** Selector for the table rows */
@@ -271,9 +287,14 @@ export interface TableConfig {
271
287
  onReset?: (context: TableContext) => Promise<void>;
272
288
  /** All interaction strategies */
273
289
  strategies?: TableStrategies;
290
+ /**
291
+ * Custom data mappers for specific columns.
292
+ * Allows extracting complex data types (boolean, number) instead of just string.
293
+ */
294
+ dataMapper?: Partial<Record<keyof T, (cell: Locator) => Promise<T[keyof T]> | T[keyof T]>>;
274
295
  }
275
296
 
276
- export interface FinalTableConfig extends TableConfig {
297
+ export interface FinalTableConfigLike<T = any> extends TableConfig<T> {
277
298
  headerSelector: string;
278
299
  rowSelector: string;
279
300
  cellSelector: string;
@@ -284,6 +305,8 @@ export interface FinalTableConfig extends TableConfig {
284
305
  onReset: (context: TableContext) => Promise<void>;
285
306
  strategies: TableStrategies;
286
307
  }
308
+ // Alias for backward compatibility if needed, or update usages
309
+ export type FinalTableConfig<T = any> = FinalTableConfigLike<T>;
287
310
 
288
311
 
289
312
  export interface FillOptions {
@@ -332,7 +355,7 @@ export interface TableResult<T = any> {
332
355
  * Throws error if table is not initialized.
333
356
  */
334
357
  getRow: (
335
- filters: Record<string, string | RegExp | number>,
358
+ filters: Record<string, FilterValue>,
336
359
  options?: { exact?: boolean }
337
360
  ) => SmartRow;
338
361
 
@@ -354,7 +377,7 @@ export interface TableResult<T = any> {
354
377
  * @param options - Search options including exact match and max pages
355
378
  */
356
379
  findRow: (
357
- filters: Record<string, string | RegExp | number>,
380
+ filters: Record<string, FilterValue>,
358
381
  options?: { exact?: boolean, maxPages?: number }
359
382
  ) => Promise<SmartRow>;
360
383
 
@@ -362,12 +385,12 @@ export interface TableResult<T = any> {
362
385
  * ASYNC: Searches for all matching rows across pages using pagination.
363
386
  * Auto-initializes the table if not already initialized.
364
387
  * @param filters - The filter criteria to match
365
- * @param options - Search options including exact match, max pages, and asJSON
388
+ * @param options - Search options including exact match and max pages
366
389
  */
367
- findRows: <R extends { asJSON?: boolean }>(
368
- filters: Record<string, string | RegExp | number>,
369
- options?: { exact?: boolean, maxPages?: number } & R
370
- ) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRowArray>;
390
+ findRows: (
391
+ filters: Record<string, FilterValue>,
392
+ options?: { exact?: boolean, maxPages?: number }
393
+ ) => Promise<SmartRowArray<T>>;
371
394
 
372
395
  /**
373
396
  * Navigates to a specific column using the configured CellNavigationStrategy.
package/dist/types.d.ts CHANGED
@@ -10,6 +10,18 @@ import type { SmartRowArray } from './utils/smartRowArray';
10
10
  * rowSelector: (root) => root.locator('[role="row"]')
11
11
  */
12
12
  export type Selector = string | ((root: Locator | Page) => Locator);
13
+ /**
14
+ * Value used to filter rows.
15
+ * - string/number/RegExp: filter by text content of the cell.
16
+ * - function: filter by custom locator logic within the cell.
17
+ * @example
18
+ * // Text filter
19
+ * { Name: 'John' }
20
+ *
21
+ * // Custom locator filter (e.g. checkbox is checked)
22
+ * { Status: (cell) => cell.locator('input:checked') }
23
+ */
24
+ export type FilterValue = string | RegExp | number | ((cell: Locator) => Locator);
13
25
  /**
14
26
  * Function to get a cell locator given row, column info.
15
27
  * Replaces the old cellResolver.
@@ -143,9 +155,9 @@ export type DebugConfig = {
143
155
  */
144
156
  logLevel?: 'verbose' | 'info' | 'error' | 'none';
145
157
  };
146
- export interface TableContext {
158
+ export interface TableContext<T = any> {
147
159
  root: Locator;
148
- config: FinalTableConfig;
160
+ config: FinalTableConfig<T>;
149
161
  page: Page;
150
162
  resolve: (selector: Selector, parent: Locator | Page) => Locator;
151
163
  }
@@ -186,7 +198,7 @@ export interface FilterStrategy {
186
198
  rows: Locator;
187
199
  filter: {
188
200
  column: string;
189
- value: string | RegExp | number;
201
+ value: FilterValue;
190
202
  };
191
203
  colIndex: number;
192
204
  tableContext: TableContext;
@@ -239,7 +251,10 @@ export interface TableStrategies {
239
251
  /**
240
252
  * Configuration options for useTable.
241
253
  */
242
- export interface TableConfig {
254
+ /**
255
+ * Configuration options for useTable.
256
+ */
257
+ export interface TableConfig<T = any> {
243
258
  /** Selector for the table headers */
244
259
  headerSelector?: string;
245
260
  /** Selector for the table rows */
@@ -263,8 +278,13 @@ export interface TableConfig {
263
278
  onReset?: (context: TableContext) => Promise<void>;
264
279
  /** All interaction strategies */
265
280
  strategies?: TableStrategies;
281
+ /**
282
+ * Custom data mappers for specific columns.
283
+ * Allows extracting complex data types (boolean, number) instead of just string.
284
+ */
285
+ dataMapper?: Partial<Record<keyof T, (cell: Locator) => Promise<T[keyof T]> | T[keyof T]>>;
266
286
  }
267
- export interface FinalTableConfig extends TableConfig {
287
+ export interface FinalTableConfigLike<T = any> extends TableConfig<T> {
268
288
  headerSelector: string;
269
289
  rowSelector: string;
270
290
  cellSelector: string;
@@ -280,6 +300,7 @@ export interface FinalTableConfig extends TableConfig {
280
300
  onReset: (context: TableContext) => Promise<void>;
281
301
  strategies: TableStrategies;
282
302
  }
303
+ export type FinalTableConfig<T = any> = FinalTableConfigLike<T>;
283
304
  export interface FillOptions {
284
305
  /**
285
306
  * Custom input mappers for specific columns.
@@ -322,7 +343,7 @@ export interface TableResult<T = any> {
322
343
  * Finds a row by filters on the current page only. Returns immediately (sync).
323
344
  * Throws error if table is not initialized.
324
345
  */
325
- getRow: (filters: Record<string, string | RegExp | number>, options?: {
346
+ getRow: (filters: Record<string, FilterValue>, options?: {
326
347
  exact?: boolean;
327
348
  }) => SmartRow;
328
349
  /**
@@ -340,7 +361,7 @@ export interface TableResult<T = any> {
340
361
  * @param filters - The filter criteria to match
341
362
  * @param options - Search options including exact match and max pages
342
363
  */
343
- findRow: (filters: Record<string, string | RegExp | number>, options?: {
364
+ findRow: (filters: Record<string, FilterValue>, options?: {
344
365
  exact?: boolean;
345
366
  maxPages?: number;
346
367
  }) => Promise<SmartRow>;
@@ -348,14 +369,12 @@ export interface TableResult<T = any> {
348
369
  * ASYNC: Searches for all matching rows across pages using pagination.
349
370
  * Auto-initializes the table if not already initialized.
350
371
  * @param filters - The filter criteria to match
351
- * @param options - Search options including exact match, max pages, and asJSON
372
+ * @param options - Search options including exact match and max pages
352
373
  */
353
- findRows: <R extends {
354
- asJSON?: boolean;
355
- }>(filters: Record<string, string | RegExp | number>, options?: {
374
+ findRows: (filters: Record<string, FilterValue>, options?: {
356
375
  exact?: boolean;
357
376
  maxPages?: number;
358
- } & R) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRowArray>;
377
+ }) => Promise<SmartRowArray<T>>;
359
378
  /**
360
379
  * Navigates to a specific column using the configured CellNavigationStrategy.
361
380
  */
@@ -8,7 +8,7 @@ import { Strategies } from './strategies';
8
8
  /**
9
9
  * Main hook to interact with a table.
10
10
  */
11
- export declare const useTable: <T = any>(rootLocator: Locator, configOptions?: TableConfig) => TableResult<T>;
11
+ export declare const useTable: <T = any>(rootLocator: Locator, configOptions?: TableConfig<T>) => TableResult<T>;
12
12
  export declare const PaginationStrategies: {
13
13
  clickNext: (nextButtonSelector: Selector, options?: {
14
14
  stabilization?: import("./strategies/stabilization").StabilizationStrategy;
package/dist/useTable.js CHANGED
@@ -225,10 +225,12 @@ const useTable = (rootLocator, configOptions = {}) => {
225
225
  return _makeSmart(rowLocator, map, index);
226
226
  },
227
227
  findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
228
+ // @ts-ignore
228
229
  return rowFinder.findRow(filters, options);
229
230
  }),
230
231
  getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
231
232
  console.warn('DEPRECATED: table.getRows() is deprecated and will be removed in a future version. Use table.findRows() instead.');
233
+ // @ts-ignore
232
234
  return rowFinder.findRows((options === null || options === void 0 ? void 0 : options.filter) || {}, Object.assign(Object.assign({}, options), { maxPages: 1 }));
233
235
  }),
234
236
  findRows: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",