@rickcedwhat/playwright-smart-table 6.18.0 → 6.20.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.
package/dist/useTable.js CHANGED
@@ -27,6 +27,7 @@ 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 rowResolution_1 = require("./engine/rowResolution");
30
31
  const debugUtils_1 = require("./utils/debugUtils");
31
32
  const smartRowArray_1 = require("./utils/smartRowArray");
32
33
  const elementTracker_1 = require("./utils/elementTracker");
@@ -69,9 +70,11 @@ const useTable = (rootLocator, configOptions = {}) => {
69
70
  const vp = config.strategies.viewport;
70
71
  let colRangeCache = null;
71
72
  let rowRangeCache = null;
73
+ let rowIndicesCache = null;
72
74
  _clearViewportCache = () => {
73
75
  colRangeCache = null;
74
76
  rowRangeCache = null;
77
+ rowIndicesCache = null;
75
78
  };
76
79
  config.strategies.viewport = Object.assign(Object.assign({}, vp), { getVisibleColumnRange: vp.getVisibleColumnRange
77
80
  ? async (ctx) => {
@@ -85,15 +88,23 @@ const useTable = (rootLocator, configOptions = {}) => {
85
88
  rowRangeCache = await vp.getVisibleRowRange(ctx);
86
89
  return rowRangeCache;
87
90
  }
91
+ : undefined, getVisibleRowIndices: vp.getVisibleRowIndices
92
+ ? async (ctx) => {
93
+ if (!rowIndicesCache)
94
+ rowIndicesCache = await vp.getVisibleRowIndices(ctx);
95
+ return rowIndicesCache;
96
+ }
88
97
  : undefined, scrollToColumn: vp.scrollToColumn
89
98
  ? async (ctx, colIndex) => {
90
99
  colRangeCache = null;
91
100
  rowRangeCache = null;
101
+ rowIndicesCache = null;
92
102
  await vp.scrollToColumn(ctx, colIndex);
93
103
  }
94
104
  : undefined, scrollToRow: vp.scrollToRow
95
105
  ? async (ctx, rowIndex) => {
96
106
  rowRangeCache = null;
107
+ rowIndicesCache = null;
97
108
  await vp.scrollToRow(ctx, rowIndex);
98
109
  }
99
110
  : undefined });
@@ -134,10 +145,16 @@ const useTable = (rootLocator, configOptions = {}) => {
134
145
  // Placeholder for the final table object
135
146
  let finalTable = null;
136
147
  // Helper factory
137
- const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier) => {
138
- return (0, smartRow_1.default)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier);
148
+ const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier, rowSelector) => {
149
+ const effectiveLocator = rowSelector
150
+ ? rootLocator.locator(rowSelector)
151
+ : rowLocator;
152
+ const sr = (0, smartRow_1.default)(effectiveLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier);
153
+ if (rowSelector)
154
+ sr._selfHealing = true;
155
+ return sr;
139
156
  };
140
- const tableState = { currentPageIndex: 0 };
157
+ const tableState = { currentPageIndex: 0, empty: false };
141
158
  const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart, tableState, (useBulk) => _advancePage(useBulk));
142
159
  /** Builds a full TableContext/StrategyContext with getHeaderCell, getHeaders, scrollToColumn. Set after result is created. */
143
160
  let createStrategyContext = () => ({ root: rootLocator, config, page: rootLocator.page(), resolve });
@@ -215,7 +232,7 @@ const useTable = (rootLocator, configOptions = {}) => {
215
232
  set currentPageIndex(v) { tableState.currentPageIndex = v; },
216
233
  init: async (options) => {
217
234
  var _a;
218
- if (tableMapper.isInitialized())
235
+ if (tableMapper.isInitialized() || tableState.empty)
219
236
  return result;
220
237
  if (config.strategies.sorting)
221
238
  (0, validation_1.validateSortingStrategy)(config.strategies.sorting);
@@ -223,7 +240,21 @@ const useTable = (rootLocator, configOptions = {}) => {
223
240
  (0, validation_1.validateFillStrategy)(config.strategies.fill);
224
241
  (0, debugUtils_1.warnIfDebugInCI)(config);
225
242
  (0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
226
- const map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
243
+ let map;
244
+ try {
245
+ map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
246
+ }
247
+ catch (headerError) {
248
+ if (config.emptyState) {
249
+ const visible = await config.emptyState.isVisible().catch(() => false);
250
+ if (visible) {
251
+ tableState.empty = true;
252
+ (0, debugUtils_1.logDebug)(config, 'info', 'init: header resolution failed but emptyState locator is visible — table is empty');
253
+ return result;
254
+ }
255
+ }
256
+ throw headerError;
257
+ }
227
258
  (0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${map.size} columns`, Array.from(map.keys()));
228
259
  if ((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.detectCurrentPage) {
229
260
  try {
@@ -256,9 +287,10 @@ const useTable = (rootLocator, configOptions = {}) => {
256
287
  await headerCell.scrollIntoViewIfNeeded();
257
288
  },
258
289
  getHeaders: async () => {
290
+ var _a;
259
291
  log("getHeaders: fetching available columns");
260
292
  const map = await tableMapper.getMap();
261
- return Array.from(map.keys());
293
+ return [...map.keys(), ...Object.keys((_a = config.syntheticColumns) !== null && _a !== void 0 ? _a : {})];
262
294
  },
263
295
  getHeaderCell: async (columnName) => {
264
296
  log(`getHeaderCell: column="${columnName}"`);
@@ -268,25 +300,86 @@ const useTable = (rootLocator, configOptions = {}) => {
268
300
  throw _createColumnError(columnName, map, 'header cell');
269
301
  return resolve(config.headerSelector, rootLocator).nth(idx);
270
302
  },
271
- countRows: async () => {
303
+ countRows: async (filters, options) => {
304
+ var _a, _b, _c, _d;
305
+ if (tableState.empty)
306
+ return 0;
272
307
  await _autoInit();
308
+ const filtersRecord = (filters !== null && filters !== void 0 ? filters : {});
309
+ const hasFilters = Object.keys(filtersRecord).length > 0;
310
+ const effectiveMaxPages = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
273
311
  const pag = config.strategies.pagination;
274
- const hasPagination = config.maxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
312
+ const hasPagination = effectiveMaxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
313
+ const domFilters = {};
314
+ const overrideFilters = {};
315
+ const syntheticFilters = {};
316
+ if (hasFilters) {
317
+ for (const [key, value] of Object.entries(filtersRecord)) {
318
+ if ((_b = config.syntheticColumns) === null || _b === void 0 ? void 0 : _b[key]) {
319
+ syntheticFilters[key] = value;
320
+ }
321
+ else if ((_d = (_c = config.columnOverrides) === null || _c === void 0 ? void 0 : _c[key]) === null || _d === void 0 ? void 0 : _d.read) {
322
+ overrideFilters[key] = value;
323
+ }
324
+ else {
325
+ domFilters[key] = value;
326
+ }
327
+ }
328
+ }
329
+ const hasPostFilters = Object.keys(overrideFilters).length > 0 || Object.keys(syntheticFilters).length > 0;
330
+ const resolveRows = () => {
331
+ var _a;
332
+ let rows = resolve(config.rowSelector, rootLocator);
333
+ if (Object.keys(domFilters).length > 0) {
334
+ const map = tableMapper.getMapSync();
335
+ if (!map)
336
+ throw new Error('Initialization Error: Table map not available. Call "await table.init()" first.');
337
+ 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);
338
+ }
339
+ return rows;
340
+ };
341
+ const countPostFilterMatches = async (candidates, indices) => {
342
+ var _a;
343
+ if (!hasPostFilters)
344
+ return indices.length;
345
+ const map = tableMapper.getMapSync();
346
+ if (!map)
347
+ throw new Error('Initialization Error: Table map not available. Call "await table.init()" first.');
348
+ const exact = (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false;
349
+ const hasOverrides = Object.keys(overrideFilters).length > 0;
350
+ const hasSynthetics = Object.keys(syntheticFilters).length > 0;
351
+ let count = 0;
352
+ for (const idx of indices) {
353
+ const row = candidates[idx];
354
+ if (hasOverrides && !await rowFinder.matchesOverrideFilters(row, overrideFilters, map, exact))
355
+ continue;
356
+ if (hasSynthetics && !await rowFinder.matchesSyntheticFilters(row, syntheticFilters, map, exact))
357
+ continue;
358
+ count++;
359
+ }
360
+ return count;
361
+ };
275
362
  if (!hasPagination) {
276
- log("countRows: counting rows in current viewport (no pagination)");
277
- return resolve(config.rowSelector, rootLocator).count();
363
+ log(`countRows: counting rows in current viewport (no pagination)${hasFilters ? ` filters=${safeStringify(filtersRecord)}` : ''}`);
364
+ if (!hasPostFilters)
365
+ return resolveRows().count();
366
+ const all = await resolveRows().all();
367
+ return countPostFilterMatches(all, all.map((_, i) => i));
278
368
  }
279
- log(`countRows: paginating up to ${config.maxPages} page(s)`);
369
+ log(`countRows: paginating up to ${effectiveMaxPages} page(s)${hasFilters ? ` filters=${safeStringify(filtersRecord)}` : ''}`);
280
370
  const tracker = new elementTracker_1.ElementTracker('countRows');
281
371
  let total = 0;
282
372
  let pagesScanned = 1;
283
373
  let paginationError;
284
374
  try {
285
375
  while (true) {
286
- const newIndices = await tracker.getUnseenIndices(resolve(config.rowSelector, rootLocator));
287
- total += newIndices.length;
288
- log(`countRows: page ${pagesScanned} — ${newIndices.length} row(s) (running total: ${total})`);
289
- if (pagesScanned >= config.maxPages)
376
+ const rowLocators = resolveRows();
377
+ const newIndices = await tracker.getUnseenIndices(rowLocators);
378
+ const candidates = await rowLocators.all();
379
+ const matched = await countPostFilterMatches(candidates, newIndices);
380
+ total += matched;
381
+ log(`countRows: page ${pagesScanned} — ${matched} row(s) (running total: ${total})`);
382
+ if (pagesScanned >= effectiveMaxPages)
290
383
  break;
291
384
  if (!await _advancePage(false))
292
385
  break;
@@ -326,7 +419,10 @@ const useTable = (rootLocator, configOptions = {}) => {
326
419
  await cell.bringIntoView();
327
420
  const columnOverride = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[columnName];
328
421
  if (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read) {
329
- return await columnOverride.read(cell);
422
+ return await columnOverride.read(cell, {
423
+ row, columnName, columnIndex: map.get(columnName),
424
+ getCell: (name) => row.getCell(name),
425
+ });
330
426
  }
331
427
  const text = await cell.innerText();
332
428
  return (text || '').trim();
@@ -340,7 +436,6 @@ const useTable = (rootLocator, configOptions = {}) => {
340
436
  reset: async () => {
341
437
  var _a;
342
438
  log("Resetting table...");
343
- await config.onReset(createStrategyContext());
344
439
  if ((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.goToFirst) {
345
440
  log("Auto-navigating to first page...");
346
441
  await config.strategies.pagination.goToFirst(createStrategyContext());
@@ -349,17 +444,25 @@ const useTable = (rootLocator, configOptions = {}) => {
349
444
  log("No goToFirst strategy configured. Table may not be on page 1.");
350
445
  }
351
446
  tableState.currentPageIndex = 0;
447
+ tableState.empty = false;
352
448
  tableMapper.clear();
353
449
  log("Table reset complete. Calling autoInit to restore state.");
354
450
  await _autoInit();
451
+ await config.onReset(createStrategyContext());
355
452
  },
356
453
  revalidate: async () => {
357
454
  log("Revalidating table structure...");
358
455
  await tableMapper.remapHeaders();
456
+ tableState.empty = false;
359
457
  log("Table revalidated.");
360
458
  },
361
459
  getRow: (filters, options = { exact: false }) => {
362
460
  log(`getRow: filters=${safeStringify(filters)} exact=${options.exact}`);
461
+ const syntheticKeys = Object.keys(filters).filter(k => { var _a; return (_a = config.syntheticColumns) === null || _a === void 0 ? void 0 : _a[k]; });
462
+ if (syntheticKeys.length > 0) {
463
+ throw new Error(`getRow() cannot filter by synthetic column(s): ${syntheticKeys.join(', ')}. ` +
464
+ `Use findRow() instead — synthetic columns require async evaluation.`);
465
+ }
363
466
  const map = tableMapper.getMapSync();
364
467
  if (!map)
365
468
  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.');
@@ -375,6 +478,62 @@ const useTable = (rootLocator, configOptions = {}) => {
375
478
  const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
376
479
  return _makeSmart(rowLocator, map, index);
377
480
  },
481
+ findRowByIndex: async (index, options) => {
482
+ var _a, _b;
483
+ log(`findRowByIndex: index=${index} options=${safeStringify(options)}`);
484
+ await _autoInit();
485
+ const map = tableMapper.getMapSync();
486
+ const resolveRI = config.strategies.resolveRowIndex;
487
+ const viewport = config.strategies.viewport;
488
+ // Identifying a row by its logical index requires a resolveRowIndex strategy. Without one
489
+ // the library can only offer render-window-relative getRowByIndex, or filter-based findRow.
490
+ if (!resolveRI) {
491
+ throw new Error(`findRowByIndex(${index}) requires a strategies.resolveRowIndex to identify rows by their logical index. ` +
492
+ `Configure one (e.g. read the grid's row-index attribute), or use getRowByIndex() (current render window) / findRow(filters).`);
493
+ }
494
+ let foundSelector;
495
+ // The currently-mounted row whose logical index === index, if any.
496
+ const findMounted = async () => {
497
+ const rows = await resolve(config.rowSelector, rootLocator).all();
498
+ for (const r of rows) {
499
+ const result = await resolveRI(r);
500
+ if (result === undefined)
501
+ continue;
502
+ const normalized = (0, rowResolution_1.normalizeRowIndexResult)(result);
503
+ if (normalized.index === index) {
504
+ foundSelector = normalized.selector;
505
+ return r;
506
+ }
507
+ }
508
+ return null;
509
+ };
510
+ // 1. Already mounted (no navigation needed)?
511
+ let loc = await findMounted();
512
+ // 2. Fast path: jump straight to it via the viewport's random-access scroll.
513
+ if (!loc && (viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow)) {
514
+ log(`findRowByIndex: scrolling to row ${index}`);
515
+ await viewport.scrollToRow(createStrategyContext(), index);
516
+ loc = await findMounted();
517
+ }
518
+ // 3. Fallback: advance pages (on infinite-scroll tables, a "page" is a scroll step) until
519
+ // the row is reached. Bounded by maxPages so an unreachable index can't loop forever.
520
+ if (!loc && config.strategies.pagination) {
521
+ const effectiveMaxPages = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
522
+ let pagesScanned = 1;
523
+ while (!loc && pagesScanned < effectiveMaxPages) {
524
+ if (!await _advancePage(false))
525
+ break;
526
+ pagesScanned++;
527
+ loc = await findMounted();
528
+ }
529
+ }
530
+ if (!loc) {
531
+ throw new Error(`findRowByIndex(${index}): could not reach row ${index}. ` +
532
+ `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}).`);
533
+ }
534
+ await (0, debugUtils_1.debugDelay)(config, 'findRow');
535
+ return _makeSmart(loc, map, index, tableState.currentPageIndex, undefined, foundSelector);
536
+ },
378
537
  findRow: async (filters, options) => {
379
538
  log(`findRow: filters=${safeStringify(filters)} options=${safeStringify(options)}`);
380
539
  return rowFinder.findRow(filters, options);
@@ -384,13 +543,16 @@ const useTable = (rootLocator, configOptions = {}) => {
384
543
  return rowFinder.findRows(filters !== null && filters !== void 0 ? filters : {}, options);
385
544
  },
386
545
  isInitialized: () => {
387
- const initialized = tableMapper.isInitialized();
546
+ const initialized = tableMapper.isInitialized() || tableState.empty;
388
547
  log(`isInitialized: ${initialized}`);
389
548
  return initialized;
390
549
  },
550
+ isEmpty: () => {
551
+ return tableState.empty;
552
+ },
391
553
  sorting: {
392
554
  apply: async (columnName, direction) => {
393
- var _a;
555
+ var _a, _b, _c, _d, _e;
394
556
  await _autoInit();
395
557
  if (!config.strategies.sorting)
396
558
  throw new Error('No sorting strategy has been configured.');
@@ -405,13 +567,16 @@ const useTable = (rootLocator, configOptions = {}) => {
405
567
  }
406
568
  await config.strategies.sorting.doSort({ columnName, direction, context });
407
569
  if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
408
- const deadline = Date.now() + 10000;
570
+ const timeout = (_b = config.strategies.loading.sortStabilizationTimeout) !== null && _b !== void 0 ? _b : 10000;
571
+ const poll = (_c = config.strategies.loading.sortStabilizationPollInterval) !== null && _c !== void 0 ? _c : 100;
572
+ const deadline = Date.now() + timeout;
409
573
  while (Date.now() < deadline && await config.strategies.loading.isTableLoading(context)) {
410
- await rootLocator.page().waitForTimeout(100);
574
+ await rootLocator.page().waitForTimeout(poll);
411
575
  }
412
576
  }
413
577
  else {
414
- await rootLocator.page().waitForTimeout(200);
578
+ const fallback = (_e = (_d = config.strategies.loading) === null || _d === void 0 ? void 0 : _d.sortStabilizationFallbackDelay) !== null && _e !== void 0 ? _e : 200;
579
+ await rootLocator.page().waitForTimeout(fallback);
415
580
  }
416
581
  await (0, debugUtils_1.debugDelay)(config, 'default');
417
582
  const newState = await config.strategies.sorting.getSortState({ columnName, context });
@@ -434,6 +599,7 @@ const useTable = (rootLocator, configOptions = {}) => {
434
599
  // ─── Shared async row iterator ───────────────────────────────────────────
435
600
  [Symbol.asyncIterator]() {
436
601
  return __asyncGenerator(this, arguments, function* _a() {
602
+ var _b;
437
603
  yield __await(_autoInit());
438
604
  const map = tableMapper.getMapSync();
439
605
  const effectiveMaxPages = config.maxPages;
@@ -449,7 +615,14 @@ const useTable = (rootLocator, configOptions = {}) => {
449
615
  const pageRows = yield __await(rowLocators.all());
450
616
  const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
451
617
  for (const idx of newIndices) {
452
- yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex, tableState.currentPageIndex, barrier), index: rowIndex, rowIndex, pageIndex: tableState.currentPageIndex });
618
+ // index = visit-order counter; rowIndex = logical/data index (B-hybrid, #362).
619
+ const resolved = yield __await((0, rowResolution_1.resolveLogicalRowIndex)(pageRows[idx], config, () => rowIndex));
620
+ const logical = (_b = resolved === null || resolved === void 0 ? void 0 : resolved.index) !== null && _b !== void 0 ? _b : rowIndex;
621
+ const sr = _makeSmart(pageRows[idx], map, logical, tableState.currentPageIndex, barrier, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
622
+ // Iterator rows share one per-page DOM snapshot (positional locators); disable
623
+ // toJSON's scroll-back recovery so reading one row can't invalidate the others (#366).
624
+ sr._inBatch = true;
625
+ yield yield __await({ row: sr, index: rowIndex, rowIndex: logical, pageIndex: tableState.currentPageIndex });
453
626
  rowIndex++;
454
627
  }
455
628
  if (pagesScanned >= effectiveMaxPages)
@@ -472,11 +645,12 @@ const useTable = (rootLocator, configOptions = {}) => {
472
645
  getRowLocators: () => resolve(config.rowSelector, rootLocator),
473
646
  getMap: () => tableMapper.getMapSync(),
474
647
  advancePage: _advancePage,
475
- makeSmartRow: (loc, map, idx, pageIdx, barrier) => _makeSmart(loc, map, idx, pageIdx, barrier),
648
+ makeSmartRow: (loc, map, idx, pageIdx, barrier, sel) => _makeSmart(loc, map, idx, pageIdx, barrier, sel),
476
649
  createSmartRowArray: smartRowArray_1.createSmartRowArray,
477
650
  config,
478
651
  getPage: () => rootLocator.page(),
479
652
  getCurrentPageIndex: () => tableState.currentPageIndex,
653
+ getContext: () => createStrategyContext(),
480
654
  }, callback, options);
481
655
  },
482
656
  map: async (callback, options = {}) => {
@@ -486,11 +660,12 @@ const useTable = (rootLocator, configOptions = {}) => {
486
660
  getRowLocators: () => resolve(config.rowSelector, rootLocator),
487
661
  getMap: () => tableMapper.getMapSync(),
488
662
  advancePage: _advancePage,
489
- makeSmartRow: (loc, map, idx, pageIdx, barrier) => _makeSmart(loc, map, idx, pageIdx, barrier),
663
+ makeSmartRow: (loc, map, idx, pageIdx, barrier, sel) => _makeSmart(loc, map, idx, pageIdx, barrier, sel),
490
664
  createSmartRowArray: smartRowArray_1.createSmartRowArray,
491
665
  config,
492
666
  getPage: () => rootLocator.page(),
493
667
  getCurrentPageIndex: () => tableState.currentPageIndex,
668
+ getContext: () => createStrategyContext(),
494
669
  }, callback, options);
495
670
  },
496
671
  toArray: async (callback, options = {}) => {
@@ -510,11 +685,12 @@ const useTable = (rootLocator, configOptions = {}) => {
510
685
  getRowLocators: () => resolve(config.rowSelector, rootLocator),
511
686
  getMap: () => tableMapper.getMapSync(),
512
687
  advancePage: _advancePage,
513
- makeSmartRow: (loc, map, idx, pageIdx, barrier) => _makeSmart(loc, map, idx, pageIdx, barrier),
688
+ makeSmartRow: (loc, map, idx, pageIdx, barrier, sel) => _makeSmart(loc, map, idx, pageIdx, barrier, sel),
514
689
  createSmartRowArray: smartRowArray_1.createSmartRowArray,
515
690
  config,
516
691
  getPage: () => rootLocator.page(),
517
692
  getCurrentPageIndex: () => tableState.currentPageIndex,
693
+ getContext: () => createStrategyContext(),
518
694
  }, predicate, options);
519
695
  },
520
696
  generateConfig: async () => {
@@ -56,5 +56,9 @@ function mergeTableConfig(base, overrides) {
56
56
  if (base.columnOverrides || overrides.columnOverrides) {
57
57
  result.columnOverrides = Object.assign(Object.assign({}, base.columnOverrides), overrides.columnOverrides);
58
58
  }
59
+ // Deep-merge syntheticColumns (override keys replace, preset keys retained)
60
+ if (base.syntheticColumns || overrides.syntheticColumns) {
61
+ result.syntheticColumns = Object.assign(Object.assign({}, base.syntheticColumns), overrides.syntheticColumns);
62
+ }
59
63
  return result;
60
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.18.0",
3
+ "version": "6.20.0",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",