@rickcedwhat/playwright-smart-table 6.20.1-next.b5aa4bd → 6.20.1-next.dbc9618
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.js +41 -60
- 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 +24 -20
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/smartRow.d.ts +2 -2
- package/dist/smartRow.js +73 -17
- package/dist/strategies/headers.d.ts +5 -0
- package/dist/strategies/headers.js +17 -9
- package/dist/strategies/viewport.d.ts +4 -1
- package/dist/strategies/viewport.js +36 -13
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +17 -1
- package/dist/types.d.ts +17 -1
- package/dist/useTable.js +86 -42
- package/package.json +1 -1
package/dist/engine/rowFinder.js
CHANGED
|
@@ -8,6 +8,7 @@ const sentinel_1 = require("../utils/sentinel");
|
|
|
8
8
|
const navigationBarrier_1 = require("../utils/navigationBarrier");
|
|
9
9
|
const rowResolution_1 = require("./rowResolution");
|
|
10
10
|
const resolveCellLocator_1 = require("../utils/resolveCellLocator");
|
|
11
|
+
const scanPages_1 = require("./scanPages");
|
|
11
12
|
class RowFinder {
|
|
12
13
|
constructor(rootLocator, config, resolve, filterEngine, tableMapper, makeSmartRow, tableState = { currentPageIndex: 0 }, advancePage = async () => false) {
|
|
13
14
|
this.rootLocator = rootLocator;
|
|
@@ -141,39 +142,40 @@ class RowFinder {
|
|
|
141
142
|
const map = await this.tableMapper.getMap();
|
|
142
143
|
const allRows = [];
|
|
143
144
|
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
144
|
-
|
|
145
|
+
const useBulk = (options === null || options === void 0 ? void 0 : options.useBulkPagination) === true && !!((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk);
|
|
146
|
+
const hasPagination = !!this.config.strategies.pagination;
|
|
145
147
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: starting (maxPages=${effectiveMaxPages}, filters=${JSON.stringify(filtersRecord)})`);
|
|
146
148
|
const tracker = new elementTracker_1.ElementTracker('findRows');
|
|
147
149
|
try {
|
|
148
|
-
await this.waitForTableReady();
|
|
149
150
|
const { domFilters, overrideFilters, syntheticFilters } = this.splitFilters(filtersRecord);
|
|
150
151
|
const hasOverrideFilters = Object.keys(overrideFilters).length > 0;
|
|
151
152
|
const hasSyntheticFilters = Object.keys(syntheticFilters).length > 0;
|
|
152
|
-
const
|
|
153
|
+
const { pagesScanned } = await (0, scanPages_1.scanPages)({
|
|
154
|
+
advancePage: this.advancePage,
|
|
155
|
+
getCurrentPageIndex: () => this.tableState.currentPageIndex,
|
|
156
|
+
config: this.config,
|
|
157
|
+
waitForReady: () => this.waitForTableReady(),
|
|
158
|
+
}, async () => {
|
|
153
159
|
var _a, _b, _c, _d;
|
|
154
160
|
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
155
161
|
if (Object.keys(domFilters).length > 0) {
|
|
156
162
|
rowLocators = this.filterEngine.applyFilters(rowLocators, domFilters, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page(), this.rootLocator);
|
|
157
163
|
}
|
|
158
|
-
// Get only newly seen matched rows
|
|
159
164
|
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
160
165
|
const currentRows = await rowLocators.all();
|
|
161
166
|
let added = 0;
|
|
162
|
-
// One barrier per batch — synchronizes cell navigation across all rows in this page's results
|
|
163
167
|
const useBarrier = this.config.concurrency === 'synchronized' && newIndices.length > 1;
|
|
164
168
|
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(newIndices.length) : undefined;
|
|
165
169
|
for (const idx of newIndices) {
|
|
166
170
|
const resolved = await (0, rowResolution_1.resolveLogicalRowIndex)(currentRows[idx], this.config, () => allRows.length);
|
|
167
171
|
const smartRow = this.makeSmartRow(currentRows[idx], map, resolved === null || resolved === void 0 ? void 0 : resolved.index, this.tableState.currentPageIndex, barrier, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
|
|
168
|
-
// findRows skips a still-loading row when no timeout is configured (legacy
|
|
169
|
-
// behavior) — see resolveRowLoading's `noTimeoutAction: 'skip'`.
|
|
170
172
|
const loadingOutcome = await (0, rowResolution_1.resolveRowLoading)(smartRow, this.config.strategies.loading, 'skip', (msg) => (0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: ${msg}`));
|
|
171
173
|
if (loadingOutcome !== 'process') {
|
|
172
174
|
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
173
175
|
if (loadingOutcome === 'throw') {
|
|
174
176
|
throw new Error(`[SmartTable] Row ${allRows.length} did not finish loading within ${(_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.rowLoadingTimeout}ms`);
|
|
175
177
|
}
|
|
176
|
-
continue;
|
|
178
|
+
continue;
|
|
177
179
|
}
|
|
178
180
|
if (hasOverrideFilters && !await this.matchesOverrideFilters(currentRows[idx], overrideFilters, map, (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false)) {
|
|
179
181
|
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
@@ -187,44 +189,33 @@ class RowFinder {
|
|
|
187
189
|
added++;
|
|
188
190
|
}
|
|
189
191
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: page ${this.tableState.currentPageIndex} — ${added} new match(es) (total: ${allRows.length})`);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// still falls back to it.
|
|
199
|
-
const useBulk = (options === null || options === void 0 ? void 0 : options.useBulkPagination) === true && !!((_c = this.config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.goNextBulk);
|
|
200
|
-
const prevPage = this.tableState.currentPageIndex;
|
|
201
|
-
const didPaginate = await this.advancePage(useBulk);
|
|
202
|
-
if (!didPaginate) {
|
|
203
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: pagination returned false — final scan`);
|
|
204
|
-
await collectMatches();
|
|
205
|
-
break;
|
|
206
|
-
}
|
|
207
|
-
const pagesJumped = this.tableState.currentPageIndex - prevPage;
|
|
208
|
-
pagesScanned += pagesJumped;
|
|
209
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
210
|
-
await (0, debugUtils_1.debugDelay)(this.config, 'pagination');
|
|
211
|
-
await collectMatches();
|
|
212
|
-
}
|
|
192
|
+
return 'continue';
|
|
193
|
+
}, {
|
|
194
|
+
maxPages: hasPagination ? effectiveMaxPages : 1,
|
|
195
|
+
useBulk,
|
|
196
|
+
label: 'findRows',
|
|
197
|
+
finalScanOnEof: hasPagination,
|
|
198
|
+
});
|
|
199
|
+
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
|
|
213
200
|
}
|
|
214
201
|
finally {
|
|
215
202
|
await tracker.cleanup(this.rootLocator.page());
|
|
216
203
|
}
|
|
217
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: done — ${allRows.length} row(s) collected across ${pagesScanned} page(s)`);
|
|
218
204
|
return (0, smartRowArray_1.createSmartRowArray)(allRows);
|
|
219
205
|
}
|
|
220
206
|
async findRowLocator(filters, options = {}) {
|
|
221
207
|
var _a, _b;
|
|
222
208
|
const map = await this.tableMapper.getMap();
|
|
223
209
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages;
|
|
224
|
-
|
|
210
|
+
const useBulk = options.useBulkPagination === true && !!((_b = this.config.strategies.pagination) === null || _b === void 0 ? void 0 : _b.goNextBulk);
|
|
211
|
+
let found = null;
|
|
225
212
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
226
|
-
|
|
227
|
-
|
|
213
|
+
await (0, scanPages_1.scanPages)({
|
|
214
|
+
advancePage: this.advancePage,
|
|
215
|
+
getCurrentPageIndex: () => this.tableState.currentPageIndex,
|
|
216
|
+
config: this.config,
|
|
217
|
+
waitForReady: () => this.waitForTableReady(),
|
|
218
|
+
}, async () => {
|
|
228
219
|
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
229
220
|
const { domFilters, overrideFilters, syntheticFilters } = this.splitFilters(filters);
|
|
230
221
|
const hasPostFilters = Object.keys(overrideFilters).length > 0 || Object.keys(syntheticFilters).length > 0;
|
|
@@ -237,8 +228,10 @@ class RowFinder {
|
|
|
237
228
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
|
|
238
229
|
if (count > 1)
|
|
239
230
|
await this.throwIfAmbiguous(await matchedRows.all(), filters, map);
|
|
240
|
-
if (count === 1)
|
|
241
|
-
|
|
231
|
+
if (count === 1) {
|
|
232
|
+
found = matchedRows.first();
|
|
233
|
+
return 'stop';
|
|
234
|
+
}
|
|
242
235
|
}
|
|
243
236
|
else {
|
|
244
237
|
const candidates = await matchedRows.all();
|
|
@@ -254,31 +247,19 @@ class RowFinder {
|
|
|
254
247
|
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: ${postFilterMatches.length} match(es) after post-filter`);
|
|
255
248
|
if (postFilterMatches.length > 1)
|
|
256
249
|
await this.throwIfAmbiguous(postFilterMatches, filters, map);
|
|
257
|
-
if (postFilterMatches.length === 1)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (pagesScanned < effectiveMaxPages) {
|
|
261
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: Not found. Attempting pagination...`);
|
|
262
|
-
// Default to single-step goNext; bulk is opt-in via useBulkPagination: true (#349).
|
|
263
|
-
// Bulk-by-default made findRow jump past the page holding the target row.
|
|
264
|
-
const useBulk = options.useBulkPagination === true && !!((_b = this.config.strategies.pagination) === null || _b === void 0 ? void 0 : _b.goNextBulk);
|
|
265
|
-
const prevPage = this.tableState.currentPageIndex;
|
|
266
|
-
const didLoadMore = await this.advancePage(useBulk);
|
|
267
|
-
if (didLoadMore) {
|
|
268
|
-
const pagesJumped = this.tableState.currentPageIndex - prevPage;
|
|
269
|
-
pagesScanned += pagesJumped;
|
|
270
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', `findRowLocator: advanced ${pagesJumped} page(s), now at page ${this.tableState.currentPageIndex}`);
|
|
271
|
-
await (0, debugUtils_1.debugDelay)(this.config, 'pagination');
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
else {
|
|
275
|
-
(0, debugUtils_1.logDebug)(this.config, 'verbose', `Page ${this.tableState.currentPageIndex}: pagination returned false — final scan`);
|
|
276
|
-
pagesScanned = effectiveMaxPages;
|
|
277
|
-
continue;
|
|
250
|
+
if (postFilterMatches.length === 1) {
|
|
251
|
+
found = postFilterMatches[0];
|
|
252
|
+
return 'stop';
|
|
278
253
|
}
|
|
279
254
|
}
|
|
280
|
-
return
|
|
281
|
-
}
|
|
255
|
+
return 'continue';
|
|
256
|
+
}, {
|
|
257
|
+
maxPages: effectiveMaxPages,
|
|
258
|
+
useBulk,
|
|
259
|
+
label: 'findRow',
|
|
260
|
+
finalScanOnEof: true,
|
|
261
|
+
});
|
|
262
|
+
return found;
|
|
282
263
|
}
|
|
283
264
|
resolveRowIndex(rowLocator) {
|
|
284
265
|
// Shared resolver: resolveRowIndex strategy first, else the DOM-position fallback.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { FinalTableConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Page-walk primitive (#427).
|
|
4
|
+
*
|
|
5
|
+
* Owns the shared pagination shell used by map/forEach/filter, findRow(s),
|
|
6
|
+
* countRows, and the public async iterator:
|
|
7
|
+
* - `maxPages` budget
|
|
8
|
+
* - `advancePage(useBulk)` with bulk page-jump accounting
|
|
9
|
+
* - EOF final scan (one more `onPage` after `advancePage` returns false)
|
|
10
|
+
* - optional `waitForReady` before each page
|
|
11
|
+
*
|
|
12
|
+
* Per-page collection (overscan, dedupe, loading, filters) stays in the caller
|
|
13
|
+
* so find vs map can keep their different row policies.
|
|
14
|
+
*
|
|
15
|
+
* ## Feature matrix (public APIs → this shell)
|
|
16
|
+
*
|
|
17
|
+
* | API | final-scan | bulk opt-in | waitForReady | overscan/dedupe/loading |
|
|
18
|
+
* |-----|------------|-------------|--------------|-------------------------|
|
|
19
|
+
* | map / forEach / filter | yes | options.useBulkPagination | via caller | in runMap |
|
|
20
|
+
* | findRows | yes | options.useBulkPagination | yes | findRows policy (skip loading) |
|
|
21
|
+
* | findRow | yes | options.useBulkPagination | yes | DOM/post filters only |
|
|
22
|
+
* | countRows | yes | no (single-step) | yes | count-only / post filters |
|
|
23
|
+
* | async iterator | yes | no | via runMap path | same as map |
|
|
24
|
+
* | findRowByIndex | no* | no | n/a | scroll/search, not a full scan |
|
|
25
|
+
* | bringIntoView | n/a | path planner | n/a | not a page scan |
|
|
26
|
+
*
|
|
27
|
+
* \* findRowByIndex stops when the target index is mounted; it does not collect rows.
|
|
28
|
+
*/
|
|
29
|
+
export type ScanDecision = 'continue' | 'stop';
|
|
30
|
+
export interface ScanPageContext {
|
|
31
|
+
/** 1-based count of pages visited so far (includes the current page). */
|
|
32
|
+
pagesScanned: number;
|
|
33
|
+
/** `tableState.currentPageIndex` at the start of this page callback. */
|
|
34
|
+
pageIndex: number;
|
|
35
|
+
/** True when this is the post-EOF extra pass after `advancePage` returned false. */
|
|
36
|
+
isFinalScan: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface ScanPagesEnv {
|
|
39
|
+
advancePage: (useBulk: boolean) => Promise<boolean>;
|
|
40
|
+
getCurrentPageIndex: () => number;
|
|
41
|
+
config: FinalTableConfig;
|
|
42
|
+
/** Optional gate before each page (e.g. `isTableLoading` poll). */
|
|
43
|
+
waitForReady?: () => Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
export interface ScanPagesOptions {
|
|
46
|
+
maxPages: number;
|
|
47
|
+
/** Prefer goNextBulk when the advance helper supports it. @default false */
|
|
48
|
+
useBulk?: boolean;
|
|
49
|
+
label?: string;
|
|
50
|
+
/**
|
|
51
|
+
* When `advancePage` returns false, invoke `onPage` once more before stopping.
|
|
52
|
+
* Matches runMap / findRows EOF behavior. @default true
|
|
53
|
+
*/
|
|
54
|
+
finalScanOnEof?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function scanPages(env: ScanPagesEnv, onPage: (ctx: ScanPageContext) => Promise<ScanDecision>, options: ScanPagesOptions): Promise<{
|
|
57
|
+
pagesScanned: number;
|
|
58
|
+
}>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scanPages = scanPages;
|
|
4
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
5
|
+
async function scanPages(env, onPage, options) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
const maxPages = options.maxPages;
|
|
8
|
+
const useBulk = (_a = options.useBulk) !== null && _a !== void 0 ? _a : false;
|
|
9
|
+
const finalScanOnEof = options.finalScanOnEof !== false;
|
|
10
|
+
const label = (_b = options.label) !== null && _b !== void 0 ? _b : 'scanPages';
|
|
11
|
+
let pagesScanned = 1;
|
|
12
|
+
let reachedEnd = false;
|
|
13
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `${label}: starting (maxPages=${maxPages}, useBulk=${useBulk}, finalScanOnEof=${finalScanOnEof})`);
|
|
14
|
+
while (true) {
|
|
15
|
+
if (env.waitForReady) {
|
|
16
|
+
await env.waitForReady();
|
|
17
|
+
}
|
|
18
|
+
const decision = await onPage({
|
|
19
|
+
pagesScanned,
|
|
20
|
+
pageIndex: env.getCurrentPageIndex(),
|
|
21
|
+
isFinalScan: reachedEnd,
|
|
22
|
+
});
|
|
23
|
+
if (decision === 'stop') {
|
|
24
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `${label}: onPage returned stop at page ${env.getCurrentPageIndex()}`);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
if (pagesScanned >= maxPages || reachedEnd) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
const prevPage = env.getCurrentPageIndex();
|
|
31
|
+
const didAdvance = await env.advancePage(useBulk);
|
|
32
|
+
if (!didAdvance) {
|
|
33
|
+
if (finalScanOnEof) {
|
|
34
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `${label}: pagination returned false — final scan`);
|
|
35
|
+
reachedEnd = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
const pagesJumped = env.getCurrentPageIndex() - prevPage;
|
|
41
|
+
pagesScanned += pagesJumped > 0 ? pagesJumped : 1;
|
|
42
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `${label}: advanced ${pagesJumped > 0 ? pagesJumped : 1} page(s), now at page ${env.getCurrentPageIndex()} (scanned=${pagesScanned})`);
|
|
43
|
+
}
|
|
44
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `${label}: done — ${pagesScanned} page(s) scanned`);
|
|
45
|
+
return { pagesScanned };
|
|
46
|
+
}
|
|
@@ -20,6 +20,7 @@ export declare function runForEach<T>(env: TableIterationEnv<T>, callback: (ctx:
|
|
|
20
20
|
/**
|
|
21
21
|
* Row iteration for map (and forEach/filter via label).
|
|
22
22
|
* Concurrency: `parallel` | `synchronized` | `sequential` (see RowIterationOptions).
|
|
23
|
+
* Page walking is owned by {@link scanPages} (#427).
|
|
23
24
|
*/
|
|
24
25
|
export declare function runMap<T, R>(env: TableIterationEnv<T>, callback: (ctx: RowIterationContext<T>) => R | Promise<R>, options?: RowIterationOptions, label?: string): Promise<R[]>;
|
|
25
26
|
/** Filter via map; returns matched rows as {@link SmartRowArray}. */
|
|
@@ -8,6 +8,7 @@ const debugUtils_1 = require("../utils/debugUtils");
|
|
|
8
8
|
const navigationBarrier_1 = require("../utils/navigationBarrier");
|
|
9
9
|
const mutex_1 = require("../utils/mutex");
|
|
10
10
|
const rowResolution_1 = require("./rowResolution");
|
|
11
|
+
const scanPages_1 = require("./scanPages");
|
|
11
12
|
function log(config, msg) {
|
|
12
13
|
(0, debugUtils_1.logDebug)(config, 'verbose', msg);
|
|
13
14
|
}
|
|
@@ -19,9 +20,10 @@ async function runForEach(env, callback, options = {}) {
|
|
|
19
20
|
/**
|
|
20
21
|
* Row iteration for map (and forEach/filter via label).
|
|
21
22
|
* Concurrency: `parallel` | `synchronized` | `sequential` (see RowIterationOptions).
|
|
23
|
+
* Page walking is owned by {@link scanPages} (#427).
|
|
22
24
|
*/
|
|
23
25
|
async function runMap(env, callback, options = {}, label = 'map') {
|
|
24
|
-
var _a, _b, _c, _d, _e
|
|
26
|
+
var _a, _b, _c, _d, _e;
|
|
25
27
|
const map = env.getMap();
|
|
26
28
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
27
29
|
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
@@ -42,8 +44,6 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
42
44
|
let rowIndex = 0;
|
|
43
45
|
let stopped = false;
|
|
44
46
|
let stoppedIndex = Infinity;
|
|
45
|
-
let pagesScanned = 1;
|
|
46
|
-
let reachedEnd = false;
|
|
47
47
|
const stop = (idx) => {
|
|
48
48
|
if (!stopped) {
|
|
49
49
|
log(env.config, `${label}: stop() called at row ${idx} — halting`);
|
|
@@ -51,7 +51,14 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
51
51
|
stoppedIndex = idx;
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
-
|
|
54
|
+
const { pagesScanned } = await (0, scanPages_1.scanPages)({
|
|
55
|
+
advancePage: env.advancePage,
|
|
56
|
+
getCurrentPageIndex: env.getCurrentPageIndex,
|
|
57
|
+
config: env.config,
|
|
58
|
+
}, async ({ pagesScanned: pageNum, isFinalScan }) => {
|
|
59
|
+
var _a;
|
|
60
|
+
if (stopped)
|
|
61
|
+
return 'stop';
|
|
55
62
|
const rowLocators = env.getRowLocators();
|
|
56
63
|
const allIndices = await tracker.peekUnseenIndices(rowLocators);
|
|
57
64
|
const pageRows = await rowLocators.all();
|
|
@@ -68,8 +75,8 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
68
75
|
// #417 follow-up: skip viewport filtering on the final scan — there are no more pages
|
|
69
76
|
// to defer to, so collect all remaining unseen rows regardless of visibility.
|
|
70
77
|
let candidateIndices = allIndices;
|
|
71
|
-
const getVisibleRowIndices = (
|
|
72
|
-
if (getVisibleRowIndices && !
|
|
78
|
+
const getVisibleRowIndices = (_a = env.config.strategies.viewport) === null || _a === void 0 ? void 0 : _a.getVisibleRowIndices;
|
|
79
|
+
if (getVisibleRowIndices && !isFinalScan) {
|
|
73
80
|
const visible = new Set(await getVisibleRowIndices(env.getContext()));
|
|
74
81
|
const minVisible = visible.size > 0 ? Math.min(...visible) : Infinity;
|
|
75
82
|
candidateIndices = allIndices.filter(idx => visible.has(idx) || idx < minVisible);
|
|
@@ -86,10 +93,10 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
86
93
|
await tracker.commitIndices(rowLocators, newIndices);
|
|
87
94
|
const batchSize = newIndices.length;
|
|
88
95
|
if (batchSize === 0) {
|
|
89
|
-
log(env.config, `${label}: page ${
|
|
96
|
+
log(env.config, `${label}: page ${pageNum} — no new row(s) found`);
|
|
90
97
|
}
|
|
91
98
|
else {
|
|
92
|
-
log(env.config, `${label}: scanning page ${
|
|
99
|
+
log(env.config, `${label}: scanning page ${pageNum} — ${batchSize} new row(s)`);
|
|
93
100
|
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(batchSize) : undefined;
|
|
94
101
|
const actionMutex = useMutex ? new mutex_1.Mutex() : null;
|
|
95
102
|
// `index` (ctx.index) is the enumeration counter — the order rows are visited,
|
|
@@ -169,8 +176,8 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
else {
|
|
172
|
-
const
|
|
173
|
-
pageResults.push(...
|
|
179
|
+
const batchResults = await Promise.all(smartRows.map((row, i) => processRow(row, positionBase + i)));
|
|
180
|
+
pageResults.push(...batchResults);
|
|
174
181
|
}
|
|
175
182
|
for (let i = 0; i < pageResults.length; i++) {
|
|
176
183
|
if (positionBase + i > stoppedIndex)
|
|
@@ -181,16 +188,13 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
181
188
|
}
|
|
182
189
|
rowIndex += batchSize;
|
|
183
190
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
pagesScanned++;
|
|
193
|
-
}
|
|
191
|
+
return stopped ? 'stop' : 'continue';
|
|
192
|
+
}, {
|
|
193
|
+
maxPages: effectiveMaxPages,
|
|
194
|
+
useBulk,
|
|
195
|
+
label,
|
|
196
|
+
finalScanOnEof: true,
|
|
197
|
+
});
|
|
194
198
|
log(env.config, `${label}: complete — ${results.length} result(s) across ${pagesScanned} page(s)`);
|
|
195
199
|
}
|
|
196
200
|
finally {
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Generated by scripts/embed-version.mjs from package.json
|
|
4
4
|
*/
|
|
5
5
|
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
6
|
-
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.20.1-next.
|
|
6
|
+
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.20.1-next.dbc9618";
|
package/dist/packageVersion.js
CHANGED
|
@@ -6,4 +6,4 @@ exports.PLAYWRIGHT_SMART_TABLE_VERSION = void 0;
|
|
|
6
6
|
* Generated by scripts/embed-version.mjs from package.json
|
|
7
7
|
*/
|
|
8
8
|
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
9
|
-
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.20.1-next.
|
|
9
|
+
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.20.1-next.dbc9618";
|
package/dist/smartRow.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Locator, type Page } from '@playwright/test';
|
|
2
2
|
import { SmartRow as SmartRowType, FinalTableConfig, TableResult } from './types';
|
|
3
3
|
import { NavigationBarrier } from './utils/navigationBarrier';
|
|
4
4
|
/**
|
|
@@ -9,5 +9,5 @@ import { NavigationBarrier } from './utils/navigationBarrier';
|
|
|
9
9
|
* @internal Internal factory for creating SmartRow objects.
|
|
10
10
|
* Not part of the public package surface; tests and consumers should use public APIs.
|
|
11
11
|
*/
|
|
12
|
-
declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null, tablePageIndex?: number, barrier?: NavigationBarrier) => SmartRowType<T>;
|
|
12
|
+
declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null, tablePageIndex?: number, barrier?: NavigationBarrier, renderWindowPosition?: boolean) => SmartRowType<T>;
|
|
13
13
|
export default createSmartRow;
|
package/dist/smartRow.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
3
4
|
const rowResolution_1 = require("./engine/rowResolution");
|
|
4
5
|
const fill_1 = require("./strategies/fill");
|
|
5
6
|
const stringUtils_1 = require("./utils/stringUtils");
|
|
@@ -12,7 +13,7 @@ const sentinel_1 = require("./utils/sentinel");
|
|
|
12
13
|
* Returns the target cell locator after navigation.
|
|
13
14
|
*/
|
|
14
15
|
const _navigateToCell = async (params) => {
|
|
15
|
-
const { config, rootLocator, page, resolve, getHeaders, column, index, rowLocator, rowIndex, barrier } = params;
|
|
16
|
+
const { config, rootLocator, page, resolve, getHeaders, column, index, rowLocator, rowIndex, barrier, allowMissingCell } = params;
|
|
16
17
|
const rowLabel = typeof rowIndex === 'number' ? `row ${rowIndex}` : 'row ?';
|
|
17
18
|
(0, debugUtils_1.logDebug)(config, 'verbose', `_navigateToCell: resolving column "${column}" (colIndex ${index}), ${rowLabel}`);
|
|
18
19
|
// Get active cell if strategy is available
|
|
@@ -65,7 +66,18 @@ const _navigateToCell = async (params) => {
|
|
|
65
66
|
? await viewport.getVisibleRowRange(context)
|
|
66
67
|
: null;
|
|
67
68
|
const rowVisible = !rowRange || (rowIndex >= rowRange.first && rowIndex <= rowRange.last);
|
|
68
|
-
|
|
69
|
+
let cellAttached = rowVisible && await targetReached();
|
|
70
|
+
if (rowVisible && !cellAttached) {
|
|
71
|
+
try {
|
|
72
|
+
await getCellLocator().waitFor({ state: 'attached', timeout: 500 });
|
|
73
|
+
cellAttached = true;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!(error instanceof test_1.errors.TimeoutError))
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (cellAttached) {
|
|
69
81
|
(0, debugUtils_1.logDebug)(config, 'verbose', `_navigateToCell: col ${index} in visible range [${knownColRange.first}-${knownColRange.last}], reading directly`);
|
|
70
82
|
// Check in to the barrier without a moveAction — peers that need the scroll
|
|
71
83
|
// still supply their own action and will trigger it when all rows arrive.
|
|
@@ -124,6 +136,8 @@ const _navigateToCell = async (params) => {
|
|
|
124
136
|
}
|
|
125
137
|
// Cell still not accessible — fall through to navigation primitives if configured.
|
|
126
138
|
if (!config.strategies.navigation) {
|
|
139
|
+
if (allowMissingCell)
|
|
140
|
+
return getCellLocator();
|
|
127
141
|
const colRange = viewport.getVisibleColumnRange ? await viewport.getVisibleColumnRange(context) : null;
|
|
128
142
|
const rowRange = viewport.getVisibleRowRange ? await viewport.getVisibleRowRange(context) : null;
|
|
129
143
|
let errMsg = `SmartTable: could not reach cell for column "${column}" (colIndex ${index}) at row ${rowIndex}.\n`;
|
|
@@ -144,15 +158,17 @@ const _navigateToCell = async (params) => {
|
|
|
144
158
|
// Use navigation primitives if available
|
|
145
159
|
if (config.strategies.navigation) {
|
|
146
160
|
const nav = config.strategies.navigation;
|
|
147
|
-
if (typeof rowIndex !== 'number') {
|
|
148
|
-
throw new Error('Row index is required for navigation');
|
|
149
|
-
}
|
|
150
161
|
if (await targetReached()) {
|
|
151
162
|
// Already there. If we have a barrier, check-in to stay in lock-step.
|
|
152
163
|
if (barrier)
|
|
153
164
|
await barrier.sync(index);
|
|
154
165
|
return getCellLocator();
|
|
155
166
|
}
|
|
167
|
+
if (typeof rowIndex !== 'number') {
|
|
168
|
+
if (allowMissingCell)
|
|
169
|
+
return getCellLocator();
|
|
170
|
+
throw new Error('Row index is required for navigation');
|
|
171
|
+
}
|
|
156
172
|
// If getActiveCell is present but returns null (no focus), and cell is in DOM,
|
|
157
173
|
// try to focus it once before entering navigation loop.
|
|
158
174
|
if (config.strategies.getActiveCell) {
|
|
@@ -271,6 +287,8 @@ const _navigateToCell = async (params) => {
|
|
|
271
287
|
const finalCell = getCellLocator();
|
|
272
288
|
if (await finalCell.count() > 0)
|
|
273
289
|
return finalCell;
|
|
290
|
+
if (allowMissingCell)
|
|
291
|
+
return finalCell;
|
|
274
292
|
const colRange = viewport && viewport.getVisibleColumnRange ? await viewport.getVisibleColumnRange(context) : null;
|
|
275
293
|
const rowRange = viewport && viewport.getVisibleRowRange ? await viewport.getVisibleRowRange(context) : null;
|
|
276
294
|
let errMsg = `SmartTable: could not reach cell for column "${column}" (colIndex ${index}) at row ${rowIndex} after exhausting navigation strategies. Ensure navigation primitives are correctly implemented.\n`;
|
|
@@ -294,7 +312,7 @@ const _navigateToCell = async (params) => {
|
|
|
294
312
|
* @internal Internal factory for creating SmartRow objects.
|
|
295
313
|
* Not part of the public package surface; tests and consumers should use public APIs.
|
|
296
314
|
*/
|
|
297
|
-
const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table, tablePageIndex, barrier) => {
|
|
315
|
+
const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table, tablePageIndex, barrier, renderWindowPosition = false) => {
|
|
298
316
|
const smart = rowLocator;
|
|
299
317
|
// Attach State
|
|
300
318
|
smart.rowIndex = rowIndex;
|
|
@@ -387,12 +405,22 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
387
405
|
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, [...map.keys(), ...Object.keys((_b = config.syntheticColumns) !== null && _b !== void 0 ? _b : {})]));
|
|
388
406
|
}
|
|
389
407
|
const columnOverride = (_c = config.columnOverrides) === null || _c === void 0 ? void 0 : _c[colName];
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
408
|
+
const page = rootLocator.page();
|
|
409
|
+
// Same nav pipeline as toJSON / getCell().bringIntoView() (#430) so virtualized
|
|
410
|
+
// off-screen columns don't return empty/stale text.
|
|
411
|
+
const cell = await _navigateToCell({
|
|
412
|
+
config,
|
|
413
|
+
rootLocator,
|
|
414
|
+
page,
|
|
415
|
+
resolve,
|
|
416
|
+
getHeaders: table === null || table === void 0 ? void 0 : table.getHeaders,
|
|
417
|
+
column: colName,
|
|
418
|
+
index: idx,
|
|
419
|
+
rowLocator,
|
|
420
|
+
rowIndex,
|
|
421
|
+
barrier: smart._barrier,
|
|
422
|
+
allowMissingCell: !!(columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read),
|
|
423
|
+
});
|
|
396
424
|
if (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read) {
|
|
397
425
|
const getCell = (name) => {
|
|
398
426
|
const ci = map.get(name);
|
|
@@ -689,7 +717,8 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
689
717
|
index: idx,
|
|
690
718
|
rowLocator: stableRow,
|
|
691
719
|
rowIndex,
|
|
692
|
-
barrier: smart._barrier
|
|
720
|
+
barrier: smart._barrier,
|
|
721
|
+
allowMissingCell: !!mapper,
|
|
693
722
|
});
|
|
694
723
|
if (navigatedCell) {
|
|
695
724
|
targetCell = navigatedCell;
|
|
@@ -716,8 +745,22 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
716
745
|
? rawCellTimeout
|
|
717
746
|
: undefined;
|
|
718
747
|
const onCellLoadingTimeout = (_h = (_g = config.strategies.loading) === null || _g === void 0 ? void 0 : _g.onCellLoadingTimeout) !== null && _h !== void 0 ? _h : 'read-as-is';
|
|
719
|
-
if (isCellLoading
|
|
720
|
-
|
|
748
|
+
if (isCellLoading) {
|
|
749
|
+
let loading = await isCellLoading(targetCell, col, smart);
|
|
750
|
+
// First-paint race: the cell node can attach a tick before the loading
|
|
751
|
+
// indicator mounts. A single false-negative then reads "" and skips
|
|
752
|
+
// onCellLoadingTimeout. Only grace when the cell still looks empty.
|
|
753
|
+
if (!loading && cellLoadingTimeout !== undefined && cellLoadingTimeout > 0) {
|
|
754
|
+
const peek = await targetCell.evaluate((el) => (el.textContent || '').trim()).catch(() => '');
|
|
755
|
+
if (peek === '') {
|
|
756
|
+
const graceDeadline = Date.now() + Math.min(250, cellLoadingTimeout);
|
|
757
|
+
while (!loading && Date.now() < graceDeadline) {
|
|
758
|
+
await page.waitForTimeout(25);
|
|
759
|
+
loading = await isCellLoading(targetCell, col, smart);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (loading && cellLoadingTimeout !== undefined) {
|
|
721
764
|
(0, debugUtils_1.logDebug)(config, 'verbose', `toJSON: cell "${col}" — waiting up to ${cellLoadingTimeout}ms`);
|
|
722
765
|
const deadline = Date.now() + cellLoadingTimeout;
|
|
723
766
|
let cellResolved = !(await isCellLoading(targetCell, col, smart)); // immediate check (handles timeout=0)
|
|
@@ -830,6 +873,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
830
873
|
(0, debugUtils_1.logDebug)(config, 'info', 'Row fill complete');
|
|
831
874
|
};
|
|
832
875
|
smart.bringIntoView = async () => {
|
|
876
|
+
var _a;
|
|
833
877
|
if (rowIndex === undefined) {
|
|
834
878
|
(0, debugUtils_1.logDebug)(config, 'info', 'bringIntoView called on a row with unknown rowIndex (e.g. from getRow()). ' +
|
|
835
879
|
'Page navigation and scrolling will proceed, but virtual-scroll keyboard navigation will be skipped. ' +
|
|
@@ -876,8 +920,20 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
876
920
|
}
|
|
877
921
|
// Delay after pagination/finding before scrolling
|
|
878
922
|
await (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
879
|
-
//
|
|
880
|
-
|
|
923
|
+
// Prefer viewport.scrollToRow when configured (#430). scrollIntoViewIfNeeded adjusts
|
|
924
|
+
// both axes and can evict sibling rows on recycling virtualizers.
|
|
925
|
+
const viewport = config.strategies.viewport;
|
|
926
|
+
const logicalRowIndex = (viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow) && renderWindowPosition
|
|
927
|
+
? (_a = (await (0, rowResolution_1.resolveLogicalRowIndex)(rowLocator, config, () => undefined))) === null || _a === void 0 ? void 0 : _a.index
|
|
928
|
+
: rowIndex;
|
|
929
|
+
if ((viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow) && typeof logicalRowIndex === 'number') {
|
|
930
|
+
const page = rootLocator.page();
|
|
931
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `bringIntoView: viewport.scrollToRow(${logicalRowIndex})`);
|
|
932
|
+
await viewport.scrollToRow({ root: rootLocator, config, page, resolve, getHeaders: parentTable === null || parentTable === void 0 ? void 0 : parentTable.getHeaders }, logicalRowIndex);
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
await rowLocator.scrollIntoViewIfNeeded();
|
|
936
|
+
}
|
|
881
937
|
};
|
|
882
938
|
return smart;
|
|
883
939
|
};
|
|
@@ -13,6 +13,11 @@ export declare const HeaderStrategies: {
|
|
|
13
13
|
/**
|
|
14
14
|
* Physically scrolls the table horizontally to force virtualized columns to mount,
|
|
15
15
|
* collecting their names along the way.
|
|
16
|
+
*
|
|
17
|
+
* **Requires `selector`** — the CSS selector for the horizontal scroll container
|
|
18
|
+
* (e.g. `.dvn-scroller` for Glide, `.rdg-viewport` for RDG). Framework class names
|
|
19
|
+
* belong in presets / your config, not in this generic factory. Without `selector`,
|
|
20
|
+
* only currently visible headers are returned (no scrolling).
|
|
16
21
|
*/
|
|
17
22
|
horizontalScroll: (options?: {
|
|
18
23
|
limit?: number;
|