@rickcedwhat/playwright-smart-table 6.20.1-next.e946424 → 6.21.0

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.
Files changed (38) hide show
  1. package/dist/engine/rowFinder.js +58 -62
  2. package/dist/engine/scanPages.d.ts +58 -0
  3. package/dist/engine/scanPages.js +46 -0
  4. package/dist/engine/tableIteration.d.ts +1 -0
  5. package/dist/engine/tableIteration.js +24 -20
  6. package/dist/filterEngine.d.ts +4 -0
  7. package/dist/filterEngine.js +15 -4
  8. package/dist/index.d.ts +6 -2
  9. package/dist/index.js +5 -1
  10. package/dist/packageVersion.d.ts +1 -1
  11. package/dist/packageVersion.js +1 -1
  12. package/dist/plugins/index.d.ts +51 -10
  13. package/dist/plugins/index.js +14 -6
  14. package/dist/presets/glide/index.d.ts +1 -1
  15. package/dist/presets/glide/index.js +2 -2
  16. package/dist/presets/rdg.d.ts +1 -1
  17. package/dist/presets/rdg.js +4 -4
  18. package/dist/smartRow.d.ts +2 -2
  19. package/dist/smartRow.js +160 -49
  20. package/dist/strategies/columns.d.ts +5 -9
  21. package/dist/strategies/columns.js +0 -10
  22. package/dist/strategies/contentReady.d.ts +20 -0
  23. package/dist/strategies/contentReady.js +62 -0
  24. package/dist/strategies/filter.d.ts +0 -4
  25. package/dist/strategies/filter.js +0 -16
  26. package/dist/strategies/headers.d.ts +5 -0
  27. package/dist/strategies/headers.js +17 -9
  28. package/dist/strategies/index.d.ts +23 -15
  29. package/dist/strategies/index.js +9 -5
  30. package/dist/strategies/viewport.d.ts +4 -1
  31. package/dist/strategies/viewport.js +36 -13
  32. package/dist/typeContext.d.ts +1 -1
  33. package/dist/typeContext.js +45 -1
  34. package/dist/types.d.ts +43 -1
  35. package/dist/useTable.js +95 -42
  36. package/dist/utils/resolveCellLocator.d.ts +35 -0
  37. package/dist/utils/resolveCellLocator.js +52 -0
  38. package/package.json +1 -1
@@ -284,6 +284,9 @@ export type SmartRow<T = any> = Locator & {
284
284
  * Scrolls/paginates to bring this row into view.
285
285
  * Works when row position metadata is known (e.g., from getRowByIndex, findRow,
286
286
  * findRows, filter, or async iteration).
287
+ *
288
+ * Prefers \`strategies.viewport.scrollToRow\` when configured; otherwise falls back to
289
+ * Playwright \`scrollIntoViewIfNeeded()\`.
287
290
  * @throws Error if row position metadata is unknown
288
291
  */
289
292
  bringIntoView(): Promise<void>;
@@ -309,6 +312,12 @@ export type SmartRow<T = any> = Locator & {
309
312
 
310
313
  /**
311
314
  * Get the resolved value of any column — real, override, or synthetic.
315
+ *
316
+ * When \`strategies.viewport\` or \`strategies.navigation\` is configured, runs the same
317
+ * cell-navigation pipeline as \`toJSON\` / \`getCell().bringIntoView()\` so off-screen
318
+ * virtualized cells are mounted before reading. Without those strategies, reads the
319
+ * current DOM cell (Playwright auto-wait).
320
+ *
312
321
  * @param column - Column name (case-sensitive)
313
322
  * @returns The column value as a string
314
323
  */
@@ -440,6 +449,8 @@ export type PaginationStrategy = PaginationPrimitives;
440
449
 
441
450
  export type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;
442
451
 
452
+ export type ContentReadyStrategy = (row: Locator, page: Page) => Promise<void>;
453
+
443
454
 
444
455
 
445
456
  export type FillStrategy = (options: {
@@ -509,6 +520,7 @@ export interface ColumnOverride<TValue = any> {
509
520
  }
510
521
 
511
522
  export type { HeaderStrategy } from './strategies/headers';
523
+ export type { NavigationPrimitives, CellNavigationStrategy } from './strategies/columns';
512
524
 
513
525
  /**
514
526
  * Strategy to resolve column names (string or regex) to their index.
@@ -654,6 +666,24 @@ export interface TableStrategies {
654
666
  */
655
667
  resolveRowIndex?: (row: Locator) => Promise<RowIndexResult | undefined>;
656
668
 
669
+ /**
670
+ * Waits until a row's content has stabilized before cloning in
671
+ * \`toJSON({ atomic: true })\`. Needed for recycling virtualizers where the
672
+ * framework updates element positions synchronously but renders cell content
673
+ * asynchronously (e.g. react-window, react-virtuoso with React concurrent mode).
674
+ *
675
+ * Without this, the clone captures stale content from the previous occupant of the
676
+ * DOM slot — the element is at the correct position but React hasn't re-rendered yet.
677
+ *
678
+ * Use \`Strategies.ContentReady.textStable()\` for the built-in text-polling strategy.
679
+ *
680
+ * @example
681
+ * strategies: {
682
+ * contentReady: Strategies.ContentReady.textStable({ timeout: 500 }),
683
+ * }
684
+ */
685
+ contentReady?: ContentReadyStrategy;
686
+
657
687
  /**
658
688
  * Viewport oracle strategies for 2D virtualized tables (e.g. MUI DataGrid, AG Grid,
659
689
  * Braintrust-style grids where both rows and columns are virtualized simultaneously).
@@ -715,6 +745,9 @@ export interface TableConfig<T = any> {
715
745
  emptyState?: Locator;
716
746
  }
717
747
 
748
+ /**
749
+ * @internal Resolved config after defaults are applied. Prefer {@link TableConfig} in public code.
750
+ */
718
751
  export interface FinalTableConfig<T = any> extends TableConfig<T> {
719
752
  headerSelector: string | ((root: Locator) => Locator);
720
753
  rowSelector: string;
@@ -786,6 +819,11 @@ export type RowIterationOptions = {
786
819
  useBulkPagination?: boolean;
787
820
  };
788
821
 
822
+ /**
823
+ * Result of {@link useTable}. Implements \`AsyncIterable\` — \`for await (const { row } of table)\`
824
+ * uses the same page-walk as \`map\` / \`forEach\` (\`scanPages\` + runMap): overscan, loading,
825
+ * dedupe, and EOF final scan (#427).
826
+ */
789
827
  export interface TableResult<T = any> extends AsyncIterable<{ row: SmartRow<T>; rowIndex: number; index: number; pageIndex: number }> {
790
828
  /**
791
829
  * Represents the current page index of the table's DOM.
@@ -821,6 +859,9 @@ export interface TableResult<T = any> extends AsyncIterable<{ row: SmartRow<T>;
821
859
  * @note The sync path cannot compute a real \`rowIndex\`, so the returned SmartRow's
822
860
  * \`rowIndex\` is \`undefined\` (virtual-scroll positioning via \`bringIntoView()\` is limited).
823
861
  * Use \`findRow()\` (async) when you need a row with an accurate \`rowIndex\`.
862
+ * @note Cannot filter by \`syntheticColumns\` or \`columnOverrides.read\` keys — those need
863
+ * async evaluation via \`findRow()\` / \`findRows()\`. DOM filters use \`strategies.getCellLocator\`
864
+ * when configured (column-virtualized grids), otherwise \`cellSelector\` + column index.
824
865
  */
825
866
  getRow: (
826
867
  filters: Record<string, FilterValue>,
@@ -890,7 +931,9 @@ export interface TableResult<T = any> extends AsyncIterable<{ row: SmartRow<T>;
890
931
  ) => Promise<SmartRowArray<T>>;
891
932
 
892
933
  /**
893
- * Navigates to a specific column using the configured CellNavigationStrategy.
934
+ * Resolves the named column and scrolls it into view.
935
+ * Prefers \`strategies.viewport.scrollToColumn\` when configured; otherwise scrolls the
936
+ * header cell via Playwright \`scrollIntoViewIfNeeded()\`.
894
937
  */
895
938
  scrollToColumn: (columnName: string) => Promise<void>;
896
939
 
@@ -910,6 +953,7 @@ export interface TableResult<T = any> extends AsyncIterable<{ row: SmartRow<T>;
910
953
  mapColumn<R = string>(columnName: string, options?: RowIterationOptions): Promise<R[]>;
911
954
 
912
955
  /**
956
+ * @deprecated Use \`mapColumn\` (or \`map\`) instead. Will be removed in v7.0.0.
913
957
  * Iterates over rows and extracts the value of a single column as strings.
914
958
  * @param columnName - The name of the column to extract
915
959
  * @param options - Iteration options
package/dist/types.d.ts CHANGED
@@ -269,6 +269,9 @@ export type SmartRow<T = any> = Locator & {
269
269
  * Scrolls/paginates to bring this row into view.
270
270
  * Works when row position metadata is known (e.g., from getRowByIndex, findRow,
271
271
  * findRows, filter, or async iteration).
272
+ *
273
+ * Prefers `strategies.viewport.scrollToRow` when configured; otherwise falls back to
274
+ * Playwright `scrollIntoViewIfNeeded()`.
272
275
  * @throws Error if row position metadata is unknown
273
276
  */
274
277
  bringIntoView(): Promise<void>;
@@ -292,6 +295,12 @@ export type SmartRow<T = any> = Locator & {
292
295
  smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;
293
296
  /**
294
297
  * Get the resolved value of any column — real, override, or synthetic.
298
+ *
299
+ * When `strategies.viewport` or `strategies.navigation` is configured, runs the same
300
+ * cell-navigation pipeline as `toJSON` / `getCell().bringIntoView()` so off-screen
301
+ * virtualized cells are mounted before reading. Without those strategies, reads the
302
+ * current DOM cell (Playwright auto-wait).
303
+ *
295
304
  * @param column - Column name (case-sensitive)
296
305
  * @returns The column value as a string
297
306
  */
@@ -403,6 +412,7 @@ export interface PaginationPrimitives {
403
412
  }
404
413
  export type PaginationStrategy = PaginationPrimitives;
405
414
  export type DedupeStrategy = (row: SmartRow) => string | number | Promise<string | number>;
415
+ export type ContentReadyStrategy = (row: Locator, page: Page) => Promise<void>;
406
416
  export type FillStrategy = (options: {
407
417
  row: SmartRow;
408
418
  columnName: string;
@@ -467,6 +477,7 @@ export interface ColumnOverride<TValue = any> {
467
477
  import { HeaderStrategy } from './strategies/headers';
468
478
  export type { HeaderStrategy } from './strategies/headers';
469
479
  import { NavigationPrimitives } from './strategies/columns';
480
+ export type { NavigationPrimitives, CellNavigationStrategy } from './strategies/columns';
470
481
  /**
471
482
  * Strategy to resolve column names (string or regex) to their index.
472
483
  */
@@ -597,6 +608,23 @@ export interface TableStrategies {
597
608
  * }
598
609
  */
599
610
  resolveRowIndex?: (row: Locator) => Promise<RowIndexResult | undefined>;
611
+ /**
612
+ * Waits until a row's content has stabilized before cloning in
613
+ * `toJSON({ atomic: true })`. Needed for recycling virtualizers where the
614
+ * framework updates element positions synchronously but renders cell content
615
+ * asynchronously (e.g. react-window, react-virtuoso with React concurrent mode).
616
+ *
617
+ * Without this, the clone captures stale content from the previous occupant of the
618
+ * DOM slot — the element is at the correct position but React hasn't re-rendered yet.
619
+ *
620
+ * Use `Strategies.ContentReady.textStable()` for the built-in text-polling strategy.
621
+ *
622
+ * @example
623
+ * strategies: {
624
+ * contentReady: Strategies.ContentReady.textStable({ timeout: 500 }),
625
+ * }
626
+ */
627
+ contentReady?: ContentReadyStrategy;
600
628
  /**
601
629
  * Viewport oracle strategies for 2D virtualized tables (e.g. MUI DataGrid, AG Grid,
602
630
  * Braintrust-style grids where both rows and columns are virtualized simultaneously).
@@ -657,6 +685,9 @@ export interface TableConfig<T = any> {
657
685
  */
658
686
  emptyState?: Locator;
659
687
  }
688
+ /**
689
+ * @internal Resolved config after defaults are applied. Prefer {@link TableConfig} in public code.
690
+ */
660
691
  export interface FinalTableConfig<T = any> extends TableConfig<T> {
661
692
  headerSelector: string | ((root: Locator) => Locator);
662
693
  rowSelector: string;
@@ -725,6 +756,11 @@ export type RowIterationOptions = {
725
756
  */
726
757
  useBulkPagination?: boolean;
727
758
  };
759
+ /**
760
+ * Result of {@link useTable}. Implements `AsyncIterable` — `for await (const { row } of table)`
761
+ * uses the same page-walk as `map` / `forEach` (`scanPages` + runMap): overscan, loading,
762
+ * dedupe, and EOF final scan (#427).
763
+ */
728
764
  export interface TableResult<T = any> extends AsyncIterable<{
729
765
  row: SmartRow<T>;
730
766
  rowIndex: number;
@@ -762,6 +798,9 @@ export interface TableResult<T = any> extends AsyncIterable<{
762
798
  * @note The sync path cannot compute a real `rowIndex`, so the returned SmartRow's
763
799
  * `rowIndex` is `undefined` (virtual-scroll positioning via `bringIntoView()` is limited).
764
800
  * Use `findRow()` (async) when you need a row with an accurate `rowIndex`.
801
+ * @note Cannot filter by `syntheticColumns` or `columnOverrides.read` keys — those need
802
+ * async evaluation via `findRow()` / `findRows()`. DOM filters use `strategies.getCellLocator`
803
+ * when configured (column-virtualized grids), otherwise `cellSelector` + column index.
765
804
  */
766
805
  getRow: (filters: Record<string, FilterValue>, options?: {
767
806
  exact?: boolean;
@@ -823,7 +862,9 @@ export interface TableResult<T = any> extends AsyncIterable<{
823
862
  useBulkPagination?: boolean;
824
863
  }) => Promise<SmartRowArray<T>>;
825
864
  /**
826
- * Navigates to a specific column using the configured CellNavigationStrategy.
865
+ * Resolves the named column and scrolls it into view.
866
+ * Prefers `strategies.viewport.scrollToColumn` when configured; otherwise scrolls the
867
+ * header cell via Playwright `scrollIntoViewIfNeeded()`.
827
868
  */
828
869
  scrollToColumn: (columnName: string) => Promise<void>;
829
870
  /**
@@ -843,6 +884,7 @@ export interface TableResult<T = any> extends AsyncIterable<{
843
884
  */
844
885
  mapColumn<R = string>(columnName: string, options?: RowIterationOptions): Promise<R[]>;
845
886
  /**
887
+ * @deprecated Use `mapColumn` (or `map`) instead. Will be removed in v7.0.0.
846
888
  * Iterates over rows and extracts the value of a single column as strings.
847
889
  * @param columnName - The name of the column to extract
848
890
  * @param options - Iteration options
package/dist/useTable.js CHANGED
@@ -28,10 +28,10 @@ const tableMapper_1 = require("./engine/tableMapper");
28
28
  const rowFinder_1 = require("./engine/rowFinder");
29
29
  const tableIteration_1 = require("./engine/tableIteration");
30
30
  const rowResolution_1 = require("./engine/rowResolution");
31
+ const scanPages_1 = require("./engine/scanPages");
31
32
  const debugUtils_1 = require("./utils/debugUtils");
32
33
  const smartRowArray_1 = require("./utils/smartRowArray");
33
34
  const elementTracker_1 = require("./utils/elementTracker");
34
- const navigationBarrier_1 = require("./utils/navigationBarrier");
35
35
  // Helper to safely serialize objects containing functions for logging
36
36
  const safeStringify = (obj) => {
37
37
  try {
@@ -145,11 +145,11 @@ const useTable = (rootLocator, configOptions = {}) => {
145
145
  // Placeholder for the final table object
146
146
  let finalTable = null;
147
147
  // Helper factory
148
- const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier, rowSelector) => {
148
+ const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier, rowSelector, renderWindowPosition = false) => {
149
149
  const effectiveLocator = rowSelector
150
150
  ? rootLocator.locator(rowSelector)
151
151
  : rowLocator;
152
- const sr = (0, smartRow_1.default)(effectiveLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier);
152
+ const sr = (0, smartRow_1.default)(effectiveLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier, renderWindowPosition);
153
153
  if (rowSelector)
154
154
  sr._selfHealing = true;
155
155
  return sr;
@@ -277,12 +277,19 @@ const useTable = (rootLocator, configOptions = {}) => {
277
277
  return result;
278
278
  },
279
279
  scrollToColumn: async (columnName) => {
280
+ var _a;
280
281
  log(`scrollToColumn: column="${columnName}"`);
281
282
  const map = await tableMapper.getMap();
282
283
  const idx = map.get(columnName);
283
284
  if (idx === undefined)
284
285
  throw _createColumnError(columnName, map);
285
- // Use header cell for scrolling
286
+ // Prefer viewport strategy when configured (#430). Raw scrollIntoViewIfNeeded on a
287
+ // header can shift Y and evict virtualized rows; viewport.scrollToColumn is X-aware.
288
+ const viewportScroll = (_a = config.strategies.viewport) === null || _a === void 0 ? void 0 : _a.scrollToColumn;
289
+ if (viewportScroll) {
290
+ await viewportScroll(createStrategyContext(), idx);
291
+ return;
292
+ }
286
293
  const headerCell = resolve(config.headerSelector, rootLocator).nth(idx);
287
294
  await headerCell.scrollIntoViewIfNeeded();
288
295
  },
@@ -381,19 +388,34 @@ const useTable = (rootLocator, configOptions = {}) => {
381
388
  let pagesScanned = 1;
382
389
  let paginationError;
383
390
  try {
384
- while (true) {
391
+ const scanned = await (0, scanPages_1.scanPages)({
392
+ advancePage: _advancePage,
393
+ getCurrentPageIndex: () => tableState.currentPageIndex,
394
+ config,
395
+ waitForReady: async () => {
396
+ if (!isTableLoading)
397
+ return;
398
+ const ctx = createStrategyContext();
399
+ while (await isTableLoading(ctx)) {
400
+ log('countRows: table is loading... waiting');
401
+ await rootLocator.page().waitForTimeout(200);
402
+ }
403
+ },
404
+ }, async ({ pagesScanned: pageNum }) => {
385
405
  const rowLocators = resolveRows();
386
406
  const newIndices = await tracker.getUnseenIndices(rowLocators);
387
407
  const candidates = await rowLocators.all();
388
408
  const matched = await countPostFilterMatches(candidates, newIndices);
389
409
  total += matched;
390
- log(`countRows: page ${pagesScanned} — ${matched} row(s) (running total: ${total})`);
391
- if (pagesScanned >= effectiveMaxPages)
392
- break;
393
- if (!await _advancePage(false))
394
- break;
395
- pagesScanned++;
396
- }
410
+ log(`countRows: page ${pageNum} — ${matched} row(s) (running total: ${total})`);
411
+ return 'continue';
412
+ }, {
413
+ maxPages: effectiveMaxPages,
414
+ useBulk: false,
415
+ label: 'countRows',
416
+ finalScanOnEof: true,
417
+ });
418
+ pagesScanned = scanned.pagesScanned;
397
419
  }
398
420
  catch (e) {
399
421
  paginationError = e;
@@ -437,6 +459,10 @@ const useTable = (rootLocator, configOptions = {}) => {
437
459
  return (text || '').trim();
438
460
  }, options);
439
461
  },
462
+ /**
463
+ * @deprecated Use `mapColumn` (or `map`) instead. Will be removed in v7.0.0.
464
+ * Iterates over rows and extracts the value of a single column as strings.
465
+ */
440
466
  getColumnValues: async (columnName, options = {}) => {
441
467
  log(`getColumnValues: column="${columnName}" options=${safeStringify(options)}`);
442
468
  const values = await result.mapColumn(columnName, options);
@@ -472,6 +498,11 @@ const useTable = (rootLocator, configOptions = {}) => {
472
498
  throw new Error(`getRow() cannot filter by synthetic column(s): ${syntheticKeys.join(', ')}. ` +
473
499
  `Use findRow() instead — synthetic columns require async evaluation.`);
474
500
  }
501
+ const overrideKeys = Object.keys(filters).filter(k => { var _a, _b; return (_b = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[k]) === null || _b === void 0 ? void 0 : _b.read; });
502
+ if (overrideKeys.length > 0) {
503
+ throw new Error(`getRow() cannot filter by columnOverrides.read column(s): ${overrideKeys.join(', ')}. ` +
504
+ `Use findRow() instead — override columns require async evaluation.`);
505
+ }
475
506
  const map = tableMapper.getMapSync();
476
507
  if (!map)
477
508
  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.');
@@ -485,7 +516,7 @@ const useTable = (rootLocator, configOptions = {}) => {
485
516
  if (!map)
486
517
  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.');
487
518
  const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
488
- return _makeSmart(rowLocator, map, index);
519
+ return _makeSmart(rowLocator, map, index, undefined, undefined, undefined, true);
489
520
  },
490
521
  findRowByIndex: async (index, options) => {
491
522
  var _a, _b;
@@ -606,43 +637,65 @@ const useTable = (rootLocator, configOptions = {}) => {
606
637
  }
607
638
  },
608
639
  // ─── Shared async row iterator ───────────────────────────────────────────
640
+ /**
641
+ * Streams rows using the same page-walk + collection path as `map` / `forEach`
642
+ * (`scanPages` + runMap): overscan filtering, loading-before-dedupe, EOF final
643
+ * scan, and config dedupe. Concurrency is sequential so yields stay ordered. (#427)
644
+ */
609
645
  [Symbol.asyncIterator]() {
610
646
  return __asyncGenerator(this, arguments, function* _a() {
611
- var _b;
612
647
  yield __await(_autoInit());
613
- const map = tableMapper.getMapSync();
614
- const effectiveMaxPages = config.maxPages;
615
- const tracker = new elementTracker_1.ElementTracker('iterator');
616
- const useBulk = false; // iterator has no options; default goNext
617
- log(`iterator: starting (maxPages=${effectiveMaxPages})`);
648
+ log(`iterator: starting via runMap (maxPages=${config.maxPages})`);
649
+ const queue = [];
650
+ let notify;
651
+ let release;
652
+ let finished = false;
653
+ let cancelled = false;
654
+ let runError;
655
+ const wake = () => {
656
+ notify === null || notify === void 0 ? void 0 : notify();
657
+ notify = undefined;
658
+ };
659
+ const runPromise = (0, tableIteration_1.runMap)({
660
+ getRowLocators: () => resolve(config.rowSelector, rootLocator),
661
+ getMap: () => tableMapper.getMapSync(),
662
+ advancePage: _advancePage,
663
+ makeSmartRow: (loc, map, idx, pageIdx, barrier, sel) => _makeSmart(loc, map, idx, pageIdx, barrier, sel),
664
+ createSmartRowArray: smartRowArray_1.createSmartRowArray,
665
+ config,
666
+ getPage: () => rootLocator.page(),
667
+ getCurrentPageIndex: () => tableState.currentPageIndex,
668
+ getContext: () => createStrategyContext(),
669
+ }, async (ctx) => {
670
+ queue.push({
671
+ row: ctx.row,
672
+ index: ctx.index,
673
+ rowIndex: ctx.rowIndex,
674
+ pageIndex: ctx.pageIndex,
675
+ });
676
+ wake();
677
+ await new Promise((resolve) => { release = resolve; });
678
+ if (cancelled)
679
+ ctx.stop();
680
+ }, { concurrency: 'sequential' }, 'iterator').then(() => { finished = true; wake(); }, (err) => { runError = err; finished = true; wake(); });
618
681
  try {
619
- let rowIndex = 0;
620
- let pagesScanned = 1;
621
- while (true) {
622
- const rowLocators = resolve(config.rowSelector, rootLocator);
623
- const newIndices = yield __await(tracker.getUnseenIndices(rowLocators));
624
- const pageRows = yield __await(rowLocators.all());
625
- const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
626
- for (const idx of newIndices) {
627
- // index = visit-order counter; rowIndex = logical/data index (B-hybrid, #362).
628
- const resolved = yield __await((0, rowResolution_1.resolveLogicalRowIndex)(pageRows[idx], config, () => rowIndex));
629
- const logical = (_b = resolved === null || resolved === void 0 ? void 0 : resolved.index) !== null && _b !== void 0 ? _b : rowIndex;
630
- const sr = _makeSmart(pageRows[idx], map, logical, tableState.currentPageIndex, barrier, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
631
- // Iterator rows share one per-page DOM snapshot (positional locators); disable
632
- // toJSON's scroll-back recovery so reading one row can't invalidate the others (#366).
633
- sr._inBatch = true;
634
- yield yield __await({ row: sr, index: rowIndex, rowIndex: logical, pageIndex: tableState.currentPageIndex });
635
- rowIndex++;
682
+ while (!finished || queue.length > 0) {
683
+ if (queue.length === 0) {
684
+ yield __await(new Promise((resolve) => { notify = resolve; }));
685
+ }
686
+ while (queue.length > 0) {
687
+ yield yield __await(queue.shift());
688
+ release === null || release === void 0 ? void 0 : release();
689
+ release = undefined;
636
690
  }
637
- if (pagesScanned >= effectiveMaxPages)
638
- break;
639
- if (!(yield __await(_advancePage(useBulk))))
640
- break;
641
- pagesScanned++;
691
+ if (runError)
692
+ throw runError;
642
693
  }
643
694
  }
644
695
  finally {
645
- yield __await(tracker.cleanup(rootLocator.page()));
696
+ cancelled = true;
697
+ release === null || release === void 0 ? void 0 : release();
698
+ yield __await(runPromise.catch(() => { }));
646
699
  }
647
700
  });
648
701
  },
@@ -0,0 +1,35 @@
1
+ import type { Locator, Page } from '@playwright/test';
2
+ import type { FinalTableConfig, Selector } from '../types';
3
+ /**
4
+ * Resolve a cell locator for a concrete row — prefers `strategies.getCellLocator`,
5
+ * otherwise `cellSelector` + `.nth(columnIndex)`.
6
+ */
7
+ export declare function resolveCellLocator(args: {
8
+ config: FinalTableConfig;
9
+ resolve: (selector: Selector, parent: Locator | Page) => Locator;
10
+ row: Locator;
11
+ root: Locator;
12
+ columnName: string;
13
+ columnIndex: number;
14
+ rowIndex?: number;
15
+ page?: Page;
16
+ }): Locator;
17
+ /**
18
+ * Cell locator for Playwright `rows.filter({ has })`.
19
+ *
20
+ * When `getCellLocator` is set, pass `page.locator(':scope')` as the row so the
21
+ * strategy's `row.locator(...)` chain stays relative to each candidate row under
22
+ * `filter({ has })`. Using the rows collection or a document-rooted parent nests
23
+ * the row selector and matches nothing.
24
+ *
25
+ * Without `getCellLocator`, keeps the historical page-scoped `cellSelector.nth(i)`
26
+ * template that Playwright re-bases into each row.
27
+ */
28
+ export declare function resolveCellLocatorForFilter(args: {
29
+ config: FinalTableConfig;
30
+ resolve: (selector: Selector, parent: Locator | Page) => Locator;
31
+ page: Page;
32
+ root?: Locator;
33
+ columnName: string;
34
+ columnIndex: number;
35
+ }): Locator;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveCellLocator = resolveCellLocator;
4
+ exports.resolveCellLocatorForFilter = resolveCellLocatorForFilter;
5
+ /**
6
+ * Resolve a cell locator for a concrete row — prefers `strategies.getCellLocator`,
7
+ * otherwise `cellSelector` + `.nth(columnIndex)`.
8
+ */
9
+ function resolveCellLocator(args) {
10
+ var _a, _b;
11
+ const { config, resolve, row, root, columnName, columnIndex, rowIndex } = args;
12
+ const page = (_a = args.page) !== null && _a !== void 0 ? _a : root.page();
13
+ if ((_b = config.strategies) === null || _b === void 0 ? void 0 : _b.getCellLocator) {
14
+ return config.strategies.getCellLocator({
15
+ row,
16
+ root,
17
+ columnName,
18
+ columnIndex,
19
+ rowIndex,
20
+ page,
21
+ config,
22
+ });
23
+ }
24
+ return resolve(config.cellSelector, row).nth(columnIndex);
25
+ }
26
+ /**
27
+ * Cell locator for Playwright `rows.filter({ has })`.
28
+ *
29
+ * When `getCellLocator` is set, pass `page.locator(':scope')` as the row so the
30
+ * strategy's `row.locator(...)` chain stays relative to each candidate row under
31
+ * `filter({ has })`. Using the rows collection or a document-rooted parent nests
32
+ * the row selector and matches nothing.
33
+ *
34
+ * Without `getCellLocator`, keeps the historical page-scoped `cellSelector.nth(i)`
35
+ * template that Playwright re-bases into each row.
36
+ */
37
+ function resolveCellLocatorForFilter(args) {
38
+ var _a;
39
+ const { config, resolve, page, root, columnName, columnIndex } = args;
40
+ if ((_a = config.strategies) === null || _a === void 0 ? void 0 : _a.getCellLocator) {
41
+ const scopeRow = page.locator(':scope');
42
+ return config.strategies.getCellLocator({
43
+ row: scopeRow,
44
+ root: root !== null && root !== void 0 ? root : scopeRow,
45
+ columnName,
46
+ columnIndex,
47
+ page,
48
+ config,
49
+ });
50
+ }
51
+ return resolve(config.cellSelector, page).nth(columnIndex);
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.20.1-next.e946424",
3
+ "version": "6.21.0",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",