@rickcedwhat/playwright-smart-table 6.24.0-next.cc0fbcb → 6.25.0-next.0d74239
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 +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 +4 -0
- package/dist/engine/rowFinder.js +15 -4
- package/dist/engine/rowSnapshot.d.ts +28 -0
- package/dist/engine/rowSnapshot.js +407 -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 +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/smartRow.js +51 -721
- 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 +36 -7
- package/dist/useTable.js +51 -241
- package/dist/utils/locatorDescription.d.ts +15 -0
- package/dist/utils/locatorDescription.js +56 -0
- package/dist/utils/navigationBarrier.js +0 -1
- package/dist/utils/safeStringify.d.ts +2 -0
- package/dist/utils/safeStringify.js +18 -0
- package/package.json +1 -1
package/dist/useTable.js
CHANGED
|
@@ -27,28 +27,15 @@ const filterEngine_1 = require("./filterEngine");
|
|
|
27
27
|
const tableMapper_1 = require("./engine/tableMapper");
|
|
28
28
|
const rowFinder_1 = require("./engine/rowFinder");
|
|
29
29
|
const tableIteration_1 = require("./engine/tableIteration");
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const countRows_1 = require("./engine/countRows");
|
|
31
|
+
const sorting_1 = require("./engine/sorting");
|
|
32
|
+
const rowByIndex_1 = require("./engine/rowByIndex");
|
|
33
|
+
const safeStringify_1 = require("./utils/safeStringify");
|
|
32
34
|
const debugUtils_1 = require("./utils/debugUtils");
|
|
33
35
|
const smartRowArray_1 = require("./utils/smartRowArray");
|
|
34
|
-
const elementTracker_1 = require("./utils/elementTracker");
|
|
35
|
-
const loadingWait_1 = require("./utils/loadingWait");
|
|
36
36
|
const pageIndex_1 = require("./utils/pageIndex");
|
|
37
37
|
const smartRowInternals_1 = require("./utils/smartRowInternals");
|
|
38
|
-
|
|
39
|
-
const safeStringify = (obj) => {
|
|
40
|
-
try {
|
|
41
|
-
return JSON.stringify(obj, (_key, value) => {
|
|
42
|
-
if (typeof value === 'function') {
|
|
43
|
-
return '[Function]';
|
|
44
|
-
}
|
|
45
|
-
return value;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
return '[unserializable]';
|
|
50
|
-
}
|
|
51
|
-
};
|
|
38
|
+
const locatorDescription_1 = require("./utils/locatorDescription");
|
|
52
39
|
/**
|
|
53
40
|
* Main hook to interact with a table.
|
|
54
41
|
*/
|
|
@@ -153,7 +140,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
153
140
|
const effectiveLocator = healSelector
|
|
154
141
|
? rootLocator.locator(healSelector)
|
|
155
142
|
: rowLocator;
|
|
156
|
-
|
|
143
|
+
// Keep a caller-supplied description (e.g. the filters) across self-healing; otherwise name the row by index.
|
|
144
|
+
const existingDescription = (0, locatorDescription_1.getLocatorDescription)(rowLocator);
|
|
145
|
+
const description = existingDescription !== null && existingDescription !== void 0 ? existingDescription : (rowIndex !== undefined ? (0, locatorDescription_1.describeRowByIndex)(rowIndex) : undefined);
|
|
146
|
+
const describedLocator = description !== undefined && (healSelector !== undefined || existingDescription === undefined)
|
|
147
|
+
? (0, locatorDescription_1.describeLocator)(effectiveLocator, description, config)
|
|
148
|
+
: effectiveLocator;
|
|
149
|
+
const sr = (0, smartRow_1.default)(describedLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier, renderWindowPosition);
|
|
157
150
|
if (healSelector)
|
|
158
151
|
(0, smartRowInternals_1.internals)(sr)._selfHealing = true;
|
|
159
152
|
return sr;
|
|
@@ -231,6 +224,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
231
224
|
}
|
|
232
225
|
return didAdvance;
|
|
233
226
|
};
|
|
227
|
+
const sortingEnv = () => ({
|
|
228
|
+
config,
|
|
229
|
+
getPage: () => rootLocator.page(),
|
|
230
|
+
getContext: () => (Object.assign(Object.assign({}, createStrategyContext()), { getHeaderCell: result.getHeaderCell })),
|
|
231
|
+
});
|
|
234
232
|
const result = {
|
|
235
233
|
get currentPageIndex() { return tableState.currentPageIndex; },
|
|
236
234
|
set currentPageIndex(v) {
|
|
@@ -319,133 +317,24 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
319
317
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
320
318
|
},
|
|
321
319
|
countRows: async (filters, options) => {
|
|
322
|
-
var _a, _b, _c, _d;
|
|
323
|
-
var _e;
|
|
324
320
|
if (tableState.empty)
|
|
325
321
|
return 0;
|
|
326
322
|
await _autoInit();
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
else if ((_c = (_b = config.columnOverrides) === null || _b === void 0 ? void 0 : _b[key]) === null || _c === void 0 ? void 0 : _c.read) {
|
|
341
|
-
overrideFilters[key] = value;
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
domFilters[key] = value;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
const hasPostFilters = Object.keys(overrideFilters).length > 0 || Object.keys(syntheticFilters).length > 0;
|
|
349
|
-
const resolveRows = () => {
|
|
350
|
-
var _a;
|
|
351
|
-
let rows = resolve(config.rowSelector, rootLocator);
|
|
352
|
-
if (Object.keys(domFilters).length > 0) {
|
|
353
|
-
const map = tableMapper.getMapSync();
|
|
354
|
-
if (!map)
|
|
355
|
-
throw new Error('Initialization Error: Table map not available. Call "await table.init()" first.');
|
|
356
|
-
rows = filterEngine.applyFilters(rows, domFilters, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, rootLocator.page(), rootLocator);
|
|
357
|
-
}
|
|
358
|
-
return rows;
|
|
359
|
-
};
|
|
360
|
-
const countPostFilterMatches = async (candidates, indices) => {
|
|
361
|
-
var _a;
|
|
362
|
-
if (!hasPostFilters)
|
|
363
|
-
return indices.length;
|
|
364
|
-
const map = tableMapper.getMapSync();
|
|
365
|
-
if (!map)
|
|
366
|
-
throw new Error('Initialization Error: Table map not available. Call "await table.init()" first.');
|
|
367
|
-
const exact = (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false;
|
|
368
|
-
const hasOverrides = Object.keys(overrideFilters).length > 0;
|
|
369
|
-
const hasSynthetics = Object.keys(syntheticFilters).length > 0;
|
|
370
|
-
let count = 0;
|
|
371
|
-
for (const idx of indices) {
|
|
372
|
-
const row = candidates[idx];
|
|
373
|
-
if (hasOverrides && !await rowFinder.matchesOverrideFilters(row, overrideFilters, map, exact))
|
|
374
|
-
continue;
|
|
375
|
-
if (hasSynthetics && !await rowFinder.matchesSyntheticFilters(row, syntheticFilters, map, exact))
|
|
376
|
-
continue;
|
|
377
|
-
count++;
|
|
378
|
-
}
|
|
379
|
-
return count;
|
|
380
|
-
};
|
|
381
|
-
// Wait for table to finish loading before counting
|
|
382
|
-
const isTableLoading = (_d = config.strategies.loading) === null || _d === void 0 ? void 0 : _d.isTableLoading;
|
|
383
|
-
if (isTableLoading) {
|
|
384
|
-
const ctx = createStrategyContext();
|
|
385
|
-
await (0, loadingWait_1.waitWhileTableLoading)(config, ctx, rootLocator.page(), 'countRows');
|
|
386
|
-
}
|
|
387
|
-
if (!hasPagination) {
|
|
388
|
-
log(`countRows: counting rows in current viewport (no pagination)${hasFilters ? ` filters=${safeStringify(filtersRecord)}` : ''}`);
|
|
389
|
-
if (!hasPostFilters)
|
|
390
|
-
return resolveRows().count();
|
|
391
|
-
const all = await resolveRows().all();
|
|
392
|
-
return countPostFilterMatches(all, all.map((_, i) => i));
|
|
393
|
-
}
|
|
394
|
-
log(`countRows: paginating up to ${effectiveMaxPages} page(s)${hasFilters ? ` filters=${safeStringify(filtersRecord)}` : ''}`);
|
|
395
|
-
const tracker = new elementTracker_1.ElementTracker('countRows');
|
|
396
|
-
let total = 0;
|
|
397
|
-
let pagesScanned = 1;
|
|
398
|
-
let paginationError;
|
|
399
|
-
try {
|
|
400
|
-
const scanned = await (0, scanPages_1.scanPages)({
|
|
401
|
-
advancePage: _advancePage,
|
|
402
|
-
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
403
|
-
config,
|
|
404
|
-
waitForReady: async () => {
|
|
405
|
-
if (!isTableLoading)
|
|
406
|
-
return;
|
|
407
|
-
const ctx = createStrategyContext();
|
|
408
|
-
await (0, loadingWait_1.waitWhileTableLoading)(config, ctx, rootLocator.page(), 'countRows');
|
|
409
|
-
},
|
|
410
|
-
}, async ({ pagesScanned: pageNum }) => {
|
|
411
|
-
const rowLocators = resolveRows();
|
|
412
|
-
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
413
|
-
const candidates = await rowLocators.all();
|
|
414
|
-
const matched = await countPostFilterMatches(candidates, newIndices);
|
|
415
|
-
total += matched;
|
|
416
|
-
log(`countRows: page ${pageNum} — ${matched} row(s) (running total: ${total})`);
|
|
417
|
-
return 'continue';
|
|
418
|
-
}, {
|
|
419
|
-
maxPages: effectiveMaxPages,
|
|
420
|
-
useBulk: false,
|
|
421
|
-
label: 'countRows',
|
|
422
|
-
finalScanOnEof: true,
|
|
423
|
-
});
|
|
424
|
-
pagesScanned = scanned.pagesScanned;
|
|
425
|
-
}
|
|
426
|
-
catch (e) {
|
|
427
|
-
paginationError = e;
|
|
428
|
-
throw e;
|
|
429
|
-
}
|
|
430
|
-
finally {
|
|
431
|
-
await tracker.cleanup(rootLocator.page());
|
|
432
|
-
// Always restore page 1, even if pagination threw mid-count (#348), so the table
|
|
433
|
-
// stays usable for callers that catch the error. If reset() itself fails, only let
|
|
434
|
-
// it surface when pagination succeeded — otherwise preserve the original error.
|
|
435
|
-
log(`countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
|
|
436
|
-
try {
|
|
437
|
-
await result.reset();
|
|
438
|
-
}
|
|
439
|
-
catch (resetError) {
|
|
440
|
-
if (paginationError === undefined)
|
|
441
|
-
throw resetError;
|
|
442
|
-
(0, debugUtils_1.logDebug)(config, 'error', 'countRows: reset() failed after pagination error', resetError);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return total;
|
|
323
|
+
return (0, countRows_1.countRows)({
|
|
324
|
+
config,
|
|
325
|
+
rootLocator,
|
|
326
|
+
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
327
|
+
tableMapper,
|
|
328
|
+
filterEngine,
|
|
329
|
+
rowFinder,
|
|
330
|
+
advancePage: _advancePage,
|
|
331
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
332
|
+
getContext: () => createStrategyContext(),
|
|
333
|
+
reset: () => result.reset(),
|
|
334
|
+
}, filters, options);
|
|
446
335
|
},
|
|
447
336
|
mapColumn: async (columnName, options = {}) => {
|
|
448
|
-
log(`mapColumn: column="${columnName}" options=${safeStringify(options)}`);
|
|
337
|
+
log(`mapColumn: column="${columnName}" options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
449
338
|
await _autoInit();
|
|
450
339
|
const map = await tableMapper.getMap();
|
|
451
340
|
if (!map.has(columnName))
|
|
@@ -470,7 +359,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
470
359
|
* Iterates over rows and extracts the value of a single column as strings.
|
|
471
360
|
*/
|
|
472
361
|
getColumnValues: async (columnName, options = {}) => {
|
|
473
|
-
log(`getColumnValues: column="${columnName}" options=${safeStringify(options)}`);
|
|
362
|
+
log(`getColumnValues: column="${columnName}" options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
474
363
|
const values = await result.mapColumn(columnName, options);
|
|
475
364
|
return values.map(v => String(v));
|
|
476
365
|
},
|
|
@@ -498,7 +387,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
498
387
|
log("Table revalidated.");
|
|
499
388
|
},
|
|
500
389
|
getRow: (filters, options = { exact: false }) => {
|
|
501
|
-
log(`getRow: filters=${safeStringify(filters)} exact=${options.exact}`);
|
|
390
|
+
log(`getRow: filters=${(0, safeStringify_1.safeStringify)(filters)} exact=${options.exact}`);
|
|
502
391
|
const syntheticKeys = Object.keys(filters).filter(k => { var _a; return (_a = config.syntheticColumns) === null || _a === void 0 ? void 0 : _a[k]; });
|
|
503
392
|
if (syntheticKeys.length > 0) {
|
|
504
393
|
throw new Error(`getRow() cannot filter by synthetic column(s): ${syntheticKeys.join(', ')}. ` +
|
|
@@ -514,7 +403,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
514
403
|
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.');
|
|
515
404
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
516
405
|
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page(), rootLocator);
|
|
517
|
-
|
|
406
|
+
const describedRows = (0, locatorDescription_1.describeLocator)(matchedRows, (0, locatorDescription_1.describeRowByFilters)(filters), config);
|
|
407
|
+
return _makeSmart(describedRows, map, undefined); // sync path cannot compute real index
|
|
518
408
|
},
|
|
519
409
|
getRowByIndex: (index) => {
|
|
520
410
|
log(`getRowByIndex: index=${index}`);
|
|
@@ -525,67 +415,23 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
525
415
|
return _makeSmart(rowLocator, map, index, undefined, undefined, undefined, true);
|
|
526
416
|
},
|
|
527
417
|
findRowByIndex: async (index, options) => {
|
|
528
|
-
var _a, _b;
|
|
529
|
-
log(`findRowByIndex: index=${index} options=${safeStringify(options)}`);
|
|
530
418
|
await _autoInit();
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
}
|
|
540
|
-
let foundSelector;
|
|
541
|
-
// The currently-mounted row whose logical index === index, if any.
|
|
542
|
-
const findMounted = async () => {
|
|
543
|
-
const rows = await resolve(config.rowSelector, rootLocator).all();
|
|
544
|
-
for (const r of rows) {
|
|
545
|
-
const result = await resolveRI(r);
|
|
546
|
-
if (result === undefined)
|
|
547
|
-
continue;
|
|
548
|
-
const normalized = (0, rowResolution_1.normalizeRowIndexResult)(result);
|
|
549
|
-
if (normalized.index === index) {
|
|
550
|
-
foundSelector = normalized.selector;
|
|
551
|
-
return r;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
return null;
|
|
555
|
-
};
|
|
556
|
-
// 1. Already mounted (no navigation needed)?
|
|
557
|
-
let loc = await findMounted();
|
|
558
|
-
// 2. Fast path: jump straight to it via the viewport's random-access scroll.
|
|
559
|
-
if (!loc && (viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow)) {
|
|
560
|
-
log(`findRowByIndex: scrolling to row ${index}`);
|
|
561
|
-
await viewport.scrollToRow(createStrategyContext(), index);
|
|
562
|
-
loc = await findMounted();
|
|
563
|
-
}
|
|
564
|
-
// 3. Fallback: advance pages (on infinite-scroll tables, a "page" is a scroll step) until
|
|
565
|
-
// the row is reached. Bounded by maxPages so an unreachable index can't loop forever.
|
|
566
|
-
if (!loc && config.strategies.pagination) {
|
|
567
|
-
const effectiveMaxPages = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
|
|
568
|
-
let pagesScanned = 1;
|
|
569
|
-
while (!loc && pagesScanned < effectiveMaxPages) {
|
|
570
|
-
if (!await _advancePage(false))
|
|
571
|
-
break;
|
|
572
|
-
pagesScanned++;
|
|
573
|
-
loc = await findMounted();
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
if (!loc) {
|
|
577
|
-
throw new Error(`findRowByIndex(${index}): could not reach row ${index}. ` +
|
|
578
|
-
`Ensure a viewport.scrollToRow or pagination can bring it into view (searched up to maxPages=${(_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages}).`);
|
|
579
|
-
}
|
|
580
|
-
await (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
581
|
-
return _makeSmart(loc, map, index, tableState.currentPageIndex, undefined, foundSelector);
|
|
419
|
+
return (0, rowByIndex_1.findRowByIndex)({
|
|
420
|
+
config,
|
|
421
|
+
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
422
|
+
getMap: () => tableMapper.getMapSync(),
|
|
423
|
+
advancePage: _advancePage,
|
|
424
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
425
|
+
getContext: () => createStrategyContext(),
|
|
426
|
+
makeSmartRow: (loc, map, idx, pageIdx, sel) => _makeSmart(loc, map, idx, pageIdx, undefined, sel),
|
|
427
|
+
}, index, options);
|
|
582
428
|
},
|
|
583
429
|
findRow: async (filters, options) => {
|
|
584
|
-
log(`findRow: filters=${safeStringify(filters)} options=${safeStringify(options)}`);
|
|
430
|
+
log(`findRow: filters=${(0, safeStringify_1.safeStringify)(filters)} options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
585
431
|
return rowFinder.findRow(filters, options);
|
|
586
432
|
},
|
|
587
433
|
findRows: async (filters, options) => {
|
|
588
|
-
log(`findRows: filters=${safeStringify(filters !== null && filters !== void 0 ? filters : {})} options=${safeStringify(options)}`);
|
|
434
|
+
log(`findRows: filters=${(0, safeStringify_1.safeStringify)(filters !== null && filters !== void 0 ? filters : {})} options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
589
435
|
return rowFinder.findRows(filters !== null && filters !== void 0 ? filters : {}, options);
|
|
590
436
|
},
|
|
591
437
|
isInitialized: () => {
|
|
@@ -598,49 +444,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
598
444
|
},
|
|
599
445
|
sorting: {
|
|
600
446
|
apply: async (columnName, direction) => {
|
|
601
|
-
var _a, _b;
|
|
602
|
-
var _c, _d, _e, _f;
|
|
603
447
|
await _autoInit();
|
|
604
|
-
|
|
605
|
-
throw new Error('No sorting strategy has been configured.');
|
|
606
|
-
log(`Applying sort for column "${columnName}" (${direction})`);
|
|
607
|
-
const context = Object.assign(Object.assign({}, createStrategyContext()), { getHeaderCell: result.getHeaderCell });
|
|
608
|
-
const maxRetries = 3;
|
|
609
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
610
|
-
const currentState = await config.strategies.sorting.getSortState({ columnName, context });
|
|
611
|
-
if (currentState === direction) {
|
|
612
|
-
log(`Sort for "${columnName}" is already "${direction}".`);
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
await config.strategies.sorting.doSort({ columnName, direction, context });
|
|
616
|
-
if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
|
|
617
|
-
const timeout = (_d = (_c = config.strategies.loading.sortStabilizationTimeout) !== null && _c !== void 0 ? _c : config.strategies.loading.loadingTimeout) !== null && _d !== void 0 ? _d : 10000;
|
|
618
|
-
const poll = (_e = config.strategies.loading.sortStabilizationPollInterval) !== null && _e !== void 0 ? _e : 100;
|
|
619
|
-
await (0, loadingWait_1.waitWhileTableLoading)(config, context, rootLocator.page(), 'sorting.apply', {
|
|
620
|
-
timeout,
|
|
621
|
-
pollInterval: poll,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
else {
|
|
625
|
-
const fallback = (_f = (_b = config.strategies.loading) === null || _b === void 0 ? void 0 : _b.sortStabilizationFallbackDelay) !== null && _f !== void 0 ? _f : 200;
|
|
626
|
-
await rootLocator.page().waitForTimeout(fallback);
|
|
627
|
-
}
|
|
628
|
-
await (0, debugUtils_1.debugDelay)(config, 'default');
|
|
629
|
-
const newState = await config.strategies.sorting.getSortState({ columnName, context });
|
|
630
|
-
if (newState === direction) {
|
|
631
|
-
log(`Successfully sorted "${columnName}" to "${direction}".`);
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${maxRetries} attempts.`);
|
|
448
|
+
return (0, sorting_1.applySort)(sortingEnv(), columnName, direction);
|
|
636
449
|
},
|
|
637
450
|
getState: async (columnName) => {
|
|
638
451
|
log(`sorting.getState: column="${columnName}"`);
|
|
639
452
|
await _autoInit();
|
|
640
|
-
|
|
641
|
-
throw new Error('No sorting strategy has been configured.');
|
|
642
|
-
const context = Object.assign(Object.assign({}, createStrategyContext()), { getHeaderCell: result.getHeaderCell });
|
|
643
|
-
return config.strategies.sorting.getSortState({ columnName, context });
|
|
453
|
+
return (0, sorting_1.getSortState)(sortingEnv(), columnName);
|
|
644
454
|
}
|
|
645
455
|
},
|
|
646
456
|
// ─── Shared async row iterator ───────────────────────────────────────────
|
|
@@ -708,7 +518,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
708
518
|
},
|
|
709
519
|
// ─── Row iteration (delegated to engine/tableIteration) ──────────────────
|
|
710
520
|
forEach: async (callback, options = {}) => {
|
|
711
|
-
log(`forEach: options=${safeStringify(options)}`);
|
|
521
|
+
log(`forEach: options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
712
522
|
await _autoInit();
|
|
713
523
|
await (0, tableIteration_1.runForEach)({
|
|
714
524
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
@@ -723,7 +533,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
723
533
|
}, callback, options);
|
|
724
534
|
},
|
|
725
535
|
map: async (callback, options = {}) => {
|
|
726
|
-
log(`map: options=${safeStringify(options)}`);
|
|
536
|
+
log(`map: options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
727
537
|
await _autoInit();
|
|
728
538
|
return (0, tableIteration_1.runMap)({
|
|
729
539
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
@@ -738,7 +548,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
738
548
|
}, callback, options);
|
|
739
549
|
},
|
|
740
550
|
toArray: async (callback, options = {}) => {
|
|
741
|
-
log(`toArray: options=${safeStringify(options)}`);
|
|
551
|
+
log(`toArray: options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
742
552
|
const cb = callback !== null && callback !== void 0 ? callback : (({ row }) => row.toJSON());
|
|
743
553
|
try {
|
|
744
554
|
return await result.map(cb, options);
|
|
@@ -748,7 +558,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
748
558
|
}
|
|
749
559
|
},
|
|
750
560
|
filter: async (predicate, options = {}) => {
|
|
751
|
-
log(`filter: options=${safeStringify(options)}`);
|
|
561
|
+
log(`filter: options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
752
562
|
await _autoInit();
|
|
753
563
|
return (0, tableIteration_1.runFilter)({
|
|
754
564
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import type { FilterValue, FinalTableConfig } from '../types';
|
|
3
|
+
/** `Name="Alice", Status=/act/i` — readable in traces, unlike JSON.stringify (which turns RegExp into `{}`). */
|
|
4
|
+
export declare const formatFilters: (filters: Record<string, FilterValue>) => string;
|
|
5
|
+
export declare const describeRowByFilters: (filters: Record<string, FilterValue>) => string;
|
|
6
|
+
/** `SmartRow #3`, or `SmartRow #3 where Dept="Eng"` when the row came from a filtered search. */
|
|
7
|
+
export declare const describeRowByIndex: (rowIndex: number, filters?: Record<string, FilterValue>) => string;
|
|
8
|
+
export declare const describeSentinelRow: (filters: Record<string, FilterValue>) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Applies `locator.describe()` (Playwright 1.53+) so traces, HTML reports and error messages show a
|
|
11
|
+
* readable name instead of the raw selector chain. Older Playwright versions get the locator back unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export declare const describeLocator: (locator: Locator, description: string, config?: FinalTableConfig) => Locator;
|
|
14
|
+
/** The description already applied to a locator, or undefined (unsupported or never described). */
|
|
15
|
+
export declare const getLocatorDescription: (locator: Locator) => string | undefined;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLocatorDescription = exports.describeLocator = exports.describeSentinelRow = exports.describeRowByIndex = exports.describeRowByFilters = exports.formatFilters = void 0;
|
|
4
|
+
const debugUtils_1 = require("./debugUtils");
|
|
5
|
+
const MAX_VALUE_LENGTH = 40;
|
|
6
|
+
const locatorDescriptions = new WeakMap();
|
|
7
|
+
let loggedUnsupported = false;
|
|
8
|
+
const formatFilterValue = (value) => {
|
|
9
|
+
if (value instanceof RegExp)
|
|
10
|
+
return value.toString();
|
|
11
|
+
if (typeof value === 'function')
|
|
12
|
+
return '<locator filter>';
|
|
13
|
+
if (typeof value === 'number')
|
|
14
|
+
return String(value);
|
|
15
|
+
const text = value.length > MAX_VALUE_LENGTH ? `${value.slice(0, MAX_VALUE_LENGTH)}…` : value;
|
|
16
|
+
return JSON.stringify(text);
|
|
17
|
+
};
|
|
18
|
+
/** `Name="Alice", Status=/act/i` — readable in traces, unlike JSON.stringify (which turns RegExp into `{}`). */
|
|
19
|
+
const formatFilters = (filters) => Object.entries(filters).map(([col, value]) => `${col}=${formatFilterValue(value)}`).join(', ');
|
|
20
|
+
exports.formatFilters = formatFilters;
|
|
21
|
+
const describeRowByFilters = (filters) => {
|
|
22
|
+
const formatted = (0, exports.formatFilters)(filters);
|
|
23
|
+
return formatted ? `SmartRow where ${formatted}` : 'SmartRow';
|
|
24
|
+
};
|
|
25
|
+
exports.describeRowByFilters = describeRowByFilters;
|
|
26
|
+
/** `SmartRow #3`, or `SmartRow #3 where Dept="Eng"` when the row came from a filtered search. */
|
|
27
|
+
const describeRowByIndex = (rowIndex, filters = {}) => {
|
|
28
|
+
const formatted = (0, exports.formatFilters)(filters);
|
|
29
|
+
return formatted ? `SmartRow #${rowIndex} where ${formatted}` : `SmartRow #${rowIndex}`;
|
|
30
|
+
};
|
|
31
|
+
exports.describeRowByIndex = describeRowByIndex;
|
|
32
|
+
const describeSentinelRow = (filters) => {
|
|
33
|
+
const formatted = (0, exports.formatFilters)(filters);
|
|
34
|
+
return formatted ? `SmartRow not found: ${formatted}` : 'SmartRow not found';
|
|
35
|
+
};
|
|
36
|
+
exports.describeSentinelRow = describeSentinelRow;
|
|
37
|
+
/**
|
|
38
|
+
* Applies `locator.describe()` (Playwright 1.53+) so traces, HTML reports and error messages show a
|
|
39
|
+
* readable name instead of the raw selector chain. Older Playwright versions get the locator back unchanged.
|
|
40
|
+
*/
|
|
41
|
+
const describeLocator = (locator, description, config) => {
|
|
42
|
+
if (typeof locator.describe === 'function') {
|
|
43
|
+
const described = locator.describe(description);
|
|
44
|
+
locatorDescriptions.set(described, description);
|
|
45
|
+
return described;
|
|
46
|
+
}
|
|
47
|
+
if ((config === null || config === void 0 ? void 0 : config.debug) && !loggedUnsupported) {
|
|
48
|
+
loggedUnsupported = true;
|
|
49
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'Upgrade @playwright/test to 1.53+ to see SmartRow / SmartCell names in traces and error messages');
|
|
50
|
+
}
|
|
51
|
+
return locator;
|
|
52
|
+
};
|
|
53
|
+
exports.describeLocator = describeLocator;
|
|
54
|
+
/** The description already applied to a locator, or undefined (unsupported or never described). */
|
|
55
|
+
const getLocatorDescription = (locator) => { var _a; return typeof locator.description === 'function' ? (_a = locator.description()) !== null && _a !== void 0 ? _a : undefined : locatorDescriptions.get(locator); };
|
|
56
|
+
exports.getLocatorDescription = getLocatorDescription;
|
|
@@ -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();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeStringify = void 0;
|
|
4
|
+
/** JSON.stringify for debug logs: functions become "[Function]" and cycles don't throw. */
|
|
5
|
+
const safeStringify = (obj) => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.stringify(obj, (_key, value) => {
|
|
8
|
+
if (typeof value === 'function') {
|
|
9
|
+
return '[Function]';
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
return '[unserializable]';
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.safeStringify = safeStringify;
|
package/package.json
CHANGED