@rickcedwhat/playwright-smart-table 5.1.0 → 5.2.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
@@ -117,7 +117,7 @@ await expect(row).toBeVisible();
117
117
  ```
118
118
  <!-- /embed: advanced-debug -->
119
119
 
120
- This will log header mappings, row scans, and pagination triggers to help troubleshoot issues.
120
+ This will log header mappings, row scans, and pagination triggers to the console, and slow down operations to help you see what's happening.
121
121
 
122
122
  ### Resetting Table State
123
123
 
@@ -922,8 +922,8 @@ export interface TableConfig {
922
922
  headerTransformer?: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;
923
923
  /** Automatically scroll to table on init */
924
924
  autoScroll?: boolean;
925
- /** Enable debug logs */
926
- debug?: boolean;
925
+ /** Debug options for development and troubleshooting */
926
+ debug?: DebugConfig;
927
927
  /** Reset hook */
928
928
  onReset?: (context: TableContext) => Promise<void>;
929
929
  /** All interaction strategies */
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FilterEngine = void 0;
4
+ const stringUtils_1 = require("./utils/stringUtils");
4
5
  class FilterEngine {
5
6
  constructor(config, resolve) {
6
7
  this.config = config;
@@ -17,7 +18,7 @@ class FilterEngine {
17
18
  const colIndex = map.get(colName);
18
19
  // TODO: Use ColumnStrategy for better resolution error handling
19
20
  if (colIndex === undefined) {
20
- throw new Error(`Filter Error: Column "${colName}" not found.`);
21
+ throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
21
22
  }
22
23
  const filterVal = typeof value === 'number' ? String(value) : value;
23
24
  // Use strategy if provided (For future: configured filter strategies)
package/dist/smartRow.js CHANGED
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.createSmartRow = void 0;
13
13
  const fill_1 = require("./strategies/fill");
14
14
  const stringUtils_1 = require("./utils/stringUtils");
15
- const traceUtils_1 = require("./utils/traceUtils");
15
+ const debugUtils_1 = require("./utils/debugUtils");
16
16
  /**
17
17
  * Factory to create a SmartRow by extending a Playwright Locator.
18
18
  * We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
@@ -36,8 +36,6 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
36
36
  page: rootLocator.page()
37
37
  });
38
38
  }
39
- // Add trace event
40
- (0, traceUtils_1.addTraceEvent)(rootLocator.page(), 'getCell', { column: colName, columnIndex: idx, rowIndex }).catch(() => { });
41
39
  return resolve(config.cellSelector, rowLocator).nth(idx);
42
40
  };
43
41
  smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
@@ -115,10 +113,13 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
115
113
  return result;
116
114
  });
117
115
  smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
116
+ (0, debugUtils_1.logDebug)(config, 'info', 'Filling row', data);
118
117
  for (const [colName, value] of Object.entries(data)) {
118
+ if (value === undefined)
119
+ continue;
119
120
  const colIdx = map.get(colName);
120
121
  if (colIdx === undefined) {
121
- throw new Error(`Column "${colName}" not found in fill data.`);
122
+ throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
122
123
  }
123
124
  yield config.strategies.cellNavigation({
124
125
  config: config,
@@ -131,6 +132,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
131
132
  rowIndex: rowIndex
132
133
  });
133
134
  const strategy = config.strategies.fill || fill_1.FillStrategies.default;
135
+ (0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
134
136
  yield strategy({
135
137
  row: smart,
136
138
  columnName: colName,
@@ -141,7 +143,10 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
141
143
  table: table,
142
144
  fillOptions
143
145
  });
146
+ // Delay after filling
147
+ yield (0, debugUtils_1.debugDelay)(config, 'getCell');
144
148
  }
149
+ (0, debugUtils_1.logDebug)(config, 'info', 'Row fill complete');
145
150
  });
146
151
  smart.bringIntoView = () => __awaiter(void 0, void 0, void 0, function* () {
147
152
  if (rowIndex === undefined) {
@@ -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\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 * 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 /** Function to get a cell locator */\n getCellLocator?: GetCellLocatorFn;\n /** Function to get the currently active/focused cell */\n getActiveCell?: GetActiveCellFn;\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 }) => string | Promise<string>;\n /** Automatically scroll to table on init */\n autoScroll?: boolean;\n /** Enable debug logs */\n debug?: boolean;\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: boolean;\n headerTransformer: (args: { text: string, index: number, locator: Locator }) => 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\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>[] : SmartRow[]>;\n\n /**\n * Navigates to a specific column using the configured CellNavigationStrategy.\n */\n scrollToColumn: (columnName: string) => Promise<void>;\n\n /**\n * ASYNC: Gets all rows on the current page only (does not paginate).\n * Auto-initializes the table if not already initialized.\n * @param options - Filter and formatting options\n */\n getRows: <R extends { asJSON?: boolean }>(\n options?: { filter?: Record<string, any>, exact?: boolean } & R\n ) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;\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: SmartRow[];\n allData: T[];\n table: RestrictedTableResult;\n batchInfo?: {\n startIndex: number;\n endIndex: number;\n size: number;\n };\n }) => T | Promise<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: SmartRow[], allData: any[] }) => void | Promise<void>;\n afterLast?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;\n }\n ) => Promise<T[]>;\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 * 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 * 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 /** Function to get a cell locator */\n getCellLocator?: GetCellLocatorFn;\n /** Function to get the currently active/focused cell */\n getActiveCell?: GetActiveCellFn;\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 }) => 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 }) => 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\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>[] : SmartRow[]>;\n\n /**\n * Navigates to a specific column using the configured CellNavigationStrategy.\n */\n scrollToColumn: (columnName: string) => Promise<void>;\n\n /**\n * ASYNC: Gets all rows on the current page only (does not paginate).\n * Auto-initializes the table if not already initialized.\n * @param options - Filter and formatting options\n */\n getRows: <R extends { asJSON?: boolean }>(\n options?: { filter?: Record<string, any>, exact?: boolean } & R\n ) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;\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: SmartRow[];\n allData: T[];\n table: RestrictedTableResult;\n batchInfo?: {\n startIndex: number;\n endIndex: number;\n size: number;\n };\n }) => T | Promise<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: SmartRow[], allData: any[] }) => void | Promise<void>;\n afterLast?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;\n }\n ) => Promise<T[]>;\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";
@@ -133,6 +133,31 @@ export interface SortingStrategy {
133
133
  }): Promise<'asc' | 'desc' | 'none'>;
134
134
  }
135
135
 
136
+ /**
137
+ * Debug configuration for development and troubleshooting
138
+ */
139
+ export type DebugConfig = {
140
+ /**
141
+ * Slow down operations for debugging
142
+ * - number: Apply same delay to all operations (ms)
143
+ * - object: Granular delays per operation type
144
+ */
145
+ slow?: number | {
146
+ pagination?: number;
147
+ getCell?: number;
148
+ findRow?: number;
149
+ default?: number;
150
+ };
151
+ /**
152
+ * Log level for debug output
153
+ * - 'verbose': All logs (verbose, info, error)
154
+ * - 'info': Info and error logs only
155
+ * - 'error': Error logs only
156
+ * - 'none': No logs
157
+ */
158
+ logLevel?: 'verbose' | 'info' | 'error' | 'none';
159
+ };
160
+
136
161
  export interface TableContext {
137
162
  root: Locator;
138
163
  config: FinalTableConfig;
@@ -221,8 +246,8 @@ export interface TableConfig {
221
246
  headerTransformer?: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;
222
247
  /** Automatically scroll to table on init */
223
248
  autoScroll?: boolean;
224
- /** Enable debug logs */
225
- debug?: boolean;
249
+ /** Debug options for development and troubleshooting */
250
+ debug?: DebugConfig;
226
251
  /** Reset hook */
227
252
  onReset?: (context: TableContext) => Promise<void>;
228
253
  /** All interaction strategies */
@@ -235,7 +260,7 @@ export interface FinalTableConfig extends TableConfig {
235
260
  cellSelector: string;
236
261
  maxPages: number;
237
262
  autoScroll: boolean;
238
- debug: boolean;
263
+ debug?: TableConfig['debug'];
239
264
  headerTransformer: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;
240
265
  onReset: (context: TableContext) => Promise<void>;
241
266
  strategies: TableStrategies;
package/dist/types.d.ts CHANGED
@@ -118,6 +118,30 @@ export interface SortingStrategy {
118
118
  context: StrategyContext;
119
119
  }): Promise<'asc' | 'desc' | 'none'>;
120
120
  }
121
+ /**
122
+ * Debug configuration for development and troubleshooting
123
+ */
124
+ export type DebugConfig = {
125
+ /**
126
+ * Slow down operations for debugging
127
+ * - number: Apply same delay to all operations (ms)
128
+ * - object: Granular delays per operation type
129
+ */
130
+ slow?: number | {
131
+ pagination?: number;
132
+ getCell?: number;
133
+ findRow?: number;
134
+ default?: number;
135
+ };
136
+ /**
137
+ * Log level for debug output
138
+ * - 'verbose': All logs (verbose, info, error)
139
+ * - 'info': Info and error logs only
140
+ * - 'error': Error logs only
141
+ * - 'none': No logs
142
+ */
143
+ logLevel?: 'verbose' | 'info' | 'error' | 'none';
144
+ };
121
145
  export interface TableContext {
122
146
  root: Locator;
123
147
  config: FinalTableConfig;
@@ -206,8 +230,8 @@ export interface TableConfig {
206
230
  }) => string | Promise<string>;
207
231
  /** Automatically scroll to table on init */
208
232
  autoScroll?: boolean;
209
- /** Enable debug logs */
210
- debug?: boolean;
233
+ /** Debug options for development and troubleshooting */
234
+ debug?: DebugConfig;
211
235
  /** Reset hook */
212
236
  onReset?: (context: TableContext) => Promise<void>;
213
237
  /** All interaction strategies */
@@ -219,7 +243,7 @@ export interface FinalTableConfig extends TableConfig {
219
243
  cellSelector: string;
220
244
  maxPages: number;
221
245
  autoScroll: boolean;
222
- debug: boolean;
246
+ debug?: TableConfig['debug'];
223
247
  headerTransformer: (args: {
224
248
  text: string;
225
249
  index: number;
package/dist/useTable.js CHANGED
@@ -26,7 +26,7 @@ Object.defineProperty(exports, "ResolutionStrategies", { enumerable: true, get:
26
26
  const strategies_1 = require("./strategies");
27
27
  Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
28
28
  const validation_1 = require("./strategies/validation");
29
- const traceUtils_1 = require("./utils/traceUtils");
29
+ const debugUtils_1 = require("./utils/debugUtils");
30
30
  /**
31
31
  * Main hook to interact with a table.
32
32
  */
@@ -41,7 +41,7 @@ const useTable = (rootLocator, configOptions = {}) => {
41
41
  cellNavigation: columns_1.CellNavigationStrategies.default,
42
42
  pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
43
43
  };
44
- const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
44
+ const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
45
45
  const resolve = (item, parent) => {
46
46
  if (typeof item === 'string')
47
47
  return parent.locator(item);
@@ -54,9 +54,8 @@ const useTable = (rootLocator, configOptions = {}) => {
54
54
  let _hasPaginated = false;
55
55
  let _isInitialized = false;
56
56
  // Helpers
57
- const logDebug = (msg) => {
58
- if (config.debug)
59
- console.log(`🔎 [SmartTable Debug] ${msg}`);
57
+ const log = (msg) => {
58
+ (0, debugUtils_1.logDebug)(config, 'verbose', msg); // Legacy(`🔎 [SmartTable Debug] ${msg}`);
60
59
  };
61
60
  const _createColumnError = (colName, map, context) => {
62
61
  const availableColumns = Array.from(map.keys());
@@ -81,7 +80,7 @@ const useTable = (rootLocator, configOptions = {}) => {
81
80
  const _getMap = (timeout) => __awaiter(void 0, void 0, void 0, function* () {
82
81
  if (_headerMap)
83
82
  return _headerMap;
84
- logDebug('Mapping headers...');
83
+ log('Mapping headers...');
85
84
  const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
86
85
  if (config.autoScroll) {
87
86
  try {
@@ -113,8 +112,25 @@ const useTable = (rootLocator, configOptions = {}) => {
113
112
  }
114
113
  return [text, i];
115
114
  })));
115
+ // Validation: Check for empty table
116
+ if (entries.length === 0) {
117
+ throw new Error(`Initialization Error: No columns found using selector "${config.headerSelector}". Check your selector or ensure the table is visible.`);
118
+ }
119
+ // Validation: Check for duplicates
120
+ const seen = new Set();
121
+ const duplicates = new Set();
122
+ for (const [name] of entries) {
123
+ if (seen.has(name)) {
124
+ duplicates.add(name);
125
+ }
126
+ seen.add(name);
127
+ }
128
+ if (duplicates.size > 0) {
129
+ const dupList = Array.from(duplicates).map(d => `"${d}"`).join(', ');
130
+ throw new Error(`Initialization Error: Duplicate column names found: ${dupList}. Use 'headerTransformer' to rename duplicate columns.`);
131
+ }
116
132
  _headerMap = new Map(entries);
117
- logDebug(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
133
+ log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
118
134
  return _headerMap;
119
135
  });
120
136
  // Placeholder for the final table object
@@ -130,13 +146,13 @@ const useTable = (rootLocator, configOptions = {}) => {
130
146
  const map = yield _getMap();
131
147
  const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
132
148
  let currentPage = 1;
133
- logDebug(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
149
+ log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
134
150
  while (true) {
135
151
  const allRows = resolve(config.rowSelector, rootLocator);
136
152
  // Use FilterEngine
137
153
  const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
138
154
  const count = yield matchedRows.count();
139
- logDebug(`Page ${currentPage}: Found ${count} matches.`);
155
+ log(`Page ${currentPage}: Found ${count} matches.`);
140
156
  if (count > 1) {
141
157
  // Sample data logic (simplified for refactor, kept inline or moved to util if needed)
142
158
  const sampleData = [];
@@ -156,7 +172,7 @@ const useTable = (rootLocator, configOptions = {}) => {
156
172
  if (count === 1)
157
173
  return matchedRows.first();
158
174
  if (currentPage < effectiveMaxPages) {
159
- logDebug(`Page ${currentPage}: Not found. Attempting pagination...`);
175
+ log(`Page ${currentPage}: Not found. Attempting pagination...`);
160
176
  const context = {
161
177
  root: rootLocator,
162
178
  config: config,
@@ -171,7 +187,7 @@ const useTable = (rootLocator, configOptions = {}) => {
171
187
  continue;
172
188
  }
173
189
  else {
174
- logDebug(`Page ${currentPage}: Pagination failed (end of data).`);
190
+ log(`Page ${currentPage}: Pagination failed (end of data).`);
175
191
  }
176
192
  }
177
193
  if (_hasPaginated) {
@@ -224,11 +240,15 @@ const useTable = (rootLocator, configOptions = {}) => {
224
240
  init: (options) => __awaiter(void 0, void 0, void 0, function* () {
225
241
  if (_isInitialized && _headerMap)
226
242
  return result;
243
+ (0, debugUtils_1.warnIfDebugInCI)(config);
244
+ (0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
227
245
  yield _getMap(options === null || options === void 0 ? void 0 : options.timeout);
228
246
  _isInitialized = true;
229
247
  if (_headerMap) {
230
- yield (0, traceUtils_1.addTraceEvent)(rootLocator.page(), 'init', { headers: Array.from(_headerMap.keys()), columnCount: _headerMap.size });
248
+ (0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${_headerMap.size} columns`, Array.from(_headerMap.keys()));
249
+ // Trace event removed - redundant with debug logging
231
250
  }
251
+ yield (0, debugUtils_1.debugDelay)(config, 'default');
232
252
  return result;
233
253
  }),
234
254
  scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
@@ -259,19 +279,19 @@ const useTable = (rootLocator, configOptions = {}) => {
259
279
  return resolve(config.headerSelector, rootLocator).nth(idx);
260
280
  }),
261
281
  reset: () => __awaiter(void 0, void 0, void 0, function* () {
262
- logDebug("Resetting table...");
282
+ log("Resetting table...");
263
283
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
264
284
  yield config.onReset(context);
265
285
  _hasPaginated = false;
266
286
  _headerMap = null;
267
287
  _isInitialized = false;
268
- logDebug("Table reset complete.");
288
+ log("Table reset complete.");
269
289
  }),
270
290
  revalidate: () => __awaiter(void 0, void 0, void 0, function* () {
271
- logDebug("Revalidating table structure...");
291
+ log("Revalidating table structure...");
272
292
  _headerMap = null; // Clear the map to force re-scanning
273
293
  yield _getMap(); // Re-scan headers
274
- logDebug("Table revalidated.");
294
+ log("Table revalidated.");
275
295
  }),
276
296
  getColumnValues: (column, options) => __awaiter(void 0, void 0, void 0, function* () {
277
297
  var _a, _b;
@@ -283,7 +303,7 @@ const useTable = (rootLocator, configOptions = {}) => {
283
303
  const effectiveMaxPages = (_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages;
284
304
  let currentPage = 1;
285
305
  const results = [];
286
- logDebug(`Getting column values for '${column}' (Pages: ${effectiveMaxPages})`);
306
+ log(`Getting column values for '${column}' (Pages: ${effectiveMaxPages})`);
287
307
  while (true) {
288
308
  const rows = yield resolve(config.rowSelector, rootLocator).all();
289
309
  for (const row of rows) {
@@ -320,11 +340,18 @@ const useTable = (rootLocator, configOptions = {}) => {
320
340
  return _makeSmart(rowLocator, _headerMap, rowIndex);
321
341
  },
322
342
  findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
343
+ (0, debugUtils_1.logDebug)(config, 'info', 'Searching for row', filters);
323
344
  yield _ensureInitialized();
324
345
  let row = yield _findRowLocator(filters, options);
325
- if (!row) {
326
- row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
346
+ if (row) {
347
+ (0, debugUtils_1.logDebug)(config, 'info', 'Row found');
348
+ yield (0, debugUtils_1.debugDelay)(config, 'findRow');
349
+ return _makeSmart(row, _headerMap, 0);
327
350
  }
351
+ (0, debugUtils_1.logDebug)(config, 'error', 'Row not found', filters);
352
+ yield (0, debugUtils_1.debugDelay)(config, 'findRow');
353
+ // Return sentinel row
354
+ row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
328
355
  return _makeSmart(row, _headerMap, 0);
329
356
  }),
330
357
  getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
@@ -383,7 +410,7 @@ const useTable = (rootLocator, configOptions = {}) => {
383
410
  yield _ensureInitialized();
384
411
  if (!config.strategies.sorting)
385
412
  throw new Error('No sorting strategy has been configured.');
386
- logDebug(`Applying sort for column "${columnName}" (${direction})`);
413
+ log(`Applying sort for column "${columnName}" (${direction})`);
387
414
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
388
415
  yield config.strategies.sorting.doSort({ columnName, direction, context });
389
416
  }),
@@ -430,7 +457,7 @@ const useTable = (rootLocator, configOptions = {}) => {
430
457
  let seenKeys = null;
431
458
  let batchRows = [];
432
459
  let batchStartIndex = 0;
433
- logDebug(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations}, batchSize: ${batchSize !== null && batchSize !== void 0 ? batchSize : 'none'})`);
460
+ log(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations}, batchSize: ${batchSize !== null && batchSize !== void 0 ? batchSize : 'none'})`);
434
461
  while (index < effectiveMaxIterations) {
435
462
  const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
436
463
  let rows = rowLocators.map((loc, i) => _makeSmart(loc, _headerMap, i));
@@ -446,7 +473,7 @@ const useTable = (rootLocator, configOptions = {}) => {
446
473
  }
447
474
  }
448
475
  rows = deduplicated;
449
- logDebug(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
476
+ log(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
450
477
  }
451
478
  // Add rows to batch if batching is enabled
452
479
  if (isBatching) {
@@ -485,13 +512,15 @@ const useTable = (rootLocator, configOptions = {}) => {
485
512
  if (!isLastIteration) {
486
513
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
487
514
  paginationResult = yield paginationStrategy(context);
515
+ (0, debugUtils_1.logDebug)(config, 'info', `Pagination ${paginationResult ? 'succeeded' : 'failed'}`);
516
+ yield (0, debugUtils_1.debugDelay)(config, 'pagination');
488
517
  finalIsLast = getIsLast({ index: callbackIndex, paginationResult }) || !paginationResult;
489
518
  }
490
519
  if (finalIsLast && (options === null || options === void 0 ? void 0 : options.afterLast)) {
491
520
  yield options.afterLast({ index: callbackIndex, rows: callbackRows, allData });
492
521
  }
493
522
  if (finalIsLast || !paginationResult) {
494
- logDebug(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
523
+ log(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
495
524
  break;
496
525
  }
497
526
  // Reset batch
@@ -504,6 +533,8 @@ const useTable = (rootLocator, configOptions = {}) => {
504
533
  // Continue paginating even when batching
505
534
  const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
506
535
  paginationResult = yield paginationStrategy(context);
536
+ (0, debugUtils_1.logDebug)(config, 'info', `Pagination ${paginationResult ? 'succeeded' : 'failed'} (batching mode)`);
537
+ yield (0, debugUtils_1.debugDelay)(config, 'pagination');
507
538
  if (!paginationResult) {
508
539
  // Pagination failed, invoke callback with current batch
509
540
  const callbackIndex = batchStartIndex;
@@ -530,14 +561,14 @@ const useTable = (rootLocator, configOptions = {}) => {
530
561
  if (options === null || options === void 0 ? void 0 : options.afterLast) {
531
562
  yield options.afterLast({ index: callbackIndex, rows: batchRows, allData });
532
563
  }
533
- logDebug(`Pagination failed mid-batch (index: ${index})`);
564
+ log(`Pagination failed mid-batch (index: ${index})`);
534
565
  break;
535
566
  }
536
567
  }
537
568
  index++;
538
- logDebug(`Iteration ${index} completed, continuing...`);
569
+ log(`Iteration ${index} completed, continuing...`);
539
570
  }
540
- logDebug(`iterateThroughTable completed after ${index + 1} iterations, collected ${allData.length} items`);
571
+ log(`iterateThroughTable completed after ${index + 1} iterations, collected ${allData.length} items`);
541
572
  return allData;
542
573
  }),
543
574
  };
@@ -0,0 +1,17 @@
1
+ import type { FinalTableConfig } from '../types';
2
+ /**
3
+ * Get delay for specific action type
4
+ */
5
+ export declare function getDebugDelay(config: FinalTableConfig, actionType: 'pagination' | 'getCell' | 'findRow' | 'default'): number;
6
+ /**
7
+ * Add debug delay for specific action type
8
+ */
9
+ export declare function debugDelay(config: FinalTableConfig, actionType: 'pagination' | 'getCell' | 'findRow' | 'default'): Promise<void>;
10
+ /**
11
+ * Log debug message based on log level
12
+ */
13
+ export declare function logDebug(config: FinalTableConfig, level: 'error' | 'info' | 'verbose', message: string, data?: any): void;
14
+ /**
15
+ * Warn if debug.slow is enabled in CI environment
16
+ */
17
+ export declare function warnIfDebugInCI(config: FinalTableConfig): void;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getDebugDelay = getDebugDelay;
13
+ exports.debugDelay = debugDelay;
14
+ exports.logDebug = logDebug;
15
+ exports.warnIfDebugInCI = warnIfDebugInCI;
16
+ /**
17
+ * Get delay for specific action type
18
+ */
19
+ function getDebugDelay(config, actionType) {
20
+ var _a, _b, _c;
21
+ if (!((_a = config.debug) === null || _a === void 0 ? void 0 : _a.slow))
22
+ return 0;
23
+ if (typeof config.debug.slow === 'number') {
24
+ return config.debug.slow;
25
+ }
26
+ return (_c = (_b = config.debug.slow[actionType]) !== null && _b !== void 0 ? _b : config.debug.slow.default) !== null && _c !== void 0 ? _c : 0;
27
+ }
28
+ /**
29
+ * Add debug delay for specific action type
30
+ */
31
+ function debugDelay(config, actionType) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const delay = getDebugDelay(config, actionType);
34
+ if (delay > 0) {
35
+ yield new Promise(resolve => setTimeout(resolve, delay));
36
+ }
37
+ });
38
+ }
39
+ /**
40
+ * Log debug message based on log level
41
+ */
42
+ function logDebug(config, level, message, data) {
43
+ var _a, _b;
44
+ const logLevel = (_b = (_a = config.debug) === null || _a === void 0 ? void 0 : _a.logLevel) !== null && _b !== void 0 ? _b : 'none';
45
+ const levels = { none: 0, error: 1, info: 2, verbose: 3 };
46
+ if (levels[logLevel] >= levels[level]) {
47
+ const prefix = level === 'error' ? '❌' : level === 'info' ? 'ℹ️' : '🔍';
48
+ const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
49
+ console.log(`${prefix} [${timestamp}] [SmartTable] ${message}`, data !== null && data !== void 0 ? data : '');
50
+ }
51
+ }
52
+ /**
53
+ * Warn if debug.slow is enabled in CI environment
54
+ */
55
+ function warnIfDebugInCI(config) {
56
+ var _a;
57
+ if (process.env.CI === 'true' && ((_a = config.debug) === null || _a === void 0 ? void 0 : _a.slow)) {
58
+ console.warn('⚠️ [SmartTable] Warning: debug.slow is enabled in CI environment.\n' +
59
+ ' This will significantly slow down test execution.\n' +
60
+ ' Consider disabling debug mode in CI.');
61
+ }
62
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "Production-ready table testing for Playwright with smart column-aware locators. Core library with plugin support for custom table implementations.",
5
5
  "repository": {
6
6
  "type": "git",