@rickcedwhat/playwright-smart-table 5.0.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 +40 -7
- package/dist/filterEngine.js +2 -1
- package/dist/smartRow.js +11 -3
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +36 -5
- package/dist/types.d.ts +35 -5
- package/dist/useTable.js +146 -37
- package/dist/utils/debugUtils.d.ts +17 -0
- package/dist/utils/debugUtils.js +62 -0
- package/dist/utils/stringUtils.d.ts +22 -0
- package/dist/utils/stringUtils.js +73 -0
- package/dist/utils/traceUtils.d.ts +11 -0
- package/dist/utils/traceUtils.js +47 -0
- package/package.json +1 -1
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
|
|
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
|
|
|
@@ -355,11 +355,11 @@ const allData = await table.iterateThroughTable(
|
|
|
355
355
|
},
|
|
356
356
|
{
|
|
357
357
|
getIsLast: ({ paginationResult }) => !paginationResult,
|
|
358
|
-
|
|
358
|
+
beforeFirst: async ({ allData }) => {
|
|
359
359
|
console.log('Starting data collection...');
|
|
360
360
|
// Could perform setup actions
|
|
361
361
|
},
|
|
362
|
-
|
|
362
|
+
afterLast: async ({ allData }) => {
|
|
363
363
|
console.log(`Collected ${allData.length} total items`);
|
|
364
364
|
// Could perform cleanup or final actions
|
|
365
365
|
}
|
|
@@ -369,10 +369,43 @@ const allData = await table.iterateThroughTable(
|
|
|
369
369
|
<!-- /embed: iterate-through-table-hooks -->
|
|
370
370
|
|
|
371
371
|
**Hook Timing:**
|
|
372
|
-
- `
|
|
373
|
-
- `
|
|
372
|
+
- `beforeFirst`: Runs **before** your callback processes the first page
|
|
373
|
+
- `afterLast`: Runs **after** your callback processes the last page
|
|
374
374
|
- Both are optional and receive `{ index, rows, allData }`
|
|
375
375
|
|
|
376
|
+
#### Batching (v5.1+)
|
|
377
|
+
|
|
378
|
+
Process multiple pages at once for better performance:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
const results = await table.iterateThroughTable(
|
|
382
|
+
async ({ rows, batchInfo }) => {
|
|
383
|
+
// rows contains data from multiple pages
|
|
384
|
+
console.log(`Processing pages ${batchInfo.startIndex}-${batchInfo.endIndex}`);
|
|
385
|
+
console.log(`Batch has ${rows.length} total rows from ${batchInfo.size} pages`);
|
|
386
|
+
|
|
387
|
+
// Bulk process (e.g., batch database insert)
|
|
388
|
+
await bulkInsert(rows);
|
|
389
|
+
return rows.length;
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
batchSize: 3 // Process 3 pages at a time
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// With 6 pages total:
|
|
397
|
+
// - Batch 1: pages 0,1,2 (batchInfo.size = 3)
|
|
398
|
+
// - Batch 2: pages 3,4,5 (batchInfo.size = 3)
|
|
399
|
+
// results.length === 2 (fewer callbacks than pages)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
**Key Points:**
|
|
403
|
+
- `batchSize` = number of **pages**, not rows
|
|
404
|
+
- `batchInfo` is undefined when not batching (`batchSize` undefined or `1`)
|
|
405
|
+
- Works with deduplication, pagination strategies, and hooks
|
|
406
|
+
- Reduces callback overhead for bulk operations
|
|
407
|
+
- Default: no batching (one callback per page)
|
|
408
|
+
|
|
376
409
|
---
|
|
377
410
|
|
|
378
411
|
## 📖 API Reference
|
|
@@ -889,8 +922,8 @@ export interface TableConfig {
|
|
|
889
922
|
headerTransformer?: (args: { text: string, index: number, locator: Locator }) => string | Promise<string>;
|
|
890
923
|
/** Automatically scroll to table on init */
|
|
891
924
|
autoScroll?: boolean;
|
|
892
|
-
/**
|
|
893
|
-
debug?:
|
|
925
|
+
/** Debug options for development and troubleshooting */
|
|
926
|
+
debug?: DebugConfig;
|
|
894
927
|
/** Reset hook */
|
|
895
928
|
onReset?: (context: TableContext) => Promise<void>;
|
|
896
929
|
/** All interaction strategies */
|
package/dist/filterEngine.js
CHANGED
|
@@ -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(
|
|
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
|
@@ -11,6 +11,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.createSmartRow = void 0;
|
|
13
13
|
const fill_1 = require("./strategies/fill");
|
|
14
|
+
const stringUtils_1 = require("./utils/stringUtils");
|
|
15
|
+
const debugUtils_1 = require("./utils/debugUtils");
|
|
14
16
|
/**
|
|
15
17
|
* Factory to create a SmartRow by extending a Playwright Locator.
|
|
16
18
|
* We avoid Class/Proxy to ensure full compatibility with Playwright's expect(locator) matchers.
|
|
@@ -23,8 +25,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
23
25
|
smart.getCell = (colName) => {
|
|
24
26
|
const idx = map.get(colName);
|
|
25
27
|
if (idx === undefined) {
|
|
26
|
-
|
|
27
|
-
throw new Error(`Column "${colName}" not found. Available: ${availableColumns.join(', ')}`);
|
|
28
|
+
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
28
29
|
}
|
|
29
30
|
if (config.strategies.getCellLocator) {
|
|
30
31
|
return config.strategies.getCellLocator({
|
|
@@ -112,10 +113,13 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
112
113
|
return result;
|
|
113
114
|
});
|
|
114
115
|
smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Filling row', data);
|
|
115
117
|
for (const [colName, value] of Object.entries(data)) {
|
|
118
|
+
if (value === undefined)
|
|
119
|
+
continue;
|
|
116
120
|
const colIdx = map.get(colName);
|
|
117
121
|
if (colIdx === undefined) {
|
|
118
|
-
throw new Error(
|
|
122
|
+
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
119
123
|
}
|
|
120
124
|
yield config.strategies.cellNavigation({
|
|
121
125
|
config: config,
|
|
@@ -128,6 +132,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
128
132
|
rowIndex: rowIndex
|
|
129
133
|
});
|
|
130
134
|
const strategy = config.strategies.fill || fill_1.FillStrategies.default;
|
|
135
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
|
|
131
136
|
yield strategy({
|
|
132
137
|
row: smart,
|
|
133
138
|
columnName: colName,
|
|
@@ -138,7 +143,10 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
138
143
|
table: table,
|
|
139
144
|
fillOptions
|
|
140
145
|
});
|
|
146
|
+
// Delay after filling
|
|
147
|
+
yield (0, debugUtils_1.debugDelay)(config, 'getCell');
|
|
141
148
|
}
|
|
149
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Row fill complete');
|
|
142
150
|
});
|
|
143
151
|
smart.bringIntoView = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
144
152
|
if (rowIndex === undefined) {
|
package/dist/typeContext.d.ts
CHANGED
|
@@ -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 }) => T | Promise<T>,\n options?: {\n pagination?: PaginationStrategy;\n dedupeStrategy?: DedupeStrategy;\n maxIterations?: number;\n getIsFirst?: (context: { index: number }) => boolean;\n getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;\n onFirst?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;\n onLast?: (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";
|
package/dist/typeContext.js
CHANGED
|
@@ -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
|
-
/**
|
|
225
|
-
debug?:
|
|
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
|
|
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;
|
|
@@ -368,15 +393,21 @@ export interface TableResult<T = any> {
|
|
|
368
393
|
rows: SmartRow[];
|
|
369
394
|
allData: T[];
|
|
370
395
|
table: RestrictedTableResult;
|
|
396
|
+
batchInfo?: {
|
|
397
|
+
startIndex: number;
|
|
398
|
+
endIndex: number;
|
|
399
|
+
size: number;
|
|
400
|
+
};
|
|
371
401
|
}) => T | Promise<T>,
|
|
372
402
|
options?: {
|
|
373
403
|
pagination?: PaginationStrategy;
|
|
374
404
|
dedupeStrategy?: DedupeStrategy;
|
|
375
405
|
maxIterations?: number;
|
|
406
|
+
batchSize?: number;
|
|
376
407
|
getIsFirst?: (context: { index: number }) => boolean;
|
|
377
408
|
getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;
|
|
378
|
-
|
|
379
|
-
|
|
409
|
+
beforeFirst?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;
|
|
410
|
+
afterLast?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;
|
|
380
411
|
}
|
|
381
412
|
) => Promise<T[]>;
|
|
382
413
|
}
|
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
|
-
/**
|
|
210
|
-
debug?:
|
|
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
|
|
246
|
+
debug?: TableConfig['debug'];
|
|
223
247
|
headerTransformer: (args: {
|
|
224
248
|
text: string;
|
|
225
249
|
index: number;
|
|
@@ -347,10 +371,16 @@ export interface TableResult<T = any> {
|
|
|
347
371
|
rows: SmartRow[];
|
|
348
372
|
allData: T[];
|
|
349
373
|
table: RestrictedTableResult;
|
|
374
|
+
batchInfo?: {
|
|
375
|
+
startIndex: number;
|
|
376
|
+
endIndex: number;
|
|
377
|
+
size: number;
|
|
378
|
+
};
|
|
350
379
|
}) => T | Promise<T>, options?: {
|
|
351
380
|
pagination?: PaginationStrategy;
|
|
352
381
|
dedupeStrategy?: DedupeStrategy;
|
|
353
382
|
maxIterations?: number;
|
|
383
|
+
batchSize?: number;
|
|
354
384
|
getIsFirst?: (context: {
|
|
355
385
|
index: number;
|
|
356
386
|
}) => boolean;
|
|
@@ -358,12 +388,12 @@ export interface TableResult<T = any> {
|
|
|
358
388
|
index: number;
|
|
359
389
|
paginationResult: boolean;
|
|
360
390
|
}) => boolean;
|
|
361
|
-
|
|
391
|
+
beforeFirst?: (context: {
|
|
362
392
|
index: number;
|
|
363
393
|
rows: SmartRow[];
|
|
364
394
|
allData: any[];
|
|
365
395
|
}) => void | Promise<void>;
|
|
366
|
-
|
|
396
|
+
afterLast?: (context: {
|
|
367
397
|
index: number;
|
|
368
398
|
rows: SmartRow[];
|
|
369
399
|
allData: any[];
|
package/dist/useTable.js
CHANGED
|
@@ -26,6 +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 debugUtils_1 = require("./utils/debugUtils");
|
|
29
30
|
/**
|
|
30
31
|
* Main hook to interact with a table.
|
|
31
32
|
*/
|
|
@@ -40,7 +41,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
40
41
|
cellNavigation: columns_1.CellNavigationStrategies.default,
|
|
41
42
|
pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
42
43
|
};
|
|
43
|
-
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true,
|
|
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) });
|
|
44
45
|
const resolve = (item, parent) => {
|
|
45
46
|
if (typeof item === 'string')
|
|
46
47
|
return parent.locator(item);
|
|
@@ -53,9 +54,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
53
54
|
let _hasPaginated = false;
|
|
54
55
|
let _isInitialized = false;
|
|
55
56
|
// Helpers
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
console.log(`🔎 [SmartTable Debug] ${msg}`);
|
|
57
|
+
const log = (msg) => {
|
|
58
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', msg); // Legacy(`🔎 [SmartTable Debug] ${msg}`);
|
|
59
59
|
};
|
|
60
60
|
const _createColumnError = (colName, map, context) => {
|
|
61
61
|
const availableColumns = Array.from(map.keys());
|
|
@@ -80,7 +80,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
80
80
|
const _getMap = (timeout) => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
81
|
if (_headerMap)
|
|
82
82
|
return _headerMap;
|
|
83
|
-
|
|
83
|
+
log('Mapping headers...');
|
|
84
84
|
const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
|
|
85
85
|
if (config.autoScroll) {
|
|
86
86
|
try {
|
|
@@ -112,8 +112,25 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
112
112
|
}
|
|
113
113
|
return [text, i];
|
|
114
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
|
+
}
|
|
115
132
|
_headerMap = new Map(entries);
|
|
116
|
-
|
|
133
|
+
log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
117
134
|
return _headerMap;
|
|
118
135
|
});
|
|
119
136
|
// Placeholder for the final table object
|
|
@@ -129,13 +146,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
129
146
|
const map = yield _getMap();
|
|
130
147
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
|
|
131
148
|
let currentPage = 1;
|
|
132
|
-
|
|
149
|
+
log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
133
150
|
while (true) {
|
|
134
151
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
135
152
|
// Use FilterEngine
|
|
136
153
|
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
|
|
137
154
|
const count = yield matchedRows.count();
|
|
138
|
-
|
|
155
|
+
log(`Page ${currentPage}: Found ${count} matches.`);
|
|
139
156
|
if (count > 1) {
|
|
140
157
|
// Sample data logic (simplified for refactor, kept inline or moved to util if needed)
|
|
141
158
|
const sampleData = [];
|
|
@@ -155,7 +172,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
155
172
|
if (count === 1)
|
|
156
173
|
return matchedRows.first();
|
|
157
174
|
if (currentPage < effectiveMaxPages) {
|
|
158
|
-
|
|
175
|
+
log(`Page ${currentPage}: Not found. Attempting pagination...`);
|
|
159
176
|
const context = {
|
|
160
177
|
root: rootLocator,
|
|
161
178
|
config: config,
|
|
@@ -170,7 +187,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
170
187
|
continue;
|
|
171
188
|
}
|
|
172
189
|
else {
|
|
173
|
-
|
|
190
|
+
log(`Page ${currentPage}: Pagination failed (end of data).`);
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
if (_hasPaginated) {
|
|
@@ -223,8 +240,15 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
223
240
|
init: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
224
241
|
if (_isInitialized && _headerMap)
|
|
225
242
|
return result;
|
|
243
|
+
(0, debugUtils_1.warnIfDebugInCI)(config);
|
|
244
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
|
|
226
245
|
yield _getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
227
246
|
_isInitialized = true;
|
|
247
|
+
if (_headerMap) {
|
|
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
|
|
250
|
+
}
|
|
251
|
+
yield (0, debugUtils_1.debugDelay)(config, 'default');
|
|
228
252
|
return result;
|
|
229
253
|
}),
|
|
230
254
|
scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -255,19 +279,19 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
255
279
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
256
280
|
}),
|
|
257
281
|
reset: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
258
|
-
|
|
282
|
+
log("Resetting table...");
|
|
259
283
|
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
260
284
|
yield config.onReset(context);
|
|
261
285
|
_hasPaginated = false;
|
|
262
286
|
_headerMap = null;
|
|
263
287
|
_isInitialized = false;
|
|
264
|
-
|
|
288
|
+
log("Table reset complete.");
|
|
265
289
|
}),
|
|
266
290
|
revalidate: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
267
|
-
|
|
291
|
+
log("Revalidating table structure...");
|
|
268
292
|
_headerMap = null; // Clear the map to force re-scanning
|
|
269
293
|
yield _getMap(); // Re-scan headers
|
|
270
|
-
|
|
294
|
+
log("Table revalidated.");
|
|
271
295
|
}),
|
|
272
296
|
getColumnValues: (column, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
273
297
|
var _a, _b;
|
|
@@ -279,7 +303,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
279
303
|
const effectiveMaxPages = (_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages;
|
|
280
304
|
let currentPage = 1;
|
|
281
305
|
const results = [];
|
|
282
|
-
|
|
306
|
+
log(`Getting column values for '${column}' (Pages: ${effectiveMaxPages})`);
|
|
283
307
|
while (true) {
|
|
284
308
|
const rows = yield resolve(config.rowSelector, rootLocator).all();
|
|
285
309
|
for (const row of rows) {
|
|
@@ -316,11 +340,18 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
316
340
|
return _makeSmart(rowLocator, _headerMap, rowIndex);
|
|
317
341
|
},
|
|
318
342
|
findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
343
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Searching for row', filters);
|
|
319
344
|
yield _ensureInitialized();
|
|
320
345
|
let row = yield _findRowLocator(filters, options);
|
|
321
|
-
if (
|
|
322
|
-
|
|
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);
|
|
323
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() });
|
|
324
355
|
return _makeSmart(row, _headerMap, 0);
|
|
325
356
|
}),
|
|
326
357
|
getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -379,7 +410,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
379
410
|
yield _ensureInitialized();
|
|
380
411
|
if (!config.strategies.sorting)
|
|
381
412
|
throw new Error('No sorting strategy has been configured.');
|
|
382
|
-
|
|
413
|
+
log(`Applying sort for column "${columnName}" (${direction})`);
|
|
383
414
|
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
384
415
|
yield config.strategies.sorting.doSort({ columnName, direction, context });
|
|
385
416
|
}),
|
|
@@ -419,10 +450,14 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
419
450
|
const getIsLast = (_c = options === null || options === void 0 ? void 0 : options.getIsLast) !== null && _c !== void 0 ? _c : (() => false);
|
|
420
451
|
const allData = [];
|
|
421
452
|
const effectiveMaxIterations = (_d = options === null || options === void 0 ? void 0 : options.maxIterations) !== null && _d !== void 0 ? _d : config.maxPages;
|
|
453
|
+
const batchSize = options === null || options === void 0 ? void 0 : options.batchSize;
|
|
454
|
+
const isBatching = batchSize !== undefined && batchSize > 1;
|
|
422
455
|
let index = 0;
|
|
423
456
|
let paginationResult = true;
|
|
424
457
|
let seenKeys = null;
|
|
425
|
-
|
|
458
|
+
let batchRows = [];
|
|
459
|
+
let batchStartIndex = 0;
|
|
460
|
+
log(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations}, batchSize: ${batchSize !== null && batchSize !== void 0 ? batchSize : 'none'})`);
|
|
426
461
|
while (index < effectiveMaxIterations) {
|
|
427
462
|
const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
|
|
428
463
|
let rows = rowLocators.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
@@ -438,28 +473,102 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
438
473
|
}
|
|
439
474
|
}
|
|
440
475
|
rows = deduplicated;
|
|
441
|
-
|
|
476
|
+
log(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
|
|
442
477
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
478
|
+
// Add rows to batch if batching is enabled
|
|
479
|
+
if (isBatching) {
|
|
480
|
+
batchRows.push(...rows);
|
|
481
|
+
}
|
|
482
|
+
const isLastIteration = index === effectiveMaxIterations - 1;
|
|
483
|
+
// Determine if we should invoke the callback
|
|
484
|
+
const batchComplete = isBatching && (index - batchStartIndex + 1) >= batchSize;
|
|
485
|
+
const shouldInvokeCallback = !isBatching || batchComplete || isLastIteration;
|
|
486
|
+
if (shouldInvokeCallback) {
|
|
487
|
+
const callbackRows = isBatching ? batchRows : rows;
|
|
488
|
+
const callbackIndex = isBatching ? batchStartIndex : index;
|
|
489
|
+
const isFirst = getIsFirst({ index: callbackIndex });
|
|
490
|
+
let isLast = getIsLast({ index: callbackIndex, paginationResult });
|
|
491
|
+
const isLastDueToMax = index === effectiveMaxIterations - 1;
|
|
492
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
493
|
+
yield options.beforeFirst({ index: callbackIndex, rows: callbackRows, allData });
|
|
494
|
+
}
|
|
495
|
+
const batchInfo = isBatching ? {
|
|
496
|
+
startIndex: batchStartIndex,
|
|
497
|
+
endIndex: index,
|
|
498
|
+
size: index - batchStartIndex + 1
|
|
499
|
+
} : undefined;
|
|
500
|
+
const returnValue = yield callback({
|
|
501
|
+
index: callbackIndex,
|
|
502
|
+
isFirst,
|
|
503
|
+
isLast,
|
|
504
|
+
rows: callbackRows,
|
|
505
|
+
allData,
|
|
506
|
+
table: restrictedTable,
|
|
507
|
+
batchInfo
|
|
508
|
+
});
|
|
509
|
+
allData.push(returnValue);
|
|
510
|
+
// Determine if this is truly the last iteration
|
|
511
|
+
let finalIsLast = isLastDueToMax;
|
|
512
|
+
if (!isLastIteration) {
|
|
513
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
514
|
+
paginationResult = yield paginationStrategy(context);
|
|
515
|
+
(0, debugUtils_1.logDebug)(config, 'info', `Pagination ${paginationResult ? 'succeeded' : 'failed'}`);
|
|
516
|
+
yield (0, debugUtils_1.debugDelay)(config, 'pagination');
|
|
517
|
+
finalIsLast = getIsLast({ index: callbackIndex, paginationResult }) || !paginationResult;
|
|
518
|
+
}
|
|
519
|
+
if (finalIsLast && (options === null || options === void 0 ? void 0 : options.afterLast)) {
|
|
520
|
+
yield options.afterLast({ index: callbackIndex, rows: callbackRows, allData });
|
|
521
|
+
}
|
|
522
|
+
if (finalIsLast || !paginationResult) {
|
|
523
|
+
log(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
// Reset batch
|
|
527
|
+
if (isBatching) {
|
|
528
|
+
batchRows = [];
|
|
529
|
+
batchStartIndex = index + 1;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
// Continue paginating even when batching
|
|
534
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
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');
|
|
538
|
+
if (!paginationResult) {
|
|
539
|
+
// Pagination failed, invoke callback with current batch
|
|
540
|
+
const callbackIndex = batchStartIndex;
|
|
541
|
+
const isFirst = getIsFirst({ index: callbackIndex });
|
|
542
|
+
const isLast = true;
|
|
543
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
544
|
+
yield options.beforeFirst({ index: callbackIndex, rows: batchRows, allData });
|
|
545
|
+
}
|
|
546
|
+
const batchInfo = {
|
|
547
|
+
startIndex: batchStartIndex,
|
|
548
|
+
endIndex: index,
|
|
549
|
+
size: index - batchStartIndex + 1
|
|
550
|
+
};
|
|
551
|
+
const returnValue = yield callback({
|
|
552
|
+
index: callbackIndex,
|
|
553
|
+
isFirst,
|
|
554
|
+
isLast,
|
|
555
|
+
rows: batchRows,
|
|
556
|
+
allData,
|
|
557
|
+
table: restrictedTable,
|
|
558
|
+
batchInfo
|
|
559
|
+
});
|
|
560
|
+
allData.push(returnValue);
|
|
561
|
+
if (options === null || options === void 0 ? void 0 : options.afterLast) {
|
|
562
|
+
yield options.afterLast({ index: callbackIndex, rows: batchRows, allData });
|
|
563
|
+
}
|
|
564
|
+
log(`Pagination failed mid-batch (index: ${index})`);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
458
567
|
}
|
|
459
568
|
index++;
|
|
460
|
-
|
|
569
|
+
log(`Iteration ${index} completed, continuing...`);
|
|
461
570
|
}
|
|
462
|
-
|
|
571
|
+
log(`iterateThroughTable completed after ${index + 1} iterations, collected ${allData.length} items`);
|
|
463
572
|
return allData;
|
|
464
573
|
}),
|
|
465
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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate Levenshtein distance between two strings
|
|
3
|
+
* Used for "did you mean?" suggestions
|
|
4
|
+
*/
|
|
5
|
+
export declare function levenshteinDistance(a: string, b: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* Calculate similarity score between two strings (0-1)
|
|
8
|
+
* 1 = identical, 0 = completely different
|
|
9
|
+
*/
|
|
10
|
+
export declare function stringSimilarity(a: string, b: string): number;
|
|
11
|
+
/**
|
|
12
|
+
* Find similar strings from a list
|
|
13
|
+
* Returns matches above threshold, sorted by similarity
|
|
14
|
+
*/
|
|
15
|
+
export declare function findSimilar(input: string, available: string[], threshold?: number): Array<{
|
|
16
|
+
value: string;
|
|
17
|
+
score: number;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Build a helpful error message for column not found
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildColumnNotFoundError(columnName: string, availableColumns: string[]): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.levenshteinDistance = levenshteinDistance;
|
|
4
|
+
exports.stringSimilarity = stringSimilarity;
|
|
5
|
+
exports.findSimilar = findSimilar;
|
|
6
|
+
exports.buildColumnNotFoundError = buildColumnNotFoundError;
|
|
7
|
+
/**
|
|
8
|
+
* Calculate Levenshtein distance between two strings
|
|
9
|
+
* Used for "did you mean?" suggestions
|
|
10
|
+
*/
|
|
11
|
+
function levenshteinDistance(a, b) {
|
|
12
|
+
const matrix = [];
|
|
13
|
+
for (let i = 0; i <= b.length; i++) {
|
|
14
|
+
matrix[i] = [i];
|
|
15
|
+
}
|
|
16
|
+
for (let j = 0; j <= a.length; j++) {
|
|
17
|
+
matrix[0][j] = j;
|
|
18
|
+
}
|
|
19
|
+
for (let i = 1; i <= b.length; i++) {
|
|
20
|
+
for (let j = 1; j <= a.length; j++) {
|
|
21
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
22
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
26
|
+
matrix[i][j - 1] + 1, // insertion
|
|
27
|
+
matrix[i - 1][j] + 1 // deletion
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return matrix[b.length][a.length];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate similarity score between two strings (0-1)
|
|
36
|
+
* 1 = identical, 0 = completely different
|
|
37
|
+
*/
|
|
38
|
+
function stringSimilarity(a, b) {
|
|
39
|
+
const distance = levenshteinDistance(a.toLowerCase(), b.toLowerCase());
|
|
40
|
+
const maxLen = Math.max(a.length, b.length);
|
|
41
|
+
return maxLen === 0 ? 1 : 1 - (distance / maxLen);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Find similar strings from a list
|
|
45
|
+
* Returns matches above threshold, sorted by similarity
|
|
46
|
+
*/
|
|
47
|
+
function findSimilar(input, available, threshold = 0.5) {
|
|
48
|
+
return available
|
|
49
|
+
.map(value => ({
|
|
50
|
+
value,
|
|
51
|
+
score: stringSimilarity(input, value)
|
|
52
|
+
}))
|
|
53
|
+
.filter(x => x.score >= threshold)
|
|
54
|
+
.sort((a, b) => b.score - a.score)
|
|
55
|
+
.slice(0, 3); // Top 3 suggestions
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Build a helpful error message for column not found
|
|
59
|
+
*/
|
|
60
|
+
function buildColumnNotFoundError(columnName, availableColumns) {
|
|
61
|
+
const suggestions = findSimilar(columnName, availableColumns);
|
|
62
|
+
let message = `Column '${columnName}' not found`;
|
|
63
|
+
if (suggestions.length > 0) {
|
|
64
|
+
message += `\n\nDid you mean:`;
|
|
65
|
+
suggestions.forEach(({ value, score }) => {
|
|
66
|
+
const percentage = Math.round(score * 100);
|
|
67
|
+
message += `\n • ${value} (${percentage}% match)`;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
message += `\n\nAvailable columns: ${availableColumns.join(', ')}`;
|
|
71
|
+
message += `\n\nTip: Column names are case-sensitive`;
|
|
72
|
+
return message;
|
|
73
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
/**
|
|
3
|
+
* Add a custom trace event to Playwright's trace viewer
|
|
4
|
+
* Uses page.evaluate to log events that appear in the trace
|
|
5
|
+
*/
|
|
6
|
+
export declare function addTraceEvent(page: Page, type: string, data?: Record<string, any>): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if tracing is currently enabled
|
|
9
|
+
* Used for conditional trace logic
|
|
10
|
+
*/
|
|
11
|
+
export declare function isTracingEnabled(page: Page): Promise<boolean>;
|
|
@@ -0,0 +1,47 @@
|
|
|
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.addTraceEvent = addTraceEvent;
|
|
13
|
+
exports.isTracingEnabled = isTracingEnabled;
|
|
14
|
+
/**
|
|
15
|
+
* Add a custom trace event to Playwright's trace viewer
|
|
16
|
+
* Uses page.evaluate to log events that appear in the trace
|
|
17
|
+
*/
|
|
18
|
+
function addTraceEvent(page_1, type_1) {
|
|
19
|
+
return __awaiter(this, arguments, void 0, function* (page, type, data = {}) {
|
|
20
|
+
try {
|
|
21
|
+
// Add a console log that will appear in the trace viewer
|
|
22
|
+
// Prefix with [SmartTable] for easy filtering
|
|
23
|
+
const message = `[SmartTable:${type}] ${JSON.stringify(data)}`;
|
|
24
|
+
yield page.evaluate((msg) => console.log(msg), message);
|
|
25
|
+
}
|
|
26
|
+
catch (_a) {
|
|
27
|
+
// Silently ignore if page is not available
|
|
28
|
+
// This ensures zero overhead when tracing is off
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Check if tracing is currently enabled
|
|
34
|
+
* Used for conditional trace logic
|
|
35
|
+
*/
|
|
36
|
+
function isTracingEnabled(page) {
|
|
37
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
38
|
+
try {
|
|
39
|
+
// We can't directly check if tracing is enabled
|
|
40
|
+
// But we can safely call addTraceEvent - it will just be a no-op if not tracing
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch (_a) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "5.
|
|
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",
|