@rickcedwhat/playwright-smart-table 6.25.0-next.49ce2de → 6.25.0-next.4d4c252
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 +326 -0
- package/dist/engine/countRows.d.ts +1 -0
- package/dist/engine/countRows.js +18 -8
- package/dist/engine/rowByIndex.d.ts +1 -0
- package/dist/engine/rowByIndex.js +12 -3
- package/dist/engine/rowFinder.d.ts +2 -0
- package/dist/engine/rowFinder.js +2 -0
- package/dist/engine/rowSnapshot.d.ts +28 -0
- package/dist/engine/rowSnapshot.js +407 -0
- package/dist/engine/scanPages.d.ts +5 -0
- package/dist/engine/scanPages.js +11 -0
- package/dist/engine/tableIteration.js +1 -0
- 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/index.d.ts +5 -0
- package/dist/types.d.ts +24 -5
- package/dist/utils/navigationBarrier.js +0 -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
|
*/
|
|
@@ -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
|
@@ -428,6 +428,8 @@ export interface PaginationPrimitives {
|
|
|
428
428
|
export type PaginationStrategy = PaginationPrimitives;
|
|
429
429
|
export type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;
|
|
430
430
|
export type ContentReadyStrategy = (row: Locator, page: Page) => Promise<void>;
|
|
431
|
+
/** 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`. */
|
|
432
|
+
export type RowReadyPredicate<Arg = undefined> = (row: HTMLElement | SVGElement, arg: Arg) => unknown;
|
|
431
433
|
export type FillStrategy = (options: {
|
|
432
434
|
row: SmartRow;
|
|
433
435
|
columnName: string;
|
|
@@ -638,12 +640,18 @@ export interface TableStrategies {
|
|
|
638
640
|
* Without this, the clone captures stale content from the previous occupant of the
|
|
639
641
|
* DOM slot — the element is at the correct position but React hasn't re-rendered yet.
|
|
640
642
|
*
|
|
641
|
-
*
|
|
643
|
+
* Built-ins: `Strategies.ContentReady.textStable()` (text stops changing),
|
|
644
|
+
* `mutationSettled()` (no DOM mutations for a quiet period), and `until(predicate)`
|
|
645
|
+
* (an in-browser condition holds, e.g. no skeleton placeholder).
|
|
642
646
|
*
|
|
643
647
|
* @example
|
|
644
648
|
* strategies: {
|
|
645
649
|
* contentReady: Strategies.ContentReady.textStable({ timeout: 500 }),
|
|
646
650
|
* }
|
|
651
|
+
* @example
|
|
652
|
+
* strategies: {
|
|
653
|
+
* contentReady: Strategies.ContentReady.until(row => !row.querySelector('.skeleton')),
|
|
654
|
+
* }
|
|
647
655
|
*/
|
|
648
656
|
contentReady?: ContentReadyStrategy;
|
|
649
657
|
/**
|
|
@@ -794,6 +802,11 @@ export type RowIterationOptions = {
|
|
|
794
802
|
* @default false — uses goNext for one-page-at-a-time advancement
|
|
795
803
|
*/
|
|
796
804
|
useBulkPagination?: boolean;
|
|
805
|
+
/**
|
|
806
|
+
* Cancels the iteration between pages. When aborted, the call rejects with
|
|
807
|
+
* `signal.reason` (rows already processed are not returned).
|
|
808
|
+
*/
|
|
809
|
+
signal?: AbortSignal;
|
|
797
810
|
};
|
|
798
811
|
/**
|
|
799
812
|
* Result of {@link useTable}. Implements `AsyncIterable` — `for await (const { row } of table)`
|
|
@@ -876,31 +889,35 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
876
889
|
* is not configured. Also throws if the row cannot be reached (no silent wrong-row fallback).
|
|
877
890
|
*
|
|
878
891
|
* @param index 0-based logical/data-model row index
|
|
879
|
-
* @param options - `maxPages` bounds how far to scroll/paginate (defaults to config.maxPages)
|
|
892
|
+
* @param options - `maxPages` bounds how far to scroll/paginate (defaults to config.maxPages).
|
|
893
|
+
* `signal` cancels the page scan; the call rejects with `signal.reason`.
|
|
880
894
|
*/
|
|
881
895
|
findRowByIndex: (index: number, options?: {
|
|
882
896
|
maxPages?: number;
|
|
897
|
+
signal?: AbortSignal;
|
|
883
898
|
}) => Promise<SmartRow<T>>;
|
|
884
899
|
/**
|
|
885
900
|
* ASYNC: Searches for a single row across pages using pagination.
|
|
886
901
|
* Auto-initializes the table if not already initialized.
|
|
887
902
|
* @param filters - The filter criteria to match
|
|
888
|
-
* @param options - Search options including exact match and max pages
|
|
903
|
+
* @param options - Search options including exact match and max pages. `signal` cancels the page scan; the call rejects with `signal.reason`.
|
|
889
904
|
*/
|
|
890
905
|
findRow: (filters: Record<string, FilterValue>, options?: {
|
|
891
906
|
exact?: boolean;
|
|
892
907
|
maxPages?: number;
|
|
908
|
+
signal?: AbortSignal;
|
|
893
909
|
}) => Promise<SmartRow<T>>;
|
|
894
910
|
/**
|
|
895
911
|
* ASYNC: Searches for all matching rows across pages using pagination.
|
|
896
912
|
* Auto-initializes the table if not already initialized.
|
|
897
913
|
* @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).
|
|
914
|
+
* @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
915
|
*/
|
|
900
916
|
findRows: (filters?: Record<string, FilterValue>, options?: {
|
|
901
917
|
exact?: boolean;
|
|
902
918
|
maxPages?: number;
|
|
903
919
|
useBulkPagination?: boolean;
|
|
920
|
+
signal?: AbortSignal;
|
|
904
921
|
}) => Promise<SmartRowArray<T>>;
|
|
905
922
|
/**
|
|
906
923
|
* Resolves the named column and scrolls it into view.
|
|
@@ -911,11 +928,13 @@ export interface TableResult<T = any> extends AsyncIterable<{
|
|
|
911
928
|
/**
|
|
912
929
|
* Counts the number of rows, optionally filtered.
|
|
913
930
|
* Without arguments, counts all rows. With filters, counts only matching rows.
|
|
914
|
-
* Paginates when pagination is configured.
|
|
931
|
+
* Paginates when pagination is configured. `signal` cancels the page scan; the call
|
|
932
|
+
* rejects with `signal.reason` (the table is still reset to page 1).
|
|
915
933
|
*/
|
|
916
934
|
countRows: (filters?: Record<string, FilterValue>, options?: {
|
|
917
935
|
exact?: boolean;
|
|
918
936
|
maxPages?: number;
|
|
937
|
+
signal?: AbortSignal;
|
|
919
938
|
}) => Promise<number>;
|
|
920
939
|
/**
|
|
921
940
|
* Iterates over rows and extracts the value of a single column.
|
|
@@ -51,7 +51,6 @@ class NavigationBarrier {
|
|
|
51
51
|
* The fn receives a fresh `targetReached` check; if the previous row's scroll
|
|
52
52
|
* already brought this row into view, fn can no-op immediately.
|
|
53
53
|
*/
|
|
54
|
-
// fallow-ignore-next-line unused-class-member
|
|
55
54
|
async runExclusiveRecovery(fn) {
|
|
56
55
|
if (!this.recoveryMutex)
|
|
57
56
|
this.recoveryMutex = new mutex_1.Mutex();
|
package/package.json
CHANGED