@rickcedwhat/playwright-smart-table 6.25.0-next.c784f2c → 6.25.0-next.d8860d0
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/cellNavigation.d.ts +24 -0
- package/dist/engine/cellNavigation.js +329 -0
- package/dist/engine/countRows.d.ts +28 -0
- package/dist/engine/countRows.js +149 -0
- package/dist/engine/rowByIndex.d.ts +20 -0
- package/dist/engine/rowByIndex.js +79 -0
- package/dist/engine/rowFinder.d.ts +3 -1
- package/dist/engine/rowFinder.js +5 -7
- package/dist/engine/rowResolution.d.ts +2 -2
- package/dist/engine/rowResolution.js +11 -7
- package/dist/engine/rowSnapshot.d.ts +28 -0
- package/dist/engine/rowSnapshot.js +422 -0
- package/dist/engine/scanPages.d.ts +7 -2
- package/dist/engine/scanPages.js +11 -0
- package/dist/engine/sorting.d.ts +11 -0
- package/dist/engine/sorting.js +51 -0
- package/dist/engine/tableIteration.js +1 -0
- package/dist/engine/tableMapper.js +16 -5
- package/dist/index.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/smartRow.js +12 -692
- package/dist/strategies/columns.d.ts +1 -1
- package/dist/strategies/contentReady.d.ts +19 -1
- package/dist/strategies/contentReady.js +81 -0
- package/dist/strategies/dedupe.d.ts +5 -2
- package/dist/strategies/dedupe.js +5 -2
- package/dist/strategies/index.d.ts +5 -0
- package/dist/types.d.ts +36 -8
- package/dist/useTable.js +41 -289
- package/dist/utils/deadline.d.ts +2 -0
- package/dist/utils/deadline.js +19 -0
- package/dist/utils/memoizeViewport.d.ts +6 -0
- package/dist/utils/memoizeViewport.js +28 -0
- package/dist/utils/navigationBarrier.js +0 -1
- package/dist/utils/safeStringify.d.ts +2 -0
- package/dist/utils/safeStringify.js +18 -0
- package/dist/utils/smartRowArray.d.ts +1 -1
- package/dist/utils/smartRowArray.js +4 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import type { Locator } from '@playwright/test';
|
|
|
2
2
|
import { StrategyContext } from '../types';
|
|
3
3
|
/**
|
|
4
4
|
* Primitive navigation functions that define HOW to move within a table.
|
|
5
|
-
* The orchestration logic (WHEN to move) lives in
|
|
5
|
+
* The orchestration logic (WHEN to move) lives in navigateToCell (src/engine/cellNavigation.ts).
|
|
6
6
|
*/
|
|
7
7
|
export interface NavigationPrimitives {
|
|
8
8
|
goUp?: (context: StrategyContext) => Promise<void>;
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import type { ContentReadyStrategy } from '../types';
|
|
1
|
+
import type { ContentReadyStrategy, RowReadyPredicate } from '../types';
|
|
2
2
|
export declare const ContentReadyStrategies: {
|
|
3
|
+
/**
|
|
4
|
+
* Waits in the browser until `predicate(rowElement, arg)` returns a truthy value, e.g. until
|
|
5
|
+
* a skeleton placeholder is gone or every cell has rendered. Resolves when `timeout` expires
|
|
6
|
+
* even if the predicate never passed, like the other content-ready strategies.
|
|
7
|
+
*
|
|
8
|
+
* Uses `locator.waitForFunction` (Playwright 1.62+), which re-resolves the row on each retry
|
|
9
|
+
* and polls without a round-trip per check. Older versions poll via `locator.evaluate`.
|
|
10
|
+
* `timeout` and `interval` must be positive finite milliseconds. `interval` only controls
|
|
11
|
+
* the older-version fallback; native polling is controlled by Playwright.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* contentReady: Strategies.ContentReady.until(row => !row.querySelector('.skeleton'))
|
|
15
|
+
*/
|
|
16
|
+
until: <Arg = undefined>(predicate: RowReadyPredicate<Arg>, options?: {
|
|
17
|
+
arg?: Arg;
|
|
18
|
+
timeout?: number;
|
|
19
|
+
interval?: number;
|
|
20
|
+
}) => ContentReadyStrategy;
|
|
3
21
|
/**
|
|
4
22
|
* Polls the row's text content until two consecutive reads match.
|
|
5
23
|
*/
|
|
@@ -1,7 +1,88 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ContentReadyStrategies = void 0;
|
|
4
|
+
function isTimeout(error) {
|
|
5
|
+
return (error === null || error === void 0 ? void 0 : error.name) === 'TimeoutError';
|
|
6
|
+
}
|
|
7
|
+
/** Playwright < 1.62 has no `locator.waitForFunction`: poll with one `evaluate` per check. */
|
|
8
|
+
async function pollPredicate(row, page, predicate, arg, timeout, interval) {
|
|
9
|
+
const deadline = Date.now() + timeout;
|
|
10
|
+
let source = predicate.toString();
|
|
11
|
+
// Build a serializable callback; the user predicate executes only inside the browser.
|
|
12
|
+
const wrap = (expression) => new Function(`return async (row, arg) => Boolean(await (${expression})(row, arg))`)();
|
|
13
|
+
let evaluateBoolean;
|
|
14
|
+
try {
|
|
15
|
+
evaluateBoolean = wrap(source);
|
|
16
|
+
}
|
|
17
|
+
catch (_a) {
|
|
18
|
+
// Like Playwright's serializer, normalize method shorthand into a function expression.
|
|
19
|
+
source = source.startsWith('async ') ? source.replace(/^async\s+/, 'async function ') : `function ${source}`;
|
|
20
|
+
evaluateBoolean = wrap(source);
|
|
21
|
+
}
|
|
22
|
+
let expired = false;
|
|
23
|
+
let deadlineTimer;
|
|
24
|
+
const timeoutReached = new Promise(resolve => {
|
|
25
|
+
deadlineTimer = setTimeout(() => { expired = true; resolve(); }, timeout);
|
|
26
|
+
});
|
|
27
|
+
const poll = async () => {
|
|
28
|
+
while (!expired && Date.now() < deadline) {
|
|
29
|
+
// Convert truthiness before serialization, including async DOM-element results.
|
|
30
|
+
if (await row.evaluate(evaluateBoolean, arg, { timeout: Math.max(1, deadline - Date.now()) }))
|
|
31
|
+
return;
|
|
32
|
+
if (expired || Date.now() >= deadline)
|
|
33
|
+
return;
|
|
34
|
+
await page.waitForTimeout(Math.min(interval, Math.max(0, deadline - Date.now())));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
// evaluate's timeout only bounds locator resolution, not an async predicate's promise.
|
|
39
|
+
await Promise.race([poll(), timeoutReached]);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(deadlineTimer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
4
45
|
exports.ContentReadyStrategies = {
|
|
46
|
+
/**
|
|
47
|
+
* Waits in the browser until `predicate(rowElement, arg)` returns a truthy value, e.g. until
|
|
48
|
+
* a skeleton placeholder is gone or every cell has rendered. Resolves when `timeout` expires
|
|
49
|
+
* even if the predicate never passed, like the other content-ready strategies.
|
|
50
|
+
*
|
|
51
|
+
* Uses `locator.waitForFunction` (Playwright 1.62+), which re-resolves the row on each retry
|
|
52
|
+
* and polls without a round-trip per check. Older versions poll via `locator.evaluate`.
|
|
53
|
+
* `timeout` and `interval` must be positive finite milliseconds. `interval` only controls
|
|
54
|
+
* the older-version fallback; native polling is controlled by Playwright.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* contentReady: Strategies.ContentReady.until(row => !row.querySelector('.skeleton'))
|
|
58
|
+
*/
|
|
59
|
+
until: (predicate, options = {}) => {
|
|
60
|
+
var _a, _b;
|
|
61
|
+
const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 500;
|
|
62
|
+
const interval = (_b = options.interval) !== null && _b !== void 0 ? _b : 50;
|
|
63
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
64
|
+
throw new Error('ContentReady.until: timeout must be a positive finite number');
|
|
65
|
+
}
|
|
66
|
+
if (!Number.isFinite(interval) || interval <= 0) {
|
|
67
|
+
throw new Error('ContentReady.until: interval must be a positive finite number');
|
|
68
|
+
}
|
|
69
|
+
const fn = predicate;
|
|
70
|
+
const arg = options.arg;
|
|
71
|
+
return async (row, page) => {
|
|
72
|
+
try {
|
|
73
|
+
if (typeof row.waitForFunction === 'function') {
|
|
74
|
+
await row.waitForFunction(fn, arg, { timeout });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
await pollPredicate(row, page, fn, arg, timeout, interval);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (!isTimeout(error))
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
},
|
|
5
86
|
/**
|
|
6
87
|
* Polls the row's text content until two consecutive reads match.
|
|
7
88
|
*/
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { DedupeStrategy } from '../types';
|
|
2
2
|
export declare const DedupeStrategies: {
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Groups rows by their viewport Y coordinate, rounded to a pixel bucket.
|
|
5
|
+
* Only applicable when each screen position represents the same record for the entire scan.
|
|
6
|
+
* Scrolling or pagination can place different records at the same position and silently drop them.
|
|
7
|
+
* @deprecated Prefer a stable record key, e.g. `async row => row.getCell('ID').innerText()`.
|
|
8
|
+
* Do not use for recycled virtualized rows or paginated tables.
|
|
6
9
|
* @param tolerance Pixel tolerance for position comparison (default: 2)
|
|
7
10
|
*/
|
|
8
11
|
byTopPosition: (tolerance?: number) => DedupeStrategy;
|
|
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DedupeStrategies = void 0;
|
|
4
4
|
exports.DedupeStrategies = {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Groups rows by their viewport Y coordinate, rounded to a pixel bucket.
|
|
7
|
+
* Only applicable when each screen position represents the same record for the entire scan.
|
|
8
|
+
* Scrolling or pagination can place different records at the same position and silently drop them.
|
|
9
|
+
* @deprecated Prefer a stable record key, e.g. `async row => row.getCell('ID').innerText()`.
|
|
10
|
+
* Do not use for recycled virtualized rows or paginated tables.
|
|
8
11
|
* @param tolerance Pixel tolerance for position comparison (default: 2)
|
|
9
12
|
*/
|
|
10
13
|
byTopPosition: (tolerance = 2) => async (row) => {
|
|
@@ -98,6 +98,11 @@ export declare const Strategies: {
|
|
|
98
98
|
dataAttribute: (options?: import("./viewport").DataAttributeViewportOptions) => import("..").ViewportStrategy;
|
|
99
99
|
};
|
|
100
100
|
ContentReady: {
|
|
101
|
+
until: <Arg = undefined>(predicate: import("..").RowReadyPredicate<Arg>, options?: {
|
|
102
|
+
arg?: Arg;
|
|
103
|
+
timeout?: number;
|
|
104
|
+
interval?: number;
|
|
105
|
+
}) => import("..").ContentReadyStrategy;
|
|
101
106
|
textStable: (options?: {
|
|
102
107
|
timeout?: number;
|
|
103
108
|
interval?: number;
|
package/dist/types.d.ts
CHANGED
|
@@ -210,9 +210,9 @@ export interface ViewportStrategy {
|
|
|
210
210
|
scrollToColumn?: (context: TableContext, colIndex: number) => Promise<void>;
|
|
211
211
|
/**
|
|
212
212
|
* When true, disables the automatic memoization of getVisibleColumnRange and
|
|
213
|
-
* getVisibleRowRange results
|
|
214
|
-
*
|
|
215
|
-
*
|
|
213
|
+
* getVisibleRowRange results within a single cell-navigation operation. Geometry
|
|
214
|
+
* is refreshed for each new read and around scroll calls. Row membership is not
|
|
215
|
+
* memoized by the library. Caches never persist across scans, resets, or revalidation.
|
|
216
216
|
*/
|
|
217
217
|
disableCache?: boolean;
|
|
218
218
|
}
|
|
@@ -426,8 +426,15 @@ export interface PaginationPrimitives {
|
|
|
426
426
|
detectCurrentPage?: (root: import('@playwright/test').Locator) => number | Promise<number>;
|
|
427
427
|
}
|
|
428
428
|
export type PaginationStrategy = PaginationPrimitives;
|
|
429
|
+
/**
|
|
430
|
+
* Returns a stable record key across the entire scan, including page advances.
|
|
431
|
+
* Different records must have different keys; viewport coordinates and mounted DOM indices
|
|
432
|
+
* are not stable identities in recycled or paginated tables.
|
|
433
|
+
*/
|
|
429
434
|
export type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;
|
|
430
435
|
export type ContentReadyStrategy = (row: Locator, page: Page) => Promise<void>;
|
|
436
|
+
/** Predicate for `Strategies.ContentReady.until`. Runs in the browser with the row element, so it must not close over test-side variables; pass values via `arg`. */
|
|
437
|
+
export type RowReadyPredicate<Arg = undefined> = (row: HTMLElement | SVGElement, arg: Arg) => unknown;
|
|
431
438
|
export type FillStrategy = (options: {
|
|
432
439
|
row: SmartRow;
|
|
433
440
|
columnName: string;
|
|
@@ -524,6 +531,7 @@ export interface LoadingStrategy {
|
|
|
524
531
|
* How long (ms) to wait for a loading row to resolve before applying onRowLoadingTimeout.
|
|
525
532
|
* Honored by findRows AND by map/forEach/filter, where the wait runs before the dedupe
|
|
526
533
|
* strategy so content-based dedupe keys see the row's final loaded state.
|
|
534
|
+
* Includes time spent awaiting the loading predicate, even if it never settles.
|
|
527
535
|
* When not set (backward-compatible behavior): findRows skips loading rows immediately;
|
|
528
536
|
* map/forEach/filter process them as-is.
|
|
529
537
|
*/
|
|
@@ -544,6 +552,7 @@ export interface LoadingStrategy {
|
|
|
544
552
|
isCellLoading?: (cell: import('@playwright/test').Locator, columnName: string, row: SmartRow) => Promise<boolean>;
|
|
545
553
|
/**
|
|
546
554
|
* How long (ms) to wait for a loading cell to resolve before applying onCellLoadingTimeout.
|
|
555
|
+
* Includes predicate evaluation and any initial rendering grace period.
|
|
547
556
|
* When not set and isCellLoading returns true, the cell is read as-is immediately.
|
|
548
557
|
*/
|
|
549
558
|
cellLoadingTimeout?: number;
|
|
@@ -638,12 +647,18 @@ export interface TableStrategies {
|
|
|
638
647
|
* Without this, the clone captures stale content from the previous occupant of the
|
|
639
648
|
* DOM slot — the element is at the correct position but React hasn't re-rendered yet.
|
|
640
649
|
*
|
|
641
|
-
*
|
|
650
|
+
* Built-ins: `Strategies.ContentReady.textStable()` (text stops changing),
|
|
651
|
+
* `mutationSettled()` (no DOM mutations for a quiet period), and `until(predicate)`
|
|
652
|
+
* (an in-browser condition holds, e.g. no skeleton placeholder).
|
|
642
653
|
*
|
|
643
654
|
* @example
|
|
644
655
|
* strategies: {
|
|
645
656
|
* contentReady: Strategies.ContentReady.textStable({ timeout: 500 }),
|
|
646
657
|
* }
|
|
658
|
+
* @example
|
|
659
|
+
* strategies: {
|
|
660
|
+
* contentReady: Strategies.ContentReady.until(row => !row.querySelector('.skeleton')),
|
|
661
|
+
* }
|
|
647
662
|
*/
|
|
648
663
|
contentReady?: ContentReadyStrategy;
|
|
649
664
|
/**
|
|
@@ -794,6 +809,11 @@ export type RowIterationOptions = {
|
|
|
794
809
|
* @default false — uses goNext for one-page-at-a-time advancement
|
|
795
810
|
*/
|
|
796
811
|
useBulkPagination?: boolean;
|
|
812
|
+
/**
|
|
813
|
+
* Cancels the iteration between pages. When aborted, the call rejects with
|
|
814
|
+
* `signal.reason` (rows already processed are not returned).
|
|
815
|
+
*/
|
|
816
|
+
signal?: AbortSignal;
|
|
797
817
|
};
|
|
798
818
|
/**
|
|
799
819
|
* Result of {@link useTable}. Implements `AsyncIterable` — `for await (const { row } of table)`
|
|
@@ -876,31 +896,37 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
876
896
|
* is not configured. Also throws if the row cannot be reached (no silent wrong-row fallback).
|
|
877
897
|
*
|
|
878
898
|
* @param index 0-based logical/data-model row index
|
|
879
|
-
* @param options - `maxPages` bounds how far to scroll/paginate (defaults to config.maxPages)
|
|
899
|
+
* @param options - `maxPages` bounds how far to scroll/paginate (defaults to config.maxPages).
|
|
900
|
+
* `signal` cancels the page scan; the call rejects with `signal.reason`.
|
|
880
901
|
*/
|
|
881
902
|
findRowByIndex: (index: number, options?: {
|
|
882
903
|
maxPages?: number;
|
|
904
|
+
signal?: AbortSignal;
|
|
883
905
|
}) => Promise<SmartRow<T>>;
|
|
884
906
|
/**
|
|
885
907
|
* ASYNC: Searches for a single row across pages using pagination.
|
|
886
908
|
* Auto-initializes the table if not already initialized.
|
|
887
909
|
* @param filters - The filter criteria to match
|
|
888
|
-
* @param options - Search options including exact match and max pages
|
|
910
|
+
* @param options - Search options including exact match and max pages. `signal` cancels the page scan; the call rejects with `signal.reason`.
|
|
889
911
|
*/
|
|
890
912
|
findRow: (filters: Record<string, FilterValue>, options?: {
|
|
891
913
|
exact?: boolean;
|
|
892
914
|
maxPages?: number;
|
|
915
|
+
signal?: AbortSignal;
|
|
893
916
|
}) => Promise<SmartRow<T>>;
|
|
894
917
|
/**
|
|
895
918
|
* ASYNC: Searches for all matching rows across pages using pagination.
|
|
896
919
|
* Auto-initializes the table if not already initialized.
|
|
920
|
+
* Returned rows can be read independently, including with `concurrency: 'synchronized'`.
|
|
921
|
+
* Use synchronized `map()` for coordinated column scrolling across virtualized rows.
|
|
897
922
|
* @param filters - The filter criteria to match (omit or pass {} for all rows)
|
|
898
|
-
* @param options - Search options (exact, maxPages). `useBulkPagination` defaults to `false`: pages advance one at a time via `goNext` so no intermediate page is skipped. Set it to `true` to opt into `goNextBulk` (faster, but skips the rows on jumped-over pages).
|
|
923
|
+
* @param options - Search options (exact, maxPages). `useBulkPagination` defaults to `false`: pages advance one at a time via `goNext` so no intermediate page is skipped. Set it to `true` to opt into `goNextBulk` (faster, but skips the rows on jumped-over pages). `signal` cancels the page scan; the call rejects with `signal.reason`.
|
|
899
924
|
*/
|
|
900
925
|
findRows: (filters?: Record<string, FilterValue>, options?: {
|
|
901
926
|
exact?: boolean;
|
|
902
927
|
maxPages?: number;
|
|
903
928
|
useBulkPagination?: boolean;
|
|
929
|
+
signal?: AbortSignal;
|
|
904
930
|
}) => Promise<SmartRowArray<T>>;
|
|
905
931
|
/**
|
|
906
932
|
* Resolves the named column and scrolls it into view.
|
|
@@ -911,11 +937,13 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
911
937
|
/**
|
|
912
938
|
* Counts the number of rows, optionally filtered.
|
|
913
939
|
* Without arguments, counts all rows. With filters, counts only matching rows.
|
|
914
|
-
* Paginates when pagination is configured.
|
|
940
|
+
* Paginates when pagination is configured. `signal` cancels the page scan; the call
|
|
941
|
+
* rejects with `signal.reason` (the table is still reset to page 1).
|
|
915
942
|
*/
|
|
916
943
|
countRows: (filters?: Record<string, FilterValue>, options?: {
|
|
917
944
|
exact?: boolean;
|
|
918
945
|
maxPages?: number;
|
|
946
|
+
signal?: AbortSignal;
|
|
919
947
|
}) => Promise<number>;
|
|
920
948
|
/**
|
|
921
949
|
* Iterates over rows and extracts the value of a single column.
|