@rickcedwhat/playwright-smart-table 6.21.0 → 6.22.0-next.28cae60

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
@@ -32,6 +32,9 @@ const scanPages_1 = require("./engine/scanPages");
32
32
  const debugUtils_1 = require("./utils/debugUtils");
33
33
  const smartRowArray_1 = require("./utils/smartRowArray");
34
34
  const elementTracker_1 = require("./utils/elementTracker");
35
+ const loadingWait_1 = require("./utils/loadingWait");
36
+ const pageIndex_1 = require("./utils/pageIndex");
37
+ const smartRowInternals_1 = require("./utils/smartRowInternals");
35
38
  // Helper to safely serialize objects containing functions for logging
36
39
  const safeStringify = (obj) => {
37
40
  try {
@@ -145,13 +148,14 @@ const useTable = (rootLocator, configOptions = {}) => {
145
148
  // Placeholder for the final table object
146
149
  let finalTable = null;
147
150
  // Helper factory
148
- const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier, rowSelector, renderWindowPosition = false) => {
149
- const effectiveLocator = rowSelector
150
- ? rootLocator.locator(rowSelector)
151
+ // healSelector: the per-row selector returned by resolveRowIndex (not config.rowSelector).
152
+ const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier, healSelector, renderWindowPosition = false) => {
153
+ const effectiveLocator = healSelector
154
+ ? rootLocator.locator(healSelector)
151
155
  : rowLocator;
152
156
  const sr = (0, smartRow_1.default)(effectiveLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier, renderWindowPosition);
153
- if (rowSelector)
154
- sr._selfHealing = true;
157
+ if (healSelector)
158
+ (0, smartRowInternals_1.internals)(sr)._selfHealing = true;
155
159
  return sr;
156
160
  };
157
161
  const tableState = { currentPageIndex: 0, empty: false };
@@ -183,7 +187,7 @@ const useTable = (rootLocator, configOptions = {}) => {
183
187
  const _handlePrompt = async (promptName, content) => {
184
188
  let finalPrompt = content;
185
189
  finalPrompt += `\n\nšŸ‘‡ Useful TypeScript Definitions šŸ‘‡\n\`\`\`typescript\n${minimalConfigContext_1.MINIMAL_CONFIG_CONTEXT}\n\`\`\`\n`;
186
- console.log(`āš ļø Throwing error to display [${promptName}] cleanly...`);
190
+ (0, debugUtils_1.logDebug)(config, 'info', `Throwing error to display [${promptName}] cleanly...`);
187
191
  throw new Error(finalPrompt);
188
192
  };
189
193
  const _autoInit = async () => {
@@ -229,9 +233,12 @@ const useTable = (rootLocator, configOptions = {}) => {
229
233
  };
230
234
  const result = {
231
235
  get currentPageIndex() { return tableState.currentPageIndex; },
232
- set currentPageIndex(v) { tableState.currentPageIndex = v; },
236
+ set currentPageIndex(v) {
237
+ console.warn('[SmartTable] Manually assigning table.currentPageIndex can desync pagination path planning. Prefer reset() / library navigation; writable access may become read-only in v7.');
238
+ tableState.currentPageIndex = v;
239
+ },
233
240
  init: async (options) => {
234
- var _a;
241
+ var _a, _b, _c;
235
242
  if (tableMapper.isInitialized() || tableState.empty)
236
243
  return result;
237
244
  if (config.strategies.sorting)
@@ -240,6 +247,10 @@ const useTable = (rootLocator, configOptions = {}) => {
240
247
  (0, validation_1.validateFillStrategy)(config.strategies.fill);
241
248
  (0, debugUtils_1.warnIfDebugInCI)(config);
242
249
  (0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
250
+ if (config.maxPages === 1 &&
251
+ (((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.goNext) || ((_b = config.strategies.pagination) === null || _b === void 0 ? void 0 : _b.goNextBulk))) {
252
+ console.warn('[SmartTable] maxPages is 1 but a pagination strategy is configured — iteration/find will never leave page 1. Pass maxPages: N (or Infinity) to scan further pages.');
253
+ }
243
254
  let map;
244
255
  try {
245
256
  map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
@@ -256,7 +267,7 @@ const useTable = (rootLocator, configOptions = {}) => {
256
267
  throw headerError;
257
268
  }
258
269
  (0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${map.size} columns`, Array.from(map.keys()));
259
- if ((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.detectCurrentPage) {
270
+ if ((_c = config.strategies.pagination) === null || _c === void 0 ? void 0 : _c.detectCurrentPage) {
260
271
  try {
261
272
  const detected = await config.strategies.pagination.detectCurrentPage(rootLocator);
262
273
  if (Number.isInteger(detected) && detected >= 0) {
@@ -370,10 +381,7 @@ const useTable = (rootLocator, configOptions = {}) => {
370
381
  const isTableLoading = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.isTableLoading;
371
382
  if (isTableLoading) {
372
383
  const ctx = createStrategyContext();
373
- while (await isTableLoading(ctx)) {
374
- log('countRows: table is loading... waiting');
375
- await rootLocator.page().waitForTimeout(200);
376
- }
384
+ await (0, loadingWait_1.waitWhileTableLoading)(config, ctx, rootLocator.page(), 'countRows');
377
385
  }
378
386
  if (!hasPagination) {
379
387
  log(`countRows: counting rows in current viewport (no pagination)${hasFilters ? ` filters=${safeStringify(filtersRecord)}` : ''}`);
@@ -396,10 +404,7 @@ const useTable = (rootLocator, configOptions = {}) => {
396
404
  if (!isTableLoading)
397
405
  return;
398
406
  const ctx = createStrategyContext();
399
- while (await isTableLoading(ctx)) {
400
- log('countRows: table is loading... waiting');
401
- await rootLocator.page().waitForTimeout(200);
402
- }
407
+ await (0, loadingWait_1.waitWhileTableLoading)(config, ctx, rootLocator.page(), 'countRows');
403
408
  },
404
409
  }, async ({ pagesScanned: pageNum }) => {
405
410
  const rowLocators = resolveRows();
@@ -592,7 +597,7 @@ const useTable = (rootLocator, configOptions = {}) => {
592
597
  },
593
598
  sorting: {
594
599
  apply: async (columnName, direction) => {
595
- var _a, _b, _c, _d, _e;
600
+ var _a, _b, _c, _d, _e, _f;
596
601
  await _autoInit();
597
602
  if (!config.strategies.sorting)
598
603
  throw new Error('No sorting strategy has been configured.');
@@ -607,15 +612,15 @@ const useTable = (rootLocator, configOptions = {}) => {
607
612
  }
608
613
  await config.strategies.sorting.doSort({ columnName, direction, context });
609
614
  if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
610
- const timeout = (_b = config.strategies.loading.sortStabilizationTimeout) !== null && _b !== void 0 ? _b : 10000;
611
- const poll = (_c = config.strategies.loading.sortStabilizationPollInterval) !== null && _c !== void 0 ? _c : 100;
612
- const deadline = Date.now() + timeout;
613
- while (Date.now() < deadline && await config.strategies.loading.isTableLoading(context)) {
614
- await rootLocator.page().waitForTimeout(poll);
615
- }
615
+ const timeout = (_c = (_b = config.strategies.loading.sortStabilizationTimeout) !== null && _b !== void 0 ? _b : config.strategies.loading.loadingTimeout) !== null && _c !== void 0 ? _c : 10000;
616
+ const poll = (_d = config.strategies.loading.sortStabilizationPollInterval) !== null && _d !== void 0 ? _d : 100;
617
+ await (0, loadingWait_1.waitWhileTableLoading)(config, context, rootLocator.page(), 'sorting.apply', {
618
+ timeout,
619
+ pollInterval: poll,
620
+ });
616
621
  }
617
622
  else {
618
- const fallback = (_e = (_d = config.strategies.loading) === null || _d === void 0 ? void 0 : _d.sortStabilizationFallbackDelay) !== null && _e !== void 0 ? _e : 200;
623
+ const fallback = (_f = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.sortStabilizationFallbackDelay) !== null && _f !== void 0 ? _f : 200;
619
624
  await rootLocator.page().waitForTimeout(fallback);
620
625
  }
621
626
  await (0, debugUtils_1.debugDelay)(config, 'default');
@@ -767,10 +772,14 @@ const useTable = (rootLocator, configOptions = {}) => {
767
772
  await _handlePrompt('Smart Table Config', content);
768
773
  },
769
774
  generateConfigPrompt: async () => {
770
- console.warn('āš ļø [playwright-smart-table] generateConfigPrompt() is deprecated and will be removed in v7.0.0. Please use generateConfig() instead.');
775
+ (0, debugUtils_1.logDebug)(config, 'error', 'generateConfigPrompt() is deprecated and will be removed in v7.0.0. Please use generateConfig() instead.');
771
776
  return result.generateConfig();
772
777
  },
773
778
  };
779
+ Object.defineProperty(result, pageIndex_1.SET_CURRENT_PAGE_INDEX, {
780
+ value: (v) => { tableState.currentPageIndex = v; },
781
+ enumerable: false,
782
+ });
774
783
  createStrategyContext = () => ({
775
784
  root: rootLocator,
776
785
  config,
@@ -0,0 +1,14 @@
1
+ import type { TableSelector } from '../types';
2
+ type SelectorKey = 'headerSelector' | 'rowSelector' | 'cellSelector';
3
+ /** Viewport range meaning "can't measure": callers treat everything as possibly visible and verify in the DOM. */
4
+ export declare const UNKNOWN_RANGE: {
5
+ first: number;
6
+ last: number;
7
+ };
8
+ /**
9
+ * Returns the configured selector when it is a CSS string. Features that run
10
+ * `querySelectorAll` in the browser can't use a function selector; for those this warns
11
+ * once per config + feature and returns `null` so the caller can skip that step.
12
+ */
13
+ export declare function cssSelectorOrWarn(config: Partial<Record<SelectorKey, TableSelector>>, key: SelectorKey, feature: string): string | null;
14
+ export {};
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UNKNOWN_RANGE = void 0;
4
+ exports.cssSelectorOrWarn = cssSelectorOrWarn;
5
+ /** Viewport range meaning "can't measure": callers treat everything as possibly visible and verify in the DOM. */
6
+ exports.UNKNOWN_RANGE = { first: 0, last: Number.POSITIVE_INFINITY };
7
+ const warned = new WeakMap();
8
+ /**
9
+ * Returns the configured selector when it is a CSS string. Features that run
10
+ * `querySelectorAll` in the browser can't use a function selector; for those this warns
11
+ * once per config + feature and returns `null` so the caller can skip that step.
12
+ */
13
+ function cssSelectorOrWarn(config, key, feature) {
14
+ const selector = config[key];
15
+ if (typeof selector === 'string')
16
+ return selector;
17
+ if (selector === undefined)
18
+ return null;
19
+ let seen = warned.get(config);
20
+ if (!seen)
21
+ warned.set(config, (seen = new Set()));
22
+ const id = `${key}:${feature}`;
23
+ if (!seen.has(id)) {
24
+ seen.add(id);
25
+ console.warn(`[SmartTable] ${feature} needs ${key} to be a CSS string, but a function was configured — skipping it. Use a string ${key} to enable ${feature}.`);
26
+ }
27
+ return null;
28
+ }
@@ -0,0 +1,15 @@
1
+ import type { Page } from '@playwright/test';
2
+ import type { FinalTableConfig, TableContext } from '../types';
3
+ /**
4
+ * Poll `loading.isTableLoading` until false or timeout.
5
+ * Shared by countRows, findRow(s), and sort stabilization (#434).
6
+ *
7
+ * Timeout resolution (first defined wins):
8
+ * 1. Explicit `options.timeout` — e.g. the sort caller's stabilization timeout
9
+ * 2. `loadingTimeout` — general gate for any isTableLoading poll
10
+ * 3. 10_000 ms default
11
+ */
12
+ export declare function waitWhileTableLoading(config: FinalTableConfig, context: TableContext, page: Page, label: string, options?: {
13
+ timeout?: number;
14
+ pollInterval?: number;
15
+ }): Promise<void>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.waitWhileTableLoading = waitWhileTableLoading;
4
+ const debugUtils_1 = require("./debugUtils");
5
+ /**
6
+ * Poll `loading.isTableLoading` until false or timeout.
7
+ * Shared by countRows, findRow(s), and sort stabilization (#434).
8
+ *
9
+ * Timeout resolution (first defined wins):
10
+ * 1. Explicit `options.timeout` — e.g. the sort caller's stabilization timeout
11
+ * 2. `loadingTimeout` — general gate for any isTableLoading poll
12
+ * 3. 10_000 ms default
13
+ */
14
+ async function waitWhileTableLoading(config, context, page, label, options) {
15
+ var _a, _b, _c, _d, _e;
16
+ const isTableLoading = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading;
17
+ if (!isTableLoading)
18
+ return;
19
+ const loading = config.strategies.loading;
20
+ const timeout = (_c = (_b = options === null || options === void 0 ? void 0 : options.timeout) !== null && _b !== void 0 ? _b : loading.loadingTimeout) !== null && _c !== void 0 ? _c : 10000;
21
+ const poll = (_e = (_d = options === null || options === void 0 ? void 0 : options.pollInterval) !== null && _d !== void 0 ? _d : loading.sortStabilizationPollInterval) !== null && _e !== void 0 ? _e : 200;
22
+ const deadline = Date.now() + timeout;
23
+ while (Date.now() < deadline) {
24
+ let timer;
25
+ try {
26
+ // A custom predicate may never settle. Stop waiting when the budget expires;
27
+ // Promise.race also handles any later rejection from that predicate.
28
+ const expired = new Promise(resolve => {
29
+ timer = setTimeout(() => resolve(false), Math.max(0, deadline - Date.now()));
30
+ });
31
+ if (!(await Promise.race([isTableLoading(context), expired])))
32
+ return;
33
+ }
34
+ finally {
35
+ clearTimeout(timer);
36
+ }
37
+ const remaining = deadline - Date.now();
38
+ if (remaining <= 0)
39
+ return;
40
+ (0, debugUtils_1.logDebug)(config, 'verbose', `${label}: table is loading... waiting`);
41
+ await page.waitForTimeout(Math.min(poll, remaining));
42
+ }
43
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Internal setter for `table.currentPageIndex` used by library navigation
3
+ * (bringIntoView path planner, etc.) so the public setter can warn on
4
+ * external writes without spamming on every library update (#434).
5
+ */
6
+ export declare const SET_CURRENT_PAGE_INDEX: unique symbol;
7
+ export type PageIndexWritable = {
8
+ currentPageIndex: number;
9
+ [SET_CURRENT_PAGE_INDEX]?: (n: number) => void;
10
+ };
11
+ /** Prefer the internal setter when present; fall back to the public property. */
12
+ export declare function setCurrentPageIndex(table: PageIndexWritable, n: number): void;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SET_CURRENT_PAGE_INDEX = void 0;
4
+ exports.setCurrentPageIndex = setCurrentPageIndex;
5
+ /**
6
+ * Internal setter for `table.currentPageIndex` used by library navigation
7
+ * (bringIntoView path planner, etc.) so the public setter can warn on
8
+ * external writes without spamming on every library update (#434).
9
+ */
10
+ exports.SET_CURRENT_PAGE_INDEX = Symbol.for('playwright-smart-table.setCurrentPageIndex');
11
+ /** Prefer the internal setter when present; fall back to the public property. */
12
+ function setCurrentPageIndex(table, n) {
13
+ const internal = table[exports.SET_CURRENT_PAGE_INDEX];
14
+ if (typeof internal === 'function') {
15
+ internal(n);
16
+ return;
17
+ }
18
+ table.currentPageIndex = n;
19
+ }
@@ -22,8 +22,8 @@ export declare function resolveCellLocator(args: {
22
22
  * `filter({ has })`. Using the rows collection or a document-rooted parent nests
23
23
  * the row selector and matches nothing.
24
24
  *
25
- * Without `getCellLocator`, keeps the historical page-scoped `cellSelector.nth(i)`
26
- * template that Playwright re-bases into each row.
25
+ * Without `getCellLocator`, resolves `cellSelector` against the same `:scope` row so a
26
+ * function `cellSelector` receives a Locator, and Playwright re-bases it into each row.
27
27
  */
28
28
  export declare function resolveCellLocatorForFilter(args: {
29
29
  config: FinalTableConfig;
@@ -31,14 +31,14 @@ function resolveCellLocator(args) {
31
31
  * `filter({ has })`. Using the rows collection or a document-rooted parent nests
32
32
  * the row selector and matches nothing.
33
33
  *
34
- * Without `getCellLocator`, keeps the historical page-scoped `cellSelector.nth(i)`
35
- * template that Playwright re-bases into each row.
34
+ * Without `getCellLocator`, resolves `cellSelector` against the same `:scope` row so a
35
+ * function `cellSelector` receives a Locator, and Playwright re-bases it into each row.
36
36
  */
37
37
  function resolveCellLocatorForFilter(args) {
38
38
  var _a;
39
39
  const { config, resolve, page, root, columnName, columnIndex } = args;
40
+ const scopeRow = page.locator(':scope');
40
41
  if ((_a = config.strategies) === null || _a === void 0 ? void 0 : _a.getCellLocator) {
41
- const scopeRow = page.locator(':scope');
42
42
  return config.strategies.getCellLocator({
43
43
  row: scopeRow,
44
44
  root: root !== null && root !== void 0 ? root : scopeRow,
@@ -48,5 +48,5 @@ function resolveCellLocatorForFilter(args) {
48
48
  config,
49
49
  });
50
50
  }
51
- return resolve(config.cellSelector, page).nth(columnIndex);
51
+ return resolve(config.cellSelector, scopeRow).nth(columnIndex);
52
52
  }
@@ -0,0 +1,14 @@
1
+ import type { SmartRow } from '../types';
2
+ import type { NavigationBarrier } from './navigationBarrier';
3
+ /**
4
+ * Library-private state stored on SmartRow instances. Not part of the public `SmartRow` type.
5
+ */
6
+ export interface SmartRowInternals {
7
+ /** Row re-resolves itself from the `resolveRowIndex` selector on every use (DOM recycling). */
8
+ _selfHealing?: boolean;
9
+ /** Row is being processed inside a batch (map/forEach/filter or findRows). */
10
+ _inBatch?: boolean;
11
+ /** Barrier coordinating column scrolls across rows in `synchronized` iteration. */
12
+ _barrier?: NavigationBarrier;
13
+ }
14
+ export declare function internals(row: SmartRow<any>): SmartRowInternals;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.internals = internals;
4
+ function internals(row) {
5
+ return row;
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "6.21.0",
3
+ "version": "6.22.0-next.28cae60",
4
4
  "description": "Smart, column-aware table interactions for Playwright",
5
5
  "author": "Cedrick Catalan",
6
6
  "license": "MIT",