@rickcedwhat/playwright-smart-table 5.0.0 → 5.1.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 +37 -4
- package/dist/smartRow.js +5 -2
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +8 -2
- package/dist/types.d.ts +8 -2
- package/dist/useTable.js +95 -17
- 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
|
@@ -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
|
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 traceUtils_1 = require("./utils/traceUtils");
|
|
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({
|
|
@@ -35,6 +36,8 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
35
36
|
page: rootLocator.page()
|
|
36
37
|
});
|
|
37
38
|
}
|
|
39
|
+
// Add trace event
|
|
40
|
+
(0, traceUtils_1.addTraceEvent)(rootLocator.page(), 'getCell', { column: colName, columnIndex: idx, rowIndex }).catch(() => { });
|
|
38
41
|
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
39
42
|
};
|
|
40
43
|
smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
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\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";
|
package/dist/typeContext.js
CHANGED
|
@@ -368,15 +368,21 @@ export interface TableResult<T = any> {
|
|
|
368
368
|
rows: SmartRow[];
|
|
369
369
|
allData: T[];
|
|
370
370
|
table: RestrictedTableResult;
|
|
371
|
+
batchInfo?: {
|
|
372
|
+
startIndex: number;
|
|
373
|
+
endIndex: number;
|
|
374
|
+
size: number;
|
|
375
|
+
};
|
|
371
376
|
}) => T | Promise<T>,
|
|
372
377
|
options?: {
|
|
373
378
|
pagination?: PaginationStrategy;
|
|
374
379
|
dedupeStrategy?: DedupeStrategy;
|
|
375
380
|
maxIterations?: number;
|
|
381
|
+
batchSize?: number;
|
|
376
382
|
getIsFirst?: (context: { index: number }) => boolean;
|
|
377
383
|
getIsLast?: (context: { index: number, paginationResult: boolean }) => boolean;
|
|
378
|
-
|
|
379
|
-
|
|
384
|
+
beforeFirst?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;
|
|
385
|
+
afterLast?: (context: { index: number, rows: SmartRow[], allData: any[] }) => void | Promise<void>;
|
|
380
386
|
}
|
|
381
387
|
) => Promise<T[]>;
|
|
382
388
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -347,10 +347,16 @@ export interface TableResult<T = any> {
|
|
|
347
347
|
rows: SmartRow[];
|
|
348
348
|
allData: T[];
|
|
349
349
|
table: RestrictedTableResult;
|
|
350
|
+
batchInfo?: {
|
|
351
|
+
startIndex: number;
|
|
352
|
+
endIndex: number;
|
|
353
|
+
size: number;
|
|
354
|
+
};
|
|
350
355
|
}) => T | Promise<T>, options?: {
|
|
351
356
|
pagination?: PaginationStrategy;
|
|
352
357
|
dedupeStrategy?: DedupeStrategy;
|
|
353
358
|
maxIterations?: number;
|
|
359
|
+
batchSize?: number;
|
|
354
360
|
getIsFirst?: (context: {
|
|
355
361
|
index: number;
|
|
356
362
|
}) => boolean;
|
|
@@ -358,12 +364,12 @@ export interface TableResult<T = any> {
|
|
|
358
364
|
index: number;
|
|
359
365
|
paginationResult: boolean;
|
|
360
366
|
}) => boolean;
|
|
361
|
-
|
|
367
|
+
beforeFirst?: (context: {
|
|
362
368
|
index: number;
|
|
363
369
|
rows: SmartRow[];
|
|
364
370
|
allData: any[];
|
|
365
371
|
}) => void | Promise<void>;
|
|
366
|
-
|
|
372
|
+
afterLast?: (context: {
|
|
367
373
|
index: number;
|
|
368
374
|
rows: SmartRow[];
|
|
369
375
|
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 traceUtils_1 = require("./utils/traceUtils");
|
|
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, 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, debug: false, 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);
|
|
@@ -225,6 +226,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
225
226
|
return result;
|
|
226
227
|
yield _getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
227
228
|
_isInitialized = true;
|
|
229
|
+
if (_headerMap) {
|
|
230
|
+
yield (0, traceUtils_1.addTraceEvent)(rootLocator.page(), 'init', { headers: Array.from(_headerMap.keys()), columnCount: _headerMap.size });
|
|
231
|
+
}
|
|
228
232
|
return result;
|
|
229
233
|
}),
|
|
230
234
|
scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -419,10 +423,14 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
419
423
|
const getIsLast = (_c = options === null || options === void 0 ? void 0 : options.getIsLast) !== null && _c !== void 0 ? _c : (() => false);
|
|
420
424
|
const allData = [];
|
|
421
425
|
const effectiveMaxIterations = (_d = options === null || options === void 0 ? void 0 : options.maxIterations) !== null && _d !== void 0 ? _d : config.maxPages;
|
|
426
|
+
const batchSize = options === null || options === void 0 ? void 0 : options.batchSize;
|
|
427
|
+
const isBatching = batchSize !== undefined && batchSize > 1;
|
|
422
428
|
let index = 0;
|
|
423
429
|
let paginationResult = true;
|
|
424
430
|
let seenKeys = null;
|
|
425
|
-
|
|
431
|
+
let batchRows = [];
|
|
432
|
+
let batchStartIndex = 0;
|
|
433
|
+
logDebug(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations}, batchSize: ${batchSize !== null && batchSize !== void 0 ? batchSize : 'none'})`);
|
|
426
434
|
while (index < effectiveMaxIterations) {
|
|
427
435
|
const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
|
|
428
436
|
let rows = rowLocators.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
@@ -440,21 +448,91 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
440
448
|
rows = deduplicated;
|
|
441
449
|
logDebug(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
|
|
442
450
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
451
|
+
// Add rows to batch if batching is enabled
|
|
452
|
+
if (isBatching) {
|
|
453
|
+
batchRows.push(...rows);
|
|
454
|
+
}
|
|
455
|
+
const isLastIteration = index === effectiveMaxIterations - 1;
|
|
456
|
+
// Determine if we should invoke the callback
|
|
457
|
+
const batchComplete = isBatching && (index - batchStartIndex + 1) >= batchSize;
|
|
458
|
+
const shouldInvokeCallback = !isBatching || batchComplete || isLastIteration;
|
|
459
|
+
if (shouldInvokeCallback) {
|
|
460
|
+
const callbackRows = isBatching ? batchRows : rows;
|
|
461
|
+
const callbackIndex = isBatching ? batchStartIndex : index;
|
|
462
|
+
const isFirst = getIsFirst({ index: callbackIndex });
|
|
463
|
+
let isLast = getIsLast({ index: callbackIndex, paginationResult });
|
|
464
|
+
const isLastDueToMax = index === effectiveMaxIterations - 1;
|
|
465
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
466
|
+
yield options.beforeFirst({ index: callbackIndex, rows: callbackRows, allData });
|
|
467
|
+
}
|
|
468
|
+
const batchInfo = isBatching ? {
|
|
469
|
+
startIndex: batchStartIndex,
|
|
470
|
+
endIndex: index,
|
|
471
|
+
size: index - batchStartIndex + 1
|
|
472
|
+
} : undefined;
|
|
473
|
+
const returnValue = yield callback({
|
|
474
|
+
index: callbackIndex,
|
|
475
|
+
isFirst,
|
|
476
|
+
isLast,
|
|
477
|
+
rows: callbackRows,
|
|
478
|
+
allData,
|
|
479
|
+
table: restrictedTable,
|
|
480
|
+
batchInfo
|
|
481
|
+
});
|
|
482
|
+
allData.push(returnValue);
|
|
483
|
+
// Determine if this is truly the last iteration
|
|
484
|
+
let finalIsLast = isLastDueToMax;
|
|
485
|
+
if (!isLastIteration) {
|
|
486
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
487
|
+
paginationResult = yield paginationStrategy(context);
|
|
488
|
+
finalIsLast = getIsLast({ index: callbackIndex, paginationResult }) || !paginationResult;
|
|
489
|
+
}
|
|
490
|
+
if (finalIsLast && (options === null || options === void 0 ? void 0 : options.afterLast)) {
|
|
491
|
+
yield options.afterLast({ index: callbackIndex, rows: callbackRows, allData });
|
|
492
|
+
}
|
|
493
|
+
if (finalIsLast || !paginationResult) {
|
|
494
|
+
logDebug(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
// Reset batch
|
|
498
|
+
if (isBatching) {
|
|
499
|
+
batchRows = [];
|
|
500
|
+
batchStartIndex = index + 1;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// Continue paginating even when batching
|
|
505
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
506
|
+
paginationResult = yield paginationStrategy(context);
|
|
507
|
+
if (!paginationResult) {
|
|
508
|
+
// Pagination failed, invoke callback with current batch
|
|
509
|
+
const callbackIndex = batchStartIndex;
|
|
510
|
+
const isFirst = getIsFirst({ index: callbackIndex });
|
|
511
|
+
const isLast = true;
|
|
512
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
513
|
+
yield options.beforeFirst({ index: callbackIndex, rows: batchRows, allData });
|
|
514
|
+
}
|
|
515
|
+
const batchInfo = {
|
|
516
|
+
startIndex: batchStartIndex,
|
|
517
|
+
endIndex: index,
|
|
518
|
+
size: index - batchStartIndex + 1
|
|
519
|
+
};
|
|
520
|
+
const returnValue = yield callback({
|
|
521
|
+
index: callbackIndex,
|
|
522
|
+
isFirst,
|
|
523
|
+
isLast,
|
|
524
|
+
rows: batchRows,
|
|
525
|
+
allData,
|
|
526
|
+
table: restrictedTable,
|
|
527
|
+
batchInfo
|
|
528
|
+
});
|
|
529
|
+
allData.push(returnValue);
|
|
530
|
+
if (options === null || options === void 0 ? void 0 : options.afterLast) {
|
|
531
|
+
yield options.afterLast({ index: callbackIndex, rows: batchRows, allData });
|
|
532
|
+
}
|
|
533
|
+
logDebug(`Pagination failed mid-batch (index: ${index})`);
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
458
536
|
}
|
|
459
537
|
index++;
|
|
460
538
|
logDebug(`Iteration ${index} completed, continuing...`);
|
|
@@ -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.1.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",
|