@rickcedwhat/playwright-smart-table 6.20.0 → 6.20.1-next.1d11eb1
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/dist/engine/rowFinder.d.ts +1 -0
- package/dist/engine/rowFinder.js +75 -75
- package/dist/engine/scanPages.d.ts +58 -0
- package/dist/engine/scanPages.js +46 -0
- package/dist/engine/tableIteration.d.ts +1 -0
- package/dist/engine/tableIteration.js +38 -20
- package/dist/filterEngine.d.ts +4 -0
- package/dist/filterEngine.js +15 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/plugins/index.d.ts +51 -10
- package/dist/plugins/index.js +14 -6
- package/dist/presets/glide/index.d.ts +1 -1
- package/dist/presets/glide/index.js +2 -2
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +4 -4
- package/dist/smartRow.js +87 -32
- package/dist/strategies/columns.d.ts +5 -9
- package/dist/strategies/columns.js +0 -10
- package/dist/strategies/contentReady.d.ts +20 -0
- package/dist/strategies/contentReady.js +62 -0
- package/dist/strategies/filter.d.ts +0 -4
- package/dist/strategies/filter.js +0 -16
- package/dist/strategies/index.d.ts +23 -15
- package/dist/strategies/index.js +9 -5
- package/dist/strategies/pagination.js +11 -2
- package/dist/strategies/viewport.js +26 -19
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +34 -1
- package/dist/types.d.ts +32 -1
- package/dist/useTable.js +94 -39
- package/dist/utils/elementTracker.js +6 -3
- package/dist/utils/resolveCellLocator.d.ts +35 -0
- package/dist/utils/resolveCellLocator.js +52 -0
- package/package.json +6 -1
package/dist/useTable.js
CHANGED
|
@@ -28,10 +28,10 @@ const tableMapper_1 = require("./engine/tableMapper");
|
|
|
28
28
|
const rowFinder_1 = require("./engine/rowFinder");
|
|
29
29
|
const tableIteration_1 = require("./engine/tableIteration");
|
|
30
30
|
const rowResolution_1 = require("./engine/rowResolution");
|
|
31
|
+
const scanPages_1 = require("./engine/scanPages");
|
|
31
32
|
const debugUtils_1 = require("./utils/debugUtils");
|
|
32
33
|
const smartRowArray_1 = require("./utils/smartRowArray");
|
|
33
34
|
const elementTracker_1 = require("./utils/elementTracker");
|
|
34
|
-
const navigationBarrier_1 = require("./utils/navigationBarrier");
|
|
35
35
|
// Helper to safely serialize objects containing functions for logging
|
|
36
36
|
const safeStringify = (obj) => {
|
|
37
37
|
try {
|
|
@@ -301,7 +301,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
301
301
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
302
302
|
},
|
|
303
303
|
countRows: async (filters, options) => {
|
|
304
|
-
var _a, _b, _c, _d;
|
|
304
|
+
var _a, _b, _c, _d, _e;
|
|
305
305
|
if (tableState.empty)
|
|
306
306
|
return 0;
|
|
307
307
|
await _autoInit();
|
|
@@ -359,6 +359,15 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
359
359
|
}
|
|
360
360
|
return count;
|
|
361
361
|
};
|
|
362
|
+
// Wait for table to finish loading before counting
|
|
363
|
+
const isTableLoading = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.isTableLoading;
|
|
364
|
+
if (isTableLoading) {
|
|
365
|
+
const ctx = createStrategyContext();
|
|
366
|
+
while (await isTableLoading(ctx)) {
|
|
367
|
+
log('countRows: table is loading... waiting');
|
|
368
|
+
await rootLocator.page().waitForTimeout(200);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
362
371
|
if (!hasPagination) {
|
|
363
372
|
log(`countRows: counting rows in current viewport (no pagination)${hasFilters ? ` filters=${safeStringify(filtersRecord)}` : ''}`);
|
|
364
373
|
if (!hasPostFilters)
|
|
@@ -372,19 +381,34 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
372
381
|
let pagesScanned = 1;
|
|
373
382
|
let paginationError;
|
|
374
383
|
try {
|
|
375
|
-
|
|
384
|
+
const scanned = await (0, scanPages_1.scanPages)({
|
|
385
|
+
advancePage: _advancePage,
|
|
386
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
387
|
+
config,
|
|
388
|
+
waitForReady: async () => {
|
|
389
|
+
if (!isTableLoading)
|
|
390
|
+
return;
|
|
391
|
+
const ctx = createStrategyContext();
|
|
392
|
+
while (await isTableLoading(ctx)) {
|
|
393
|
+
log('countRows: table is loading... waiting');
|
|
394
|
+
await rootLocator.page().waitForTimeout(200);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
}, async ({ pagesScanned: pageNum }) => {
|
|
376
398
|
const rowLocators = resolveRows();
|
|
377
399
|
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
378
400
|
const candidates = await rowLocators.all();
|
|
379
401
|
const matched = await countPostFilterMatches(candidates, newIndices);
|
|
380
402
|
total += matched;
|
|
381
|
-
log(`countRows: page ${
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
403
|
+
log(`countRows: page ${pageNum} — ${matched} row(s) (running total: ${total})`);
|
|
404
|
+
return 'continue';
|
|
405
|
+
}, {
|
|
406
|
+
maxPages: effectiveMaxPages,
|
|
407
|
+
useBulk: false,
|
|
408
|
+
label: 'countRows',
|
|
409
|
+
finalScanOnEof: true,
|
|
410
|
+
});
|
|
411
|
+
pagesScanned = scanned.pagesScanned;
|
|
388
412
|
}
|
|
389
413
|
catch (e) {
|
|
390
414
|
paginationError = e;
|
|
@@ -428,6 +452,10 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
428
452
|
return (text || '').trim();
|
|
429
453
|
}, options);
|
|
430
454
|
},
|
|
455
|
+
/**
|
|
456
|
+
* @deprecated Use `mapColumn` (or `map`) instead. Will be removed in v7.0.0.
|
|
457
|
+
* Iterates over rows and extracts the value of a single column as strings.
|
|
458
|
+
*/
|
|
431
459
|
getColumnValues: async (columnName, options = {}) => {
|
|
432
460
|
log(`getColumnValues: column="${columnName}" options=${safeStringify(options)}`);
|
|
433
461
|
const values = await result.mapColumn(columnName, options);
|
|
@@ -463,6 +491,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
463
491
|
throw new Error(`getRow() cannot filter by synthetic column(s): ${syntheticKeys.join(', ')}. ` +
|
|
464
492
|
`Use findRow() instead — synthetic columns require async evaluation.`);
|
|
465
493
|
}
|
|
494
|
+
const overrideKeys = Object.keys(filters).filter(k => { var _a, _b; return (_b = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[k]) === null || _b === void 0 ? void 0 : _b.read; });
|
|
495
|
+
if (overrideKeys.length > 0) {
|
|
496
|
+
throw new Error(`getRow() cannot filter by columnOverrides.read column(s): ${overrideKeys.join(', ')}. ` +
|
|
497
|
+
`Use findRow() instead — override columns require async evaluation.`);
|
|
498
|
+
}
|
|
466
499
|
const map = tableMapper.getMapSync();
|
|
467
500
|
if (!map)
|
|
468
501
|
throw new Error('Initialization Error: You attempted to access a row before the table structure was mapped. Please call "await table.init()" once before using synchronous row access.');
|
|
@@ -597,43 +630,65 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
597
630
|
}
|
|
598
631
|
},
|
|
599
632
|
// ─── Shared async row iterator ───────────────────────────────────────────
|
|
633
|
+
/**
|
|
634
|
+
* Streams rows using the same page-walk + collection path as `map` / `forEach`
|
|
635
|
+
* (`scanPages` + runMap): overscan filtering, loading-before-dedupe, EOF final
|
|
636
|
+
* scan, and config dedupe. Concurrency is sequential so yields stay ordered. (#427)
|
|
637
|
+
*/
|
|
600
638
|
[Symbol.asyncIterator]() {
|
|
601
639
|
return __asyncGenerator(this, arguments, function* _a() {
|
|
602
|
-
var _b;
|
|
603
640
|
yield __await(_autoInit());
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
641
|
+
log(`iterator: starting via runMap (maxPages=${config.maxPages})`);
|
|
642
|
+
const queue = [];
|
|
643
|
+
let notify;
|
|
644
|
+
let release;
|
|
645
|
+
let finished = false;
|
|
646
|
+
let cancelled = false;
|
|
647
|
+
let runError;
|
|
648
|
+
const wake = () => {
|
|
649
|
+
notify === null || notify === void 0 ? void 0 : notify();
|
|
650
|
+
notify = undefined;
|
|
651
|
+
};
|
|
652
|
+
const runPromise = (0, tableIteration_1.runMap)({
|
|
653
|
+
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
654
|
+
getMap: () => tableMapper.getMapSync(),
|
|
655
|
+
advancePage: _advancePage,
|
|
656
|
+
makeSmartRow: (loc, map, idx, pageIdx, barrier, sel) => _makeSmart(loc, map, idx, pageIdx, barrier, sel),
|
|
657
|
+
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
658
|
+
config,
|
|
659
|
+
getPage: () => rootLocator.page(),
|
|
660
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
661
|
+
getContext: () => createStrategyContext(),
|
|
662
|
+
}, async (ctx) => {
|
|
663
|
+
queue.push({
|
|
664
|
+
row: ctx.row,
|
|
665
|
+
index: ctx.index,
|
|
666
|
+
rowIndex: ctx.rowIndex,
|
|
667
|
+
pageIndex: ctx.pageIndex,
|
|
668
|
+
});
|
|
669
|
+
wake();
|
|
670
|
+
await new Promise((resolve) => { release = resolve; });
|
|
671
|
+
if (cancelled)
|
|
672
|
+
ctx.stop();
|
|
673
|
+
}, { concurrency: 'sequential' }, 'iterator').then(() => { finished = true; wake(); }, (err) => { runError = err; finished = true; wake(); });
|
|
609
674
|
try {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
// index = visit-order counter; rowIndex = logical/data index (B-hybrid, #362).
|
|
619
|
-
const resolved = yield __await((0, rowResolution_1.resolveLogicalRowIndex)(pageRows[idx], config, () => rowIndex));
|
|
620
|
-
const logical = (_b = resolved === null || resolved === void 0 ? void 0 : resolved.index) !== null && _b !== void 0 ? _b : rowIndex;
|
|
621
|
-
const sr = _makeSmart(pageRows[idx], map, logical, tableState.currentPageIndex, barrier, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
|
|
622
|
-
// Iterator rows share one per-page DOM snapshot (positional locators); disable
|
|
623
|
-
// toJSON's scroll-back recovery so reading one row can't invalidate the others (#366).
|
|
624
|
-
sr._inBatch = true;
|
|
625
|
-
yield yield __await({ row: sr, index: rowIndex, rowIndex: logical, pageIndex: tableState.currentPageIndex });
|
|
626
|
-
rowIndex++;
|
|
675
|
+
while (!finished || queue.length > 0) {
|
|
676
|
+
if (queue.length === 0) {
|
|
677
|
+
yield __await(new Promise((resolve) => { notify = resolve; }));
|
|
678
|
+
}
|
|
679
|
+
while (queue.length > 0) {
|
|
680
|
+
yield yield __await(queue.shift());
|
|
681
|
+
release === null || release === void 0 ? void 0 : release();
|
|
682
|
+
release = undefined;
|
|
627
683
|
}
|
|
628
|
-
if (
|
|
629
|
-
|
|
630
|
-
if (!(yield __await(_advancePage(useBulk))))
|
|
631
|
-
break;
|
|
632
|
-
pagesScanned++;
|
|
684
|
+
if (runError)
|
|
685
|
+
throw runError;
|
|
633
686
|
}
|
|
634
687
|
}
|
|
635
688
|
finally {
|
|
636
|
-
|
|
689
|
+
cancelled = true;
|
|
690
|
+
release === null || release === void 0 ? void 0 : release();
|
|
691
|
+
yield __await(runPromise.catch(() => { }));
|
|
637
692
|
}
|
|
638
693
|
});
|
|
639
694
|
},
|
|
@@ -18,7 +18,8 @@ class ElementTracker {
|
|
|
18
18
|
const seenMap = win[trackerId];
|
|
19
19
|
const newIndices = [];
|
|
20
20
|
elements.forEach((el, index) => {
|
|
21
|
-
const
|
|
21
|
+
const htmlEl = el;
|
|
22
|
+
const signature = htmlEl.offsetTop + '|' + (el.textContent || '');
|
|
22
23
|
if (seenMap.get(el) !== signature) {
|
|
23
24
|
newIndices.push(index);
|
|
24
25
|
}
|
|
@@ -39,8 +40,10 @@ class ElementTracker {
|
|
|
39
40
|
const seenMap = win[trackerId];
|
|
40
41
|
for (const index of indicesToCommit) {
|
|
41
42
|
const el = elements[index];
|
|
42
|
-
if (el)
|
|
43
|
-
|
|
43
|
+
if (el) {
|
|
44
|
+
const htmlEl = el;
|
|
45
|
+
seenMap.set(el, htmlEl.offsetTop + '|' + (el.textContent || ''));
|
|
46
|
+
}
|
|
44
47
|
}
|
|
45
48
|
}, [this.id, indices]);
|
|
46
49
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import type { FinalTableConfig, Selector } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a cell locator for a concrete row — prefers `strategies.getCellLocator`,
|
|
5
|
+
* otherwise `cellSelector` + `.nth(columnIndex)`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveCellLocator(args: {
|
|
8
|
+
config: FinalTableConfig;
|
|
9
|
+
resolve: (selector: Selector, parent: Locator | Page) => Locator;
|
|
10
|
+
row: Locator;
|
|
11
|
+
root: Locator;
|
|
12
|
+
columnName: string;
|
|
13
|
+
columnIndex: number;
|
|
14
|
+
rowIndex?: number;
|
|
15
|
+
page?: Page;
|
|
16
|
+
}): Locator;
|
|
17
|
+
/**
|
|
18
|
+
* Cell locator for Playwright `rows.filter({ has })`.
|
|
19
|
+
*
|
|
20
|
+
* When `getCellLocator` is set, pass `page.locator(':scope')` as the row so the
|
|
21
|
+
* strategy's `row.locator(...)` chain stays relative to each candidate row under
|
|
22
|
+
* `filter({ has })`. Using the rows collection or a document-rooted parent nests
|
|
23
|
+
* the row selector and matches nothing.
|
|
24
|
+
*
|
|
25
|
+
* Without `getCellLocator`, keeps the historical page-scoped `cellSelector.nth(i)`
|
|
26
|
+
* template that Playwright re-bases into each row.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveCellLocatorForFilter(args: {
|
|
29
|
+
config: FinalTableConfig;
|
|
30
|
+
resolve: (selector: Selector, parent: Locator | Page) => Locator;
|
|
31
|
+
page: Page;
|
|
32
|
+
root?: Locator;
|
|
33
|
+
columnName: string;
|
|
34
|
+
columnIndex: number;
|
|
35
|
+
}): Locator;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCellLocator = resolveCellLocator;
|
|
4
|
+
exports.resolveCellLocatorForFilter = resolveCellLocatorForFilter;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a cell locator for a concrete row — prefers `strategies.getCellLocator`,
|
|
7
|
+
* otherwise `cellSelector` + `.nth(columnIndex)`.
|
|
8
|
+
*/
|
|
9
|
+
function resolveCellLocator(args) {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
const { config, resolve, row, root, columnName, columnIndex, rowIndex } = args;
|
|
12
|
+
const page = (_a = args.page) !== null && _a !== void 0 ? _a : root.page();
|
|
13
|
+
if ((_b = config.strategies) === null || _b === void 0 ? void 0 : _b.getCellLocator) {
|
|
14
|
+
return config.strategies.getCellLocator({
|
|
15
|
+
row,
|
|
16
|
+
root,
|
|
17
|
+
columnName,
|
|
18
|
+
columnIndex,
|
|
19
|
+
rowIndex,
|
|
20
|
+
page,
|
|
21
|
+
config,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return resolve(config.cellSelector, row).nth(columnIndex);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cell locator for Playwright `rows.filter({ has })`.
|
|
28
|
+
*
|
|
29
|
+
* When `getCellLocator` is set, pass `page.locator(':scope')` as the row so the
|
|
30
|
+
* strategy's `row.locator(...)` chain stays relative to each candidate row under
|
|
31
|
+
* `filter({ has })`. Using the rows collection or a document-rooted parent nests
|
|
32
|
+
* the row selector and matches nothing.
|
|
33
|
+
*
|
|
34
|
+
* Without `getCellLocator`, keeps the historical page-scoped `cellSelector.nth(i)`
|
|
35
|
+
* template that Playwright re-bases into each row.
|
|
36
|
+
*/
|
|
37
|
+
function resolveCellLocatorForFilter(args) {
|
|
38
|
+
var _a;
|
|
39
|
+
const { config, resolve, page, root, columnName, columnIndex } = args;
|
|
40
|
+
if ((_a = config.strategies) === null || _a === void 0 ? void 0 : _a.getCellLocator) {
|
|
41
|
+
const scopeRow = page.locator(':scope');
|
|
42
|
+
return config.strategies.getCellLocator({
|
|
43
|
+
row: scopeRow,
|
|
44
|
+
root: root !== null && root !== void 0 ? root : scopeRow,
|
|
45
|
+
columnName,
|
|
46
|
+
columnIndex,
|
|
47
|
+
page,
|
|
48
|
+
config,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return resolve(config.cellSelector, page).nth(columnIndex);
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "6.20.
|
|
3
|
+
"version": "6.20.1-next.1d11eb1",
|
|
4
4
|
"description": "Smart, column-aware table interactions for Playwright",
|
|
5
5
|
"author": "Cedrick Catalan",
|
|
6
6
|
"license": "MIT",
|
|
@@ -110,6 +110,11 @@
|
|
|
110
110
|
"peerDependencies": {
|
|
111
111
|
"@playwright/test": "^1.40.0"
|
|
112
112
|
},
|
|
113
|
+
"peerDependenciesMeta": {
|
|
114
|
+
"@playwright/test": {
|
|
115
|
+
"optional": true
|
|
116
|
+
}
|
|
117
|
+
},
|
|
113
118
|
"devDependencies": {
|
|
114
119
|
"@commitlint/cli": "^21.0.0",
|
|
115
120
|
"@commitlint/config-conventional": "^21.0.0",
|