@rickcedwhat/playwright-smart-table 6.7.5 → 6.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cedrick Catalan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md CHANGED
@@ -70,7 +70,7 @@ const table = await useTable(page.locator('#my-table')).init();
70
70
  const row = await table.findRow({ Name: 'John Doe' });
71
71
 
72
72
  // Access cells by column name
73
- const email = await row.getCell('Email').textContent();
73
+ const email = await row.getCell('Email').innerText();
74
74
 
75
75
  // Search across paginated tables
76
76
  const allActive = await table.findRows({ Status: 'Active' });
@@ -146,9 +146,9 @@ const table = useTable(page.locator('#table'), {
146
146
  },
147
147
  // Override how data is written to the 'Tags' column (for .smartFill())
148
148
  Tags: {
149
- write: async (cell, value) => {
149
+ write: async ({ cell, targetValue }) => {
150
150
  await cell.click();
151
- await page.keyboard.type(value);
151
+ await page.keyboard.type(targetValue);
152
152
  await page.keyboard.press('Enter');
153
153
  }
154
154
  }
@@ -219,6 +219,10 @@ for (const row of active) {
219
219
 
220
220
  Contributions are welcome! Please feel free to submit a Pull Request.
221
221
 
222
+ ## Deprecations
223
+
224
+ - `generateConfigPrompt()` — deprecated. Use `generateConfig()` instead. `generateConfigPrompt()` will be removed in v7.0.0.
225
+
222
226
  ## License
223
227
 
224
228
  MIT © Cedrick Catalan
@@ -62,7 +62,7 @@ class RowFinder {
62
62
  let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
63
63
  // Only apply filters if we have them
64
64
  if (Object.keys(filtersRecord).length > 0) {
65
- rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
65
+ rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page(), this.rootLocator);
66
66
  }
67
67
  // Get only newly seen matched rows
68
68
  const newIndices = yield tracker.getUnseenIndices(rowLocators);
@@ -133,7 +133,7 @@ class RowFinder {
133
133
  }
134
134
  }
135
135
  const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
136
- const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page());
136
+ const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
137
137
  const count = yield matchedRows.count();
138
138
  this.log(`Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
139
139
  if (count > 1) {
@@ -6,6 +6,10 @@ export declare class FilterEngine {
6
6
  constructor(config: FinalTableConfig, resolve: (selector: any, parent: Locator | Page) => Locator);
7
7
  /**
8
8
  * Applies filters to a set of rows.
9
+ *
10
+ * Note: `rootLocator` is optional for backward compatibility in call sites that already
11
+ * pass only the page. When strategies.filter is present we construct a TableContext
12
+ * using the provided `rootLocator`.
9
13
  */
10
- applyFilters(baseRows: Locator, filters: Record<string, FilterValue>, map: Map<string, number>, exact: boolean, page: Page): Locator;
14
+ applyFilters(baseRows: Locator, filters: Record<string, FilterValue>, map: Map<string, number>, exact: boolean, page: Page, rootLocator?: Locator): Locator;
11
15
  }
@@ -9,31 +9,41 @@ class FilterEngine {
9
9
  }
10
10
  /**
11
11
  * Applies filters to a set of rows.
12
+ *
13
+ * Note: `rootLocator` is optional for backward compatibility in call sites that already
14
+ * pass only the page. When strategies.filter is present we construct a TableContext
15
+ * using the provided `rootLocator`.
12
16
  */
13
- applyFilters(baseRows, filters, map, exact, page) {
17
+ applyFilters(baseRows, filters, map, exact, page, rootLocator) {
18
+ var _a;
14
19
  let filtered = baseRows;
15
20
  // Iterate through each filter criteria
16
21
  for (const [colName, value] of Object.entries(filters)) {
17
22
  // Find column index
18
23
  const colIndex = map.get(colName);
19
- // TODO: Use ColumnStrategy for better resolution error handling
20
24
  if (colIndex === undefined) {
21
25
  throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
22
26
  }
23
27
  const filterVal = value;
24
- // Use strategy if provided (For future: configured filter strategies)
25
- // But for now, we implement the default logic or use custom if we add it to config later
28
+ // If a pluggable FilterStrategy is provided, prefer it.
29
+ if (((_a = this.config.strategies) === null || _a === void 0 ? void 0 : _a.filter) && typeof this.config.strategies.filter.apply === 'function') {
30
+ const tableContext = {
31
+ root: rootLocator,
32
+ config: this.config,
33
+ page,
34
+ resolve: this.resolve
35
+ };
36
+ filtered = this.config.strategies.filter.apply({
37
+ rows: filtered,
38
+ filter: { column: colName, value: filterVal },
39
+ colIndex,
40
+ tableContext
41
+ });
42
+ continue;
43
+ }
26
44
  // Default Filter Logic
27
45
  const cellTemplate = this.resolve(this.config.cellSelector, page);
28
- // ⚠️ CRITICAL WARNING: DO NOT "FIX" OR REFACTOR THIS LOGIC. ⚠️
29
- // At first glance, `cellTemplate.nth(colIndex)` looks like a global page selector
30
- // that will return the Nth cell on the entire page, rather than the Nth cell in the row.
31
- // THIS IS INTENTIONAL AND CORRECT.
32
- // Playwright deeply understands nested locator scoping. When this global-looking locator
33
- // is passed into `filtered.filter({ has: ... })` below, Playwright magically and
34
- // automatically re-bases the `nth()` selector to be strictly relative to the ROW being evaluated.
35
- // Attempting to manually force generic relative locators here will break complex function
36
- // selectors and introduce regressions. Leave it as is.
46
+ // Playwright scoping: `cellTemplate.nth(colIndex)` will be re-based when used in filtered.filter({ has: ... })
37
47
  const targetCell = cellTemplate.nth(colIndex);
38
48
  if (typeof filterVal === 'function') {
39
49
  // Locator-based filter: (cell) => cell.locator(...)
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { useTable } from './useTable';
2
- export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, } from './types';
2
+ export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, } from './types';
3
3
  export { Strategies } from './strategies';
4
4
  export { Plugins } from './plugins';
@@ -4,4 +4,9 @@ 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<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null, tablePageIndex?: number) => SmartRowType<T>;
7
+ /**
8
+ * @internal Internal factory for creating SmartRow objects.
9
+ * Not part of the public package surface; tests and consumers should use public APIs.
10
+ */
11
+ 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, tablePageIndex?: number) => SmartRowType<T>;
12
+ export default createSmartRow;
package/dist/smartRow.js CHANGED
@@ -9,7 +9,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.createSmartRow = void 0;
13
12
  const fill_1 = require("./strategies/fill");
14
13
  const stringUtils_1 = require("./utils/stringUtils");
15
14
  const debugUtils_1 = require("./utils/debugUtils");
@@ -112,6 +111,10 @@ const _navigateToCell = (params) => __awaiter(void 0, void 0, void 0, function*
112
111
  * Factory to create a SmartRow by extending a Playwright Locator.
113
112
  * We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
114
113
  */
114
+ /**
115
+ * @internal Internal factory for creating SmartRow objects.
116
+ * Not part of the public package surface; tests and consumers should use public APIs.
117
+ */
115
118
  const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table, tablePageIndex) => {
116
119
  const smart = rowLocator;
117
120
  // Attach State
@@ -312,4 +315,4 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
312
315
  });
313
316
  return smart;
314
317
  };
315
- exports.createSmartRow = createSmartRow;
318
+ exports.default = createSmartRow;
@@ -0,0 +1,13 @@
1
+ import type { FilterStrategy } from '../types';
2
+ /**
3
+ * Example filter strategies.
4
+ * - default: small convenience wrapper that mirrors the engine's default behavior.
5
+ * - spy(factory): returns a strategy that marks `calledRef.called = true` when invoked.
6
+ */
7
+ export declare const FilterStrategies: {
8
+ default: FilterStrategy;
9
+ spy: (calledRef?: {
10
+ called?: boolean;
11
+ }) => FilterStrategy;
12
+ };
13
+ export type { FilterStrategy } from '../types';
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterStrategies = void 0;
4
+ /**
5
+ * Example filter strategies.
6
+ * - default: small convenience wrapper that mirrors the engine's default behavior.
7
+ * - spy(factory): returns a strategy that marks `calledRef.called = true` when invoked.
8
+ */
9
+ exports.FilterStrategies = {
10
+ default: {
11
+ apply({ rows, filter, colIndex, tableContext }) {
12
+ const page = tableContext.page;
13
+ const resolve = tableContext.resolve;
14
+ const cellTemplate = resolve(tableContext.config.cellSelector, page);
15
+ const targetCell = cellTemplate.nth(colIndex);
16
+ if (typeof filter.value === 'function') {
17
+ return rows.filter({ has: filter.value(targetCell) });
18
+ }
19
+ const textVal = typeof filter.value === 'number' ? String(filter.value) : filter.value;
20
+ return rows.filter({ has: targetCell.getByText(textVal, { exact: true }) });
21
+ }
22
+ },
23
+ spy: (calledRef = {}) => ({
24
+ apply({ rows, filter, colIndex, tableContext }) {
25
+ calledRef.called = true;
26
+ // Delegate to default behaviour for actual filtering
27
+ const page = tableContext.page;
28
+ const resolve = tableContext.resolve;
29
+ const cellTemplate = resolve(tableContext.config.cellSelector, page);
30
+ const targetCell = cellTemplate.nth(colIndex);
31
+ if (typeof filter.value === 'function') {
32
+ return rows.filter({ has: filter.value(targetCell) });
33
+ }
34
+ const textVal = typeof filter.value === 'number' ? String(filter.value) : filter.value;
35
+ return rows.filter({ has: targetCell.getByText(textVal, { exact: true }) });
36
+ }
37
+ }),
38
+ };
@@ -6,6 +6,8 @@ export * from './fill';
6
6
  export * from './resolution';
7
7
  export * from './dedupe';
8
8
  export * from './loading';
9
+ export * from './stabilization';
10
+ export * from './filter';
9
11
  export declare const Strategies: {
10
12
  Pagination: {
11
13
  click: (selectors: {
@@ -63,4 +65,23 @@ export declare const Strategies: {
63
65
  never: () => Promise<boolean>;
64
66
  };
65
67
  };
68
+ Stabilization: {
69
+ contentChanged: (options?: {
70
+ scope?: "all" | "first";
71
+ timeout?: number;
72
+ }) => import("./stabilization").StabilizationStrategy;
73
+ rowCountIncreased: (options?: {
74
+ timeout?: number;
75
+ }) => import("./stabilization").StabilizationStrategy;
76
+ networkIdle: (options?: {
77
+ spinnerSelector?: string;
78
+ timeout?: number;
79
+ }) => import("./stabilization").StabilizationStrategy;
80
+ };
81
+ Filter: {
82
+ default: import("./filter").FilterStrategy;
83
+ spy: (calledRef?: {
84
+ called?: boolean;
85
+ }) => import("./filter").FilterStrategy;
86
+ };
66
87
  };
@@ -23,6 +23,8 @@ const fill_1 = require("./fill");
23
23
  const resolution_1 = require("./resolution");
24
24
  const dedupe_1 = require("./dedupe");
25
25
  const loading_1 = require("./loading");
26
+ const stabilization_1 = require("./stabilization");
27
+ const filter_1 = require("./filter");
26
28
  __exportStar(require("./pagination"), exports);
27
29
  __exportStar(require("./sorting"), exports);
28
30
  __exportStar(require("./columns"), exports);
@@ -31,6 +33,8 @@ __exportStar(require("./fill"), exports);
31
33
  __exportStar(require("./resolution"), exports);
32
34
  __exportStar(require("./dedupe"), exports);
33
35
  __exportStar(require("./loading"), exports);
36
+ __exportStar(require("./stabilization"), exports);
37
+ __exportStar(require("./filter"), exports);
34
38
  exports.Strategies = {
35
39
  Pagination: pagination_1.PaginationStrategies,
36
40
  Sorting: sorting_1.SortingStrategies,
@@ -40,4 +44,6 @@ exports.Strategies = {
40
44
  Resolution: resolution_1.ResolutionStrategies,
41
45
  Dedupe: dedupe_1.DedupeStrategies,
42
46
  Loading: loading_1.LoadingStrategies,
47
+ Stabilization: stabilization_1.StabilizationStrategies,
48
+ Filter: filter_1.FilterStrategies,
43
49
  };
@@ -3,4 +3,4 @@
3
3
  * This file is generated by scripts/embed-types.mjs
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) | ((root: Locator) => 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 * Hook called before each cell value is read in toJSON (and columnOverrides.read).\n * Use this to scroll off-screen columns into view in horizontally virtualized tables,\n * wait for lazy-rendered content, or perform any pre-read setup.\n *\n * @example\n * // Scroll the column header into view to trigger horizontal virtualization render\n * strategies: {\n * beforeCellRead: async ({ columnName, getHeaderCell }) => {\n * const header = await getHeaderCell(columnName);\n * await header.scrollIntoViewIfNeeded();\n * }\n * }\n */\nexport type BeforeCellReadFn = (args: {\n /** The resolved cell locator */\n cell: Locator;\n columnName: string;\n columnIndex: number;\n row: Locator;\n page: Page;\n root: Locator;\n /** Resolves a column name to its header cell locator */\n getHeaderCell: (columnName: string) => Promise<Locator>;\n}) => Promise<void>;\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 /** Optional page index this row was found on (0-based) */\n tablePageIndex?: number;\n\n /** Reference to the parent TableResult */\n table: TableResult<T>;\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 /**\n * Returns whether the row exists in the DOM (i.e. is not a sentinel row).\n */\n wasFound(): boolean;\n};\n\nexport type StrategyContext = TableContext & {\n rowLocator?: Locator;\n rowIndex?: number;\n};\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 /** Resolves a column name to its header cell locator. Available after table is initialized. */\n getHeaderCell?: (columnName: string) => Promise<Locator>;\n /** Returns all column names in order. Available after table is initialized. */\n getHeaders?: () => Promise<string[]>;\n /** Scrolls the table horizontally to bring the given column's header into view. */\n scrollToColumn?: (columnName: string) => Promise<void>;\n}\n\nexport interface PaginationPrimitives {\n /** Classic \"Next Page\" or \"Scroll Down\" */\n goNext?: (context: TableContext) => Promise<boolean>;\n\n /** Classic \"Previous Page\" or \"Scroll Up\" */\n goPrevious?: (context: TableContext) => Promise<boolean>;\n\n /** Bulk skip forward multiple pages at once. Returns number of pages skipped. */\n goNextBulk?: (context: TableContext) => Promise<boolean | number>;\n\n /** Bulk skip backward multiple pages at once. Returns number of pages skipped. */\n goPreviousBulk?: (context: TableContext) => Promise<boolean | number>;\n\n /** Jump to first page / scroll to top */\n goToFirst?: (context: TableContext) => Promise<boolean>;\n\n /**\n * Jump to specific page index (0-indexed).\n * Can be full-range (e.g. page number input: any page works) or windowed (e.g. only visible links 6\u201314).\n * Return false when the page is not reachable in the current UI; the library will step toward the target (goNextBulk/goNext or goPreviousBulk/goPrevious) and retry goToPage until it succeeds.\n */\n goToPage?: (pageIndex: number, context: TableContext) => Promise<boolean>;\n\n /** How many pages one goNextBulk() advances. Used by navigation path planner for optimal bringIntoView. */\n nextBulkPages?: number;\n\n /** How many pages one goPreviousBulk() goes back. Used by navigation path planner for optimal bringIntoView. */\n previousBulkPages?: number;\n}\n\nexport type PaginationStrategy = PaginationPrimitives;\n\nexport type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;\n\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 config: FinalTableConfig<any>;\n table: TableResult; // The parent table instance\n fillOptions?: FillOptions;\n}) => Promise<void>;\n\nexport interface ColumnOverride<TValue = any> {\n /** \n * How to extract the value from the cell.\n */\n read?: (cell: Locator) => Promise<TValue> | TValue;\n\n /** \n * How to fill the cell with a new value. (Replaces smartFill default logic)\n * Provides the current value (via `read`) if a `write` wants to check state first.\n */\n write?: (params: {\n cell: Locator;\n targetValue: TValue;\n currentValue?: TValue;\n row: SmartRow<any>;\n }) => Promise<void>;\n}\n\nexport type { HeaderStrategy } from './strategies/headers';\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. Applied when using getRow/findRow/findRows with filters.\n * The default engine handles string, RegExp, number, and function (cell) => Locator filters.\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. Used after pagination/sort to wait for content.\n * E.g. isHeaderLoading for init stability; isTableLoading after sort/pagination.\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 /** Primitive navigation functions (goUp, goDown, goLeft, goRight, goHome) */\n navigation?: NavigationPrimitives;\n\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 /**\n * Hook called before each cell value is read in toJSON and columnOverrides.read.\n * Fires for both the default innerText extraction and custom read mappers.\n * Useful for scrolling off-screen columns into view in horizontally virtualized tables.\n */\n beforeCellRead?: BeforeCellReadFn;\n /**\n * Strategy for detecting loading states. Use this for table-, row-, and header-level readiness.\n * E.g. after sort/pagination, the engine uses loading.isTableLoading when present.\n */\n loading?: LoadingStrategy;\n}\n\n\nexport interface TableConfig<T = any> {\n /** Selector for the table headers */\n headerSelector?: string | ((root: Locator) => Locator);\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 /**\n * Unified interface for reading and writing data to specific columns.\n * Overrides both default extraction (toJSON) and filling (smartFill) logic.\n */\n columnOverrides?: Partial<Record<keyof T, ColumnOverride<T[keyof T]>>>;\n}\n\nexport interface FinalTableConfig<T = any> extends TableConfig<T> {\n headerSelector: string | ((root: Locator) => Locator);\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\n/** Callback context passed to forEach, map, and filter. */\nexport type RowIterationContext<T = any> = {\n row: SmartRow<T>;\n rowIndex: number;\n stop: () => void;\n};\n\n/** Shared options for forEach, map, and filter. */\nexport type RowIterationOptions = {\n /** Maximum number of pages to iterate. Defaults to config.maxPages. */\n maxPages?: number;\n /**\n * Whether to process rows within a page concurrently.\n * @default false for forEach/filter, true for map\n */\n parallel?: boolean;\n /**\n * Deduplication strategy. Use when rows may repeat across iterations\n * (e.g. infinite scroll tables). Returns a unique key per row.\n */\n dedupe?: DedupeStrategy;\n /**\n * When true, use goNextBulk (if present) to advance pages during iteration.\n * @default false \u2014 uses goNext for one-page-at-a-time advancement\n */\n useBulkPagination?: boolean;\n};\n\nexport interface TableResult<T = any> extends AsyncIterable<{ row: SmartRow<T>; rowIndex: number }> {\n /**\n * Represents the current page index of the table's DOM.\n * Starts at 0. Automatically maintained by the library during pagination and bringIntoView.\n */\n currentPageIndex: number;\n\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 * @note The returned SmartRow may have `rowIndex` as 0 when the match is not the first row.\n * Use getRowByIndex(index) when you need a known index (e.g. for bringIntoView()).\n */\n getRow: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean }\n ) => SmartRow;\n\n /**\n * Gets a row by 0-based index on the current page.\n * Throws error if table is not initialized.\n * @param index 0-based row index\n */\n getRowByIndex: (\n index: number\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 (omit or pass {} for all rows)\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\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 * Iterates every row across all pages, calling the callback for side effects.\n * Execution is sequential by default (safe for interactions like clicking/filling).\n * Call `stop()` in the callback to end iteration early.\n *\n * @example\n * await table.forEach(async ({ row, stop }) => {\n * if (await row.getCell('Status').innerText() === 'Done') stop();\n * await row.getCell('Checkbox').click();\n * });\n */\n forEach(\n callback: (ctx: RowIterationContext<T>) => void | Promise<void>,\n options?: RowIterationOptions\n ): Promise<void>;\n\n /**\n * Transforms every row across all pages into a value. Returns a flat array.\n * Execution is parallel within each page by default (safe for reads).\n * Call `stop()` to halt after the current page finishes.\n *\n * > **\u26A0\uFE0F UI Interactions:** `map` defaults to `parallel: true`. If your callback opens popovers,\n * > fills inputs, or otherwise mutates UI state, pass `{ parallel: false }` to avoid concurrent\n * > interactions interfering with each other.\n *\n * @example\n * // Data extraction \u2014 parallel is safe\n * const emails = await table.map(({ row }) => row.getCell('Email').innerText());\n *\n * @example\n * // UI interactions \u2014 must use parallel: false\n * const assignees = await table.map(async ({ row }) => {\n * await row.getCell('Assignee').locator('button').click();\n * const name = await page.locator('.popover .name').innerText();\n * await page.keyboard.press('Escape');\n * return name;\n * }, { parallel: false });\n */\n map<R>(\n callback: (ctx: RowIterationContext<T>) => R | Promise<R>,\n options?: RowIterationOptions\n ): Promise<R[]>;\n\n /**\n * Filters rows across all pages by an async predicate. Returns a SmartRowArray.\n * Rows are returned as-is \u2014 call `bringIntoView()` on each if needed.\n * Execution is sequential by default.\n *\n * @example\n * const active = await table.filter(async ({ row }) =>\n * await row.getCell('Status').innerText() === 'Active'\n * );\n */\n filter(\n predicate: (ctx: RowIterationContext<T>) => boolean | Promise<boolean>,\n options?: RowIterationOptions\n ): Promise<SmartRowArray<T>>;\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 * Generate an AI-friendly configuration prompt for debugging.\n * Outputs table HTML and TypeScript definitions to help AI assistants generate config.\n * Automatically throws an Error containing the prompt.\n */\n generateConfig: () => Promise<void>;\n\n /**\n * @deprecated Use `generateConfig()` instead. Will be removed in v7.0.0.\n */\n generateConfigPrompt: () => Promise<void>;\n}\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) | ((root: Locator) => 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 * Hook called before each cell value is read in toJSON (and columnOverrides.read).\n * Use this to scroll off-screen columns into view in horizontally virtualized tables,\n * wait for lazy-rendered content, or perform any pre-read setup.\n *\n * @example\n * // Scroll the column header into view to trigger horizontal virtualization render\n * strategies: {\n * beforeCellRead: async ({ columnName, getHeaderCell }) => {\n * const header = await getHeaderCell(columnName);\n * await header.scrollIntoViewIfNeeded();\n * }\n * }\n */\nexport type BeforeCellReadFn = (args: {\n /** The resolved cell locator */\n cell: Locator;\n columnName: string;\n columnIndex: number;\n row: Locator;\n page: Page;\n root: Locator;\n /** Resolves a column name to its header cell locator */\n getHeaderCell: (columnName: string) => Promise<Locator>;\n}) => Promise<void>;\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 /** Optional page index this row was found on (0-based) */\n tablePageIndex?: number;\n\n /** Reference to the parent TableResult */\n table: TableResult<T>;\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 /**\n * Returns whether the row exists in the DOM (i.e. is not a sentinel row).\n */\n wasFound(): boolean;\n};\n\nexport type StrategyContext = TableContext & {\n rowLocator?: Locator;\n rowIndex?: number;\n};\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 /** Resolves a column name to its header cell locator. Available after table is initialized. */\n getHeaderCell?: (columnName: string) => Promise<Locator>;\n /** Returns all column names in order. Available after table is initialized. */\n getHeaders?: () => Promise<string[]>;\n /** Scrolls the table horizontally to bring the given column's header into view. */\n scrollToColumn?: (columnName: string) => Promise<void>;\n}\n\nexport interface PaginationPrimitives {\n /** Classic \"Next Page\" or \"Scroll Down\" */\n goNext?: (context: TableContext) => Promise<boolean>;\n\n /** Classic \"Previous Page\" or \"Scroll Up\" */\n goPrevious?: (context: TableContext) => Promise<boolean>;\n\n /** Bulk skip forward multiple pages at once. Returns number of pages skipped. */\n goNextBulk?: (context: TableContext) => Promise<boolean | number>;\n\n /** Bulk skip backward multiple pages at once. Returns number of pages skipped. */\n goPreviousBulk?: (context: TableContext) => Promise<boolean | number>;\n\n /** Jump to first page / scroll to top */\n goToFirst?: (context: TableContext) => Promise<boolean>;\n\n /**\n * Jump to specific page index (0-indexed).\n * Can be full-range (e.g. page number input: any page works) or windowed (e.g. only visible links 6\u201314).\n * Return false when the page is not reachable in the current UI; the library will step toward the target (goNextBulk/goNext or goPreviousBulk/goPrevious) and retry goToPage until it succeeds.\n */\n goToPage?: (pageIndex: number, context: TableContext) => Promise<boolean>;\n\n /** How many pages one goNextBulk() advances. Used by navigation path planner for optimal bringIntoView. */\n nextBulkPages?: number;\n\n /** How many pages one goPreviousBulk() goes back. Used by navigation path planner for optimal bringIntoView. */\n previousBulkPages?: number;\n}\n\nexport type PaginationStrategy = PaginationPrimitives;\n\nexport type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;\n\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 config: FinalTableConfig<any>;\n table: TableResult; // The parent table instance\n fillOptions?: FillOptions;\n}) => Promise<void>;\n\nexport interface ColumnOverride<TValue = any> {\n /** \n * How to extract the value from the cell.\n */\n read?: (cell: Locator) => Promise<TValue> | TValue;\n\n /** \n * How to fill the cell with a new value. (Replaces smartFill default logic)\n * Provides the current value (via `read`) if a `write` wants to check state first.\n */\n write?: (params: {\n cell: Locator;\n targetValue: TValue;\n currentValue?: TValue;\n row: SmartRow<any>;\n }) => Promise<void>;\n}\n\nexport type { HeaderStrategy } from './strategies/headers';\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. Applied when using getRow/findRow/findRows with filters.\n * The default engine handles string, RegExp, number, and function (cell) => Locator filters.\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. Used after pagination/sort to wait for content.\n * E.g. isHeaderLoading for init stability; isTableLoading after sort/pagination.\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 /** Primitive navigation functions (goUp, goDown, goLeft, goRight, goHome) */\n navigation?: NavigationPrimitives;\n\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 /**\n * Strategy for filtering rows. If present, FilterEngine will delegate filter application\n * to this pluggable strategy.\n */\n filter?: FilterStrategy;\n /**\n * Hook called before each cell value is read in toJSON and columnOverrides.read.\n * Fires for both the default innerText extraction and custom read mappers.\n * Useful for scrolling off-screen columns into view in horizontally virtualized tables.\n */\n beforeCellRead?: BeforeCellReadFn;\n /**\n * Strategy for detecting loading states. Use this for table-, row-, and header-level readiness.\n * E.g. after sort/pagination, the engine uses loading.isTableLoading when present.\n */\n loading?: LoadingStrategy;\n}\n\n\nexport interface TableConfig<T = any> {\n /** Selector for the table headers */\n headerSelector?: string | ((root: Locator) => Locator);\n /** Selector for the table rows */\n rowSelector?: string;\n /** Selector for the cells within a row */\n cellSelector?: string | ((row: Locator) => Locator);\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 /**\n * Unified interface for reading and writing data to specific columns.\n * Overrides both default extraction (toJSON) and filling (smartFill) logic.\n */\n columnOverrides?: Partial<Record<keyof T, ColumnOverride<T[keyof T]>>>;\n}\n\nexport interface FinalTableConfig<T = any> extends TableConfig<T> {\n headerSelector: string | ((root: Locator) => Locator);\n rowSelector: string;\n cellSelector: string | ((row: Locator) => Locator);\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\n/** Callback context passed to forEach, map, and filter. */\nexport type RowIterationContext<T = any> = {\n row: SmartRow<T>;\n rowIndex: number;\n stop: () => void;\n};\n\n/** Shared options for forEach, map, and filter. */\nexport type RowIterationOptions = {\n /** Maximum number of pages to iterate. Defaults to config.maxPages. */\n maxPages?: number;\n /**\n * Whether to process rows within a page concurrently.\n * @default false for forEach/filter, true for map\n */\n parallel?: boolean;\n /**\n * Deduplication strategy. Use when rows may repeat across iterations\n * (e.g. infinite scroll tables). Returns a unique key per row.\n */\n dedupe?: DedupeStrategy;\n /**\n * When true, use goNextBulk (if present) to advance pages during iteration.\n * @default false \u2014 uses goNext for one-page-at-a-time advancement\n */\n useBulkPagination?: boolean;\n};\n\nexport interface TableResult<T = any> extends AsyncIterable<{ row: SmartRow<T>; rowIndex: number }> {\n /**\n * Represents the current page index of the table's DOM.\n * Starts at 0. Automatically maintained by the library during pagination and bringIntoView.\n */\n currentPageIndex: number;\n\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 * @note The returned SmartRow may have `rowIndex` as 0 when the match is not the first row.\n * Use getRowByIndex(index) when you need a known index (e.g. for bringIntoView()).\n */\n getRow: (\n filters: Record<string, FilterValue>,\n options?: { exact?: boolean }\n ) => SmartRow;\n\n /**\n * Gets a row by 0-based index on the current page.\n * Throws error if table is not initialized.\n * @param index 0-based row index\n */\n getRowByIndex: (\n index: number\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 (omit or pass {} for all rows)\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\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 * Iterates every row across all pages, calling the callback for side effects.\n * Execution is sequential by default (safe for interactions like clicking/filling).\n * Call `stop()` in the callback to end iteration early.\n *\n * @example\n * await table.forEach(async ({ row, stop }) => {\n * if (await row.getCell('Status').innerText() === 'Done') stop();\n * await row.getCell('Checkbox').click();\n * });\n */\n forEach(\n callback: (ctx: RowIterationContext<T>) => void | Promise<void>,\n options?: RowIterationOptions\n ): Promise<void>;\n\n /**\n * Transforms every row across all pages into a value. Returns a flat array.\n * Execution is parallel within each page by default (safe for reads).\n * Call `stop()` to halt after the current page finishes.\n *\n * > **\u26A0\uFE0F UI Interactions:** `map` defaults to `parallel: true`. If your callback opens popovers,\n * > fills inputs, or otherwise mutates UI state, pass `{ parallel: false }` to avoid concurrent\n * > interactions interfering with each other.\n *\n * @example\n * // Data extraction \u2014 parallel is safe\n * const emails = await table.map(({ row }) => row.getCell('Email').innerText());\n *\n * @example\n * // UI interactions \u2014 must use parallel: false\n * const assignees = await table.map(async ({ row }) => {\n * await row.getCell('Assignee').locator('button').click();\n * const name = await page.locator('.popover .name').innerText();\n * await page.keyboard.press('Escape');\n * return name;\n * }, { parallel: false });\n */\n map<R>(\n callback: (ctx: RowIterationContext<T>) => R | Promise<R>,\n options?: RowIterationOptions\n ): Promise<R[]>;\n\n /**\n * Filters rows across all pages by an async predicate. Returns a SmartRowArray.\n * Rows are returned as-is \u2014 call `bringIntoView()` on each if needed.\n * Execution is sequential by default.\n *\n * @example\n * const active = await table.filter(async ({ row }) =>\n * await row.getCell('Status').innerText() === 'Active'\n * );\n */\n filter(\n predicate: (ctx: RowIterationContext<T>) => boolean | Promise<boolean>,\n options?: RowIterationOptions\n ): Promise<SmartRowArray<T>>;\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 * Generate an AI-friendly configuration prompt for debugging.\n * Outputs table HTML and TypeScript definitions to help AI assistants generate config.\n * Automatically throws an Error containing the prompt.\n */\n generateConfig: () => Promise<void>;\n\n /**\n * @deprecated Use `generateConfig()` instead. Will be removed in v7.0.0.\n */\n generateConfigPrompt: () => Promise<void>;\n}\n";
@@ -341,6 +341,11 @@ export interface TableStrategies {
341
341
  getCellLocator?: GetCellLocatorFn;
342
342
  /** Function to get the currently active/focused cell */
343
343
  getActiveCell?: GetActiveCellFn;
344
+ /**
345
+ * Strategy for filtering rows. If present, FilterEngine will delegate filter application
346
+ * to this pluggable strategy.
347
+ */
348
+ filter?: FilterStrategy;
344
349
  /**
345
350
  * Hook called before each cell value is read in toJSON and columnOverrides.read.
346
351
  * Fires for both the default innerText extraction and custom read mappers.
@@ -361,7 +366,7 @@ export interface TableConfig<T = any> {
361
366
  /** Selector for the table rows */
362
367
  rowSelector?: string;
363
368
  /** Selector for the cells within a row */
364
- cellSelector?: string;
369
+ cellSelector?: string | ((row: Locator) => Locator);
365
370
  /** Number of pages to scan for verification */
366
371
  maxPages?: number;
367
372
  /** Hook to rename columns dynamically */
@@ -385,7 +390,7 @@ export interface TableConfig<T = any> {
385
390
  export interface FinalTableConfig<T = any> extends TableConfig<T> {
386
391
  headerSelector: string | ((root: Locator) => Locator);
387
392
  rowSelector: string;
388
- cellSelector: string;
393
+ cellSelector: string | ((row: Locator) => Locator);
389
394
  maxPages: number;
390
395
  autoScroll: boolean;
391
396
  debug?: TableConfig['debug'];
package/dist/types.d.ts CHANGED
@@ -302,6 +302,11 @@ export interface TableStrategies {
302
302
  getCellLocator?: GetCellLocatorFn;
303
303
  /** Function to get the currently active/focused cell */
304
304
  getActiveCell?: GetActiveCellFn;
305
+ /**
306
+ * Strategy for filtering rows. If present, FilterEngine will delegate filter application
307
+ * to this pluggable strategy.
308
+ */
309
+ filter?: FilterStrategy;
305
310
  /**
306
311
  * Hook called before each cell value is read in toJSON and columnOverrides.read.
307
312
  * Fires for both the default innerText extraction and custom read mappers.
@@ -320,7 +325,7 @@ export interface TableConfig<T = any> {
320
325
  /** Selector for the table rows */
321
326
  rowSelector?: string;
322
327
  /** Selector for the cells within a row */
323
- cellSelector?: string;
328
+ cellSelector?: string | ((row: Locator) => Locator);
324
329
  /** Number of pages to scan for verification */
325
330
  maxPages?: number;
326
331
  /** Hook to rename columns dynamically */
@@ -347,7 +352,7 @@ export interface TableConfig<T = any> {
347
352
  export interface FinalTableConfig<T = any> extends TableConfig<T> {
348
353
  headerSelector: string | ((root: Locator) => Locator);
349
354
  rowSelector: string;
350
- cellSelector: string;
355
+ cellSelector: string | ((row: Locator) => Locator);
351
356
  maxPages: number;
352
357
  autoScroll: boolean;
353
358
  debug?: TableConfig['debug'];
package/dist/useTable.js CHANGED
@@ -21,6 +21,9 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
21
21
  function reject(value) { resume("throw", value); }
22
22
  function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
23
23
  };
24
+ var __importDefault = (this && this.__importDefault) || function (mod) {
25
+ return (mod && mod.__esModule) ? mod : { "default": mod };
26
+ };
24
27
  Object.defineProperty(exports, "__esModule", { value: true });
25
28
  exports.useTable = void 0;
26
29
  const minimalConfigContext_1 = require("./minimalConfigContext");
@@ -28,7 +31,7 @@ const validation_1 = require("./strategies/validation");
28
31
  const loading_1 = require("./strategies/loading");
29
32
  const fill_1 = require("./strategies/fill");
30
33
  const headers_1 = require("./strategies/headers");
31
- const smartRow_1 = require("./smartRow");
34
+ const smartRow_1 = __importDefault(require("./smartRow"));
32
35
  const filterEngine_1 = require("./filterEngine");
33
36
  const tableMapper_1 = require("./engine/tableMapper");
34
37
  const rowFinder_1 = require("./engine/rowFinder");
@@ -92,7 +95,7 @@ const useTable = (rootLocator, configOptions = {}) => {
92
95
  let finalTable = null;
93
96
  // Helper factory
94
97
  const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex) => {
95
- return (0, smartRow_1.createSmartRow)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex);
98
+ return (0, smartRow_1.default)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex);
96
99
  };
97
100
  const tableState = { currentPageIndex: 0 };
98
101
  const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart, tableState);
@@ -210,7 +213,7 @@ const useTable = (rootLocator, configOptions = {}) => {
210
213
  if (!map)
211
214
  throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.findRows() which auto-initialize.');
212
215
  const allRows = resolve(config.rowSelector, rootLocator);
213
- const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
216
+ const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page(), rootLocator);
214
217
  const rowLocator = matchedRows.first();
215
218
  return _makeSmart(rowLocator, map, 0); // fallback index 0
216
219
  },
@@ -142,7 +142,8 @@ function executeNavigationWithGoToPageRetry(targetPageIndex, primitives, context
142
142
  setCurrentPage(getCurrentPage() + 1);
143
143
  }
144
144
  else {
145
- throw new Error(`bringIntoView: goToPage(${targetPageIndex}) returned false and no goNext/goNextBulk to advance`);
145
+ const available = Object.keys(primitives).filter(k => primitives[k]);
146
+ throw new Error(`bringIntoView: goToPage(${targetPageIndex}) returned false and no goNext/goNextBulk to advance. Available primitives: ${available.join(', ')}`);
146
147
  }
147
148
  }
148
149
  else {
@@ -164,11 +165,13 @@ function executeNavigationWithGoToPageRetry(targetPageIndex, primitives, context
164
165
  setCurrentPage(getCurrentPage() - 1);
165
166
  }
166
167
  else {
167
- throw new Error(`bringIntoView: goToPage(${targetPageIndex}) returned false and no goPrevious/goPreviousBulk to step back`);
168
+ const available = Object.keys(primitives).filter(k => primitives[k]);
169
+ throw new Error(`bringIntoView: goToPage(${targetPageIndex}) returned false and no goPrevious/goPreviousBulk to step back. Available primitives: ${available.join(', ')}`);
168
170
  }
169
171
  }
170
172
  }
171
- throw new Error(`bringIntoView: failed to reach page ${targetPageIndex} after ${MAX_GO_TO_PAGE_RETRIES} goToPage retries`);
173
+ const available = Object.keys(primitives).filter(k => primitives[k]);
174
+ throw new Error(`bringIntoView: failed to reach page ${targetPageIndex} after ${MAX_GO_TO_PAGE_RETRIES} goToPage retries. Available primitives: ${available.join(', ')}`);
172
175
  });
173
176
  }
174
177
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.7.5",
3
+ "version": "6.7.7",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",
@@ -27,12 +27,11 @@
27
27
  "pretest": "npm run clean-port",
28
28
  "posttest": "npm run clean-port",
29
29
  "test": "npm run test:unit && npx playwright test",
30
- "test:unit": "vitest run --reporter=verbose --reporter=html",
30
+ "test:unit": "vitest run --coverage --reporter=verbose --reporter=html",
31
31
  "test:unit:ui": "vitest --ui",
32
32
  "pretest:e2e": "npm run clean-port",
33
33
  "posttest:e2e": "npm run clean-port",
34
34
  "test:e2e": "npx playwright test",
35
- "test:compatibility": "npm run clean-port && npx playwright test compatibility",
36
35
  "prepare": "husky install"
37
36
  },
38
37
  "keywords": [
@@ -47,6 +46,7 @@
47
46
  "devDependencies": {
48
47
  "@playwright/test": "^1.49.1",
49
48
  "@types/node": "^22.10.5",
49
+ "@vitest/coverage-v8": "^4.0.18",
50
50
  "@vitest/ui": "^4.0.18",
51
51
  "happy-dom": "^20.6.1",
52
52
  "husky": "^9.1.7",
@@ -54,4 +54,4 @@
54
54
  "vitepress": "^1.6.4",
55
55
  "vitest": "^4.0.18"
56
56
  }
57
- }
57
+ }
@@ -1,31 +0,0 @@
1
- import { FillStrategy, TableConfig } from '../types';
2
- /** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
3
- export declare const GlideStrategies: {
4
- fillSimple: FillStrategy;
5
- fill: FillStrategy;
6
- pagination: import("../types").PaginationPrimitives;
7
- header: (context: import("../types").StrategyContext, options?: {
8
- limit?: number;
9
- selector?: string;
10
- scrollAmount?: number;
11
- }) => Promise<string[]>;
12
- navigation: {
13
- goUp: (context: import("../types").StrategyContext) => Promise<void>;
14
- goDown: (context: import("../types").StrategyContext) => Promise<void>;
15
- goLeft: (context: import("../types").StrategyContext) => Promise<void>;
16
- goRight: (context: import("../types").StrategyContext) => Promise<void>;
17
- goHome: (context: import("../types").StrategyContext) => Promise<void>;
18
- };
19
- loading: {
20
- isHeaderLoading: () => Promise<boolean>;
21
- };
22
- getCellLocator: ({ row, columnIndex }: any) => any;
23
- getActiveCell: ({ page }: any) => Promise<{
24
- rowIndex: number;
25
- columnIndex: number;
26
- locator: any;
27
- } | null>;
28
- };
29
- export declare const Glide: Partial<TableConfig> & {
30
- Strategies: typeof GlideStrategies;
31
- };