@rickcedwhat/playwright-smart-table 6.7.8 → 6.8.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 +13 -9
- package/dist/engine/tableIteration.d.ts +7 -9
- package/dist/engine/tableIteration.js +89 -191
- package/dist/engine/tableMapper.js +1 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/presets/glide/columns.d.ts +4 -1
- package/dist/presets/glide/columns.js +29 -18
- package/dist/presets/glide/index.d.ts +2 -0
- package/dist/presets/glide/index.js +26 -3
- package/dist/smartRow.d.ts +2 -1
- package/dist/smartRow.js +191 -63
- package/dist/strategies/columns.d.ts +11 -0
- package/dist/strategies/fill.js +1 -1
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +34 -7
- package/dist/types.d.ts +33 -7
- package/dist/useTable.js +8 -6
- package/dist/utils/mutex.d.ts +8 -0
- package/dist/utils/mutex.js +31 -0
- package/dist/utils/navigationBarrier.d.ts +21 -0
- package/dist/utils/navigationBarrier.js +82 -0
- package/package.json +14 -2
package/dist/types.d.ts
CHANGED
|
@@ -329,6 +329,11 @@ export interface TableConfig<T = any> {
|
|
|
329
329
|
cellSelector?: string | ((row: Locator) => Locator);
|
|
330
330
|
/** Number of pages to scan for verification */
|
|
331
331
|
maxPages?: number;
|
|
332
|
+
/**
|
|
333
|
+
* Default concurrency strategy for iteration methods.
|
|
334
|
+
* Can be overridden by the options passed to forEach, map, or filter.
|
|
335
|
+
*/
|
|
336
|
+
concurrency?: RowIterationMode;
|
|
332
337
|
/** Hook to rename columns dynamically */
|
|
333
338
|
headerTransformer?: (args: {
|
|
334
339
|
text: string;
|
|
@@ -356,6 +361,7 @@ export interface FinalTableConfig<T = any> extends TableConfig<T> {
|
|
|
356
361
|
cellSelector: string | ((row: Locator) => Locator);
|
|
357
362
|
maxPages: number;
|
|
358
363
|
autoScroll: boolean;
|
|
364
|
+
concurrency?: RowIterationMode;
|
|
359
365
|
debug?: TableConfig['debug'];
|
|
360
366
|
headerTransformer: (args: {
|
|
361
367
|
text: string;
|
|
@@ -379,15 +385,26 @@ export type RowIterationContext<T = any> = {
|
|
|
379
385
|
rowIndex: number;
|
|
380
386
|
stop: () => void;
|
|
381
387
|
};
|
|
388
|
+
/** Concurrency modes for row iteration. */
|
|
389
|
+
export type RowIterationMode = 'parallel' | 'sequential' | 'synchronized';
|
|
382
390
|
/** Shared options for forEach, map, and filter. */
|
|
383
391
|
export type RowIterationOptions = {
|
|
384
392
|
/** Maximum number of pages to iterate. Defaults to config.maxPages. */
|
|
385
393
|
maxPages?: number;
|
|
386
394
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
395
|
+
* @deprecated Use 'concurrency' mode instead.
|
|
396
|
+
* - parallel: true -> concurrency: 'parallel'
|
|
397
|
+
* - parallel: false -> concurrency: 'sequential'
|
|
389
398
|
*/
|
|
390
399
|
parallel?: boolean;
|
|
400
|
+
/**
|
|
401
|
+
* Concurrency strategy for iteration.
|
|
402
|
+
* - 'parallel': Full parallel execution of both navigation and actions.
|
|
403
|
+
* - 'synchronized': Parallel navigation (lock-step) with serial actions.
|
|
404
|
+
* - 'sequential': Strictly serial one-at-a-time execution. No parallel navigation.
|
|
405
|
+
* @default 'parallel' (for map), 'sequential' (for forEach/filter)
|
|
406
|
+
*/
|
|
407
|
+
concurrency?: RowIterationMode;
|
|
391
408
|
/**
|
|
392
409
|
* Deduplication strategy. Use when rows may repeat across iterations
|
|
393
410
|
* (e.g. infinite scroll tables). Returns a unique key per row.
|
|
@@ -475,6 +492,9 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
475
492
|
* Execution is sequential by default (safe for interactions like clicking/filling).
|
|
476
493
|
* Call `stop()` in the callback to end iteration early.
|
|
477
494
|
*
|
|
495
|
+
* @param callback - Function receiving { row, rowIndex, stop }
|
|
496
|
+
* @param options - maxPages, concurrency, dedupe, useBulkPagination (`parallel` is deprecated; use `concurrency`)
|
|
497
|
+
*
|
|
478
498
|
* @example
|
|
479
499
|
* await table.forEach(async ({ row, stop }) => {
|
|
480
500
|
* if (await row.getCell('Status').innerText() === 'Done') stop();
|
|
@@ -487,22 +507,25 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
487
507
|
* Execution is parallel within each page by default (safe for reads).
|
|
488
508
|
* Call `stop()` to halt after the current page finishes.
|
|
489
509
|
*
|
|
490
|
-
* > **⚠️ UI Interactions:** `map` defaults to `
|
|
491
|
-
* > fills inputs, or otherwise mutates UI state, pass `
|
|
492
|
-
* >
|
|
510
|
+
* > **⚠️ UI Interactions:** `map` defaults to `concurrency: 'parallel'`. If your callback opens popovers,
|
|
511
|
+
* > fills inputs, or otherwise mutates UI state, pass `concurrency: 'sequential'` (or `'synchronized'`
|
|
512
|
+
* > when navigation must stay lock-step) to avoid overlapping interactions.
|
|
513
|
+
*
|
|
514
|
+
* @param callback - Function receiving { row, rowIndex, stop }
|
|
515
|
+
* @param options - maxPages, concurrency, dedupe, useBulkPagination (`parallel` is deprecated; use `concurrency`)
|
|
493
516
|
*
|
|
494
517
|
* @example
|
|
495
518
|
* // Data extraction — parallel is safe
|
|
496
519
|
* const emails = await table.map(({ row }) => row.getCell('Email').innerText());
|
|
497
520
|
*
|
|
498
521
|
* @example
|
|
499
|
-
* // UI interactions —
|
|
522
|
+
* // UI interactions — use sequential (or synchronized) concurrency
|
|
500
523
|
* const assignees = await table.map(async ({ row }) => {
|
|
501
524
|
* await row.getCell('Assignee').locator('button').click();
|
|
502
525
|
* const name = await page.locator('.popover .name').innerText();
|
|
503
526
|
* await page.keyboard.press('Escape');
|
|
504
527
|
* return name;
|
|
505
|
-
* }, {
|
|
528
|
+
* }, { concurrency: 'sequential' });
|
|
506
529
|
*/
|
|
507
530
|
map<R>(callback: (ctx: RowIterationContext<T>) => R | Promise<R>, options?: RowIterationOptions): Promise<R[]>;
|
|
508
531
|
/**
|
|
@@ -510,6 +533,9 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
510
533
|
* Rows are returned as-is — call `bringIntoView()` on each if needed.
|
|
511
534
|
* Execution is sequential by default.
|
|
512
535
|
*
|
|
536
|
+
* @param predicate - Function receiving { row, rowIndex, stop }
|
|
537
|
+
* @param options - maxPages, concurrency, dedupe, useBulkPagination (`parallel` is deprecated; use `concurrency`)
|
|
538
|
+
*
|
|
513
539
|
* @example
|
|
514
540
|
* const active = await table.filter(async ({ row }) =>
|
|
515
541
|
* await row.getCell('Status').innerText() === 'Active'
|
package/dist/useTable.js
CHANGED
|
@@ -39,6 +39,7 @@ const tableIteration_1 = require("./engine/tableIteration");
|
|
|
39
39
|
const debugUtils_1 = require("./utils/debugUtils");
|
|
40
40
|
const smartRowArray_1 = require("./utils/smartRowArray");
|
|
41
41
|
const elementTracker_1 = require("./utils/elementTracker");
|
|
42
|
+
const navigationBarrier_1 = require("./utils/navigationBarrier");
|
|
42
43
|
/**
|
|
43
44
|
* Main hook to interact with a table.
|
|
44
45
|
*/
|
|
@@ -94,8 +95,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
94
95
|
// Placeholder for the final table object
|
|
95
96
|
let finalTable = null;
|
|
96
97
|
// Helper factory
|
|
97
|
-
const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex) => {
|
|
98
|
-
return (0, smartRow_1.default)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex);
|
|
98
|
+
const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier) => {
|
|
99
|
+
return (0, smartRow_1.default)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier);
|
|
99
100
|
};
|
|
100
101
|
const tableState = { currentPageIndex: 0 };
|
|
101
102
|
const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart, tableState);
|
|
@@ -299,8 +300,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
299
300
|
const rowLocators = resolve(config.rowSelector, rootLocator);
|
|
300
301
|
const newIndices = yield __await(tracker.getUnseenIndices(rowLocators));
|
|
301
302
|
const pageRows = yield __await(rowLocators.all());
|
|
303
|
+
const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
|
|
302
304
|
for (const idx of newIndices) {
|
|
303
|
-
yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex), rowIndex });
|
|
305
|
+
yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex, pagesScanned - 1, barrier), rowIndex });
|
|
304
306
|
rowIndex++;
|
|
305
307
|
}
|
|
306
308
|
if (pagesScanned >= effectiveMaxPages)
|
|
@@ -322,7 +324,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
322
324
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
323
325
|
getMap: () => tableMapper.getMapSync(),
|
|
324
326
|
advancePage: _advancePage,
|
|
325
|
-
makeSmartRow: (loc, map, idx, pageIdx) => _makeSmart(loc, map, idx, pageIdx),
|
|
327
|
+
makeSmartRow: (loc, map, idx, pageIdx, barrier) => _makeSmart(loc, map, idx, pageIdx, barrier),
|
|
326
328
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
327
329
|
config,
|
|
328
330
|
getPage: () => rootLocator.page(),
|
|
@@ -334,7 +336,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
334
336
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
335
337
|
getMap: () => tableMapper.getMapSync(),
|
|
336
338
|
advancePage: _advancePage,
|
|
337
|
-
makeSmartRow: (loc, map, idx, pageIdx) => _makeSmart(loc, map, idx, pageIdx),
|
|
339
|
+
makeSmartRow: (loc, map, idx, pageIdx, barrier) => _makeSmart(loc, map, idx, pageIdx, barrier),
|
|
338
340
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
339
341
|
config,
|
|
340
342
|
getPage: () => rootLocator.page(),
|
|
@@ -346,7 +348,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
346
348
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
347
349
|
getMap: () => tableMapper.getMapSync(),
|
|
348
350
|
advancePage: _advancePage,
|
|
349
|
-
makeSmartRow: (loc, map, idx, pageIdx) => _makeSmart(loc, map, idx, pageIdx),
|
|
351
|
+
makeSmartRow: (loc, map, idx, pageIdx, barrier) => _makeSmart(loc, map, idx, pageIdx, barrier),
|
|
350
352
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
351
353
|
config,
|
|
352
354
|
getPage: () => rootLocator.page(),
|
|
@@ -0,0 +1,31 @@
|
|
|
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.Mutex = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* A simple async mutex to handle sequential execution within
|
|
15
|
+
* a parallel batch.
|
|
16
|
+
*/
|
|
17
|
+
class Mutex {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.queue = Promise.resolve();
|
|
20
|
+
}
|
|
21
|
+
run(action) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
const next = this.queue.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
return yield action();
|
|
25
|
+
}));
|
|
26
|
+
this.queue = next.catch(() => { }); // prevent chain breakage
|
|
27
|
+
return yield next;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.Mutex = Mutex;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A synchronization barrier that allows multiple parallel row processors
|
|
3
|
+
* to coordinate their navigation actions.
|
|
4
|
+
*/
|
|
5
|
+
export declare class NavigationBarrier {
|
|
6
|
+
private total;
|
|
7
|
+
private finishedCount;
|
|
8
|
+
private buckets;
|
|
9
|
+
constructor(total: number);
|
|
10
|
+
/**
|
|
11
|
+
* Synchronize all active rows at a specific column index.
|
|
12
|
+
* The last row to arrive will trigger the `moveAction`.
|
|
13
|
+
*/
|
|
14
|
+
sync<T = void>(colIndex: number, moveAction?: () => Promise<T>): Promise<T | void>;
|
|
15
|
+
/**
|
|
16
|
+
* Mark a row as finished (no more cells to process).
|
|
17
|
+
* This ensures the barrier doesn't wait for rows that have exited the loop.
|
|
18
|
+
*/
|
|
19
|
+
markFinished(): void;
|
|
20
|
+
private broadcast;
|
|
21
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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.NavigationBarrier = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* A synchronization barrier that allows multiple parallel row processors
|
|
15
|
+
* to coordinate their navigation actions.
|
|
16
|
+
*/
|
|
17
|
+
class NavigationBarrier {
|
|
18
|
+
constructor(total) {
|
|
19
|
+
this.finishedCount = 0;
|
|
20
|
+
// Map of colIndex -> Set of waiting resolvers
|
|
21
|
+
this.buckets = new Map();
|
|
22
|
+
this.total = total;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Synchronize all active rows at a specific column index.
|
|
26
|
+
* The last row to arrive will trigger the `moveAction`.
|
|
27
|
+
*/
|
|
28
|
+
sync(colIndex, moveAction) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
if (this.total <= 1) {
|
|
31
|
+
if (moveAction)
|
|
32
|
+
return yield moveAction();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
let bucket = this.buckets.get(colIndex);
|
|
37
|
+
if (!bucket) {
|
|
38
|
+
bucket = new Set();
|
|
39
|
+
this.buckets.set(colIndex, bucket);
|
|
40
|
+
}
|
|
41
|
+
bucket.add(resolve);
|
|
42
|
+
if (bucket.size + this.finishedCount >= this.total) {
|
|
43
|
+
// Use a self-executing async function to handle the moveAction synchronously in this context
|
|
44
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
45
|
+
let result;
|
|
46
|
+
try {
|
|
47
|
+
if (moveAction)
|
|
48
|
+
result = yield moveAction();
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
this.broadcast(colIndex, result);
|
|
52
|
+
}
|
|
53
|
+
}))();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Mark a row as finished (no more cells to process).
|
|
60
|
+
* This ensures the barrier doesn't wait for rows that have exited the loop.
|
|
61
|
+
*/
|
|
62
|
+
markFinished() {
|
|
63
|
+
this.finishedCount++;
|
|
64
|
+
// Check all buckets - any bucket that was waiting for this row might now be ready
|
|
65
|
+
for (const colIndex of Array.from(this.buckets.keys())) {
|
|
66
|
+
const bucket = this.buckets.get(colIndex);
|
|
67
|
+
if (bucket.size + this.finishedCount >= this.total) {
|
|
68
|
+
this.broadcast(colIndex);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
broadcast(colIndex, result) {
|
|
73
|
+
const bucket = this.buckets.get(colIndex);
|
|
74
|
+
if (bucket) {
|
|
75
|
+
for (const resolve of bucket) {
|
|
76
|
+
resolve(result);
|
|
77
|
+
}
|
|
78
|
+
this.buckets.delete(colIndex);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.NavigationBarrier = NavigationBarrier;
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.0",
|
|
4
4
|
"description": "Smart, column-aware table interactions for Playwright",
|
|
5
5
|
"author": "Cedrick Catalan",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"homepage": "https://rickcedwhat.github.io/playwright-smart-table/",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
9
10
|
"url": "https://github.com/rickcedwhat/playwright-smart-table.git"
|
|
10
11
|
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/rickcedwhat/playwright-smart-table/issues"
|
|
14
|
+
},
|
|
11
15
|
"main": "dist/index.js",
|
|
12
16
|
"types": "dist/index.d.ts",
|
|
13
17
|
"files": [
|
|
@@ -55,7 +59,15 @@
|
|
|
55
59
|
"playwright",
|
|
56
60
|
"testing",
|
|
57
61
|
"table",
|
|
58
|
-
"automation"
|
|
62
|
+
"automation",
|
|
63
|
+
"locators",
|
|
64
|
+
"e2e",
|
|
65
|
+
"qa",
|
|
66
|
+
"typescript",
|
|
67
|
+
"datagrid",
|
|
68
|
+
"smart-table",
|
|
69
|
+
"ag-grid",
|
|
70
|
+
"mui"
|
|
59
71
|
],
|
|
60
72
|
"peerDependencies": {
|
|
61
73
|
"@playwright/test": "^1.40.0"
|