@rickcedwhat/playwright-smart-table 6.25.0-next.c784f2c → 6.25.0-next.c81cb3d

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.
@@ -0,0 +1,407 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.headerCellGetter = headerCellGetter;
4
+ exports.readRowAtomic = readRowAtomic;
5
+ exports.readRowLive = readRowLive;
6
+ const rowResolution_1 = require("./rowResolution");
7
+ const cellNavigation_1 = require("./cellNavigation");
8
+ const debugUtils_1 = require("../utils/debugUtils");
9
+ /** `getHeaderCell` for strategy hooks: the table's when available, else an index lookup. */
10
+ function headerCellGetter(env) {
11
+ const { table } = env;
12
+ return (table === null || table === void 0 ? void 0 : table.getHeaderCell)
13
+ ? table.getHeaderCell.bind(table)
14
+ : async (colName) => {
15
+ const idx = env.map.get(colName);
16
+ if (idx === undefined)
17
+ throw new Error(`Column "${colName}" not found`);
18
+ return env.resolve(env.config.headerSelector, env.rootLocator).nth(idx);
19
+ };
20
+ }
21
+ /** Adds synthetic columns to `result`; their compute() reads already-read columns from `result`. */
22
+ async function computeSyntheticColumns(env, result, columns) {
23
+ var _a;
24
+ const smart = env.row;
25
+ for (const [name, def] of Object.entries((_a = env.config.syntheticColumns) !== null && _a !== void 0 ? _a : {})) {
26
+ if (columns && !columns.includes(name))
27
+ continue;
28
+ const snapshotRow = Object.create(smart);
29
+ snapshotRow.getValue = async (col) => {
30
+ if (col in result)
31
+ return String(result[col]);
32
+ return smart.getValue(col);
33
+ };
34
+ result[name] = String(await def.compute(snapshotRow));
35
+ }
36
+ }
37
+ const isBatchRow = (row) => row._inBatch === true || !!row._barrier;
38
+ // ─── Atomic snapshot ─────────────────────────────────────────────────────────
39
+ /** Mounted row whose logical index (per resolveRowIndex) is `rowIndex`; tolerates rows detaching mid-scan. */
40
+ async function rescanForRow(env, rowIndex) {
41
+ const resolveRI = env.config.strategies.resolveRowIndex;
42
+ const rows = await env.resolve(env.config.rowSelector, env.rootLocator).all();
43
+ for (const r of rows) {
44
+ try {
45
+ const rResult = await resolveRI(r);
46
+ if (rResult !== undefined && (0, rowResolution_1.normalizeRowIndexResult)(rResult).index === rowIndex)
47
+ return r;
48
+ }
49
+ catch (_a) { /* element may have detached during scan */ }
50
+ }
51
+ return null;
52
+ }
53
+ async function logicalIndexOf(env, loc) {
54
+ try {
55
+ const r = await env.config.strategies.resolveRowIndex(loc);
56
+ return r !== undefined ? (0, rowResolution_1.normalizeRowIndexResult)(r).index : undefined;
57
+ }
58
+ catch (_a) {
59
+ return undefined;
60
+ }
61
+ }
62
+ const recycledRowHint = (inBatch, batchNote, otherwise) => inBatch ? batchNote : otherwise;
63
+ /** Scrolls the row back into view and rescans for it for up to 500ms. */
64
+ async function scrollBackAndRescan(env, rowIndex) {
65
+ var _a;
66
+ const { config, rootLocator, resolve } = env;
67
+ const scrollToRow = (_a = config.strategies.viewport) === null || _a === void 0 ? void 0 : _a.scrollToRow;
68
+ if (!scrollToRow)
69
+ return null;
70
+ const page = rootLocator.page();
71
+ await scrollToRow({ root: rootLocator, config, page, resolve }, rowIndex);
72
+ const deadline = Date.now() + 500;
73
+ let recovered = null;
74
+ while (!recovered && Date.now() < deadline) {
75
+ recovered = await rescanForRow(env, rowIndex);
76
+ if (!recovered)
77
+ await page.waitForTimeout(50);
78
+ }
79
+ return recovered;
80
+ }
81
+ /**
82
+ * Re-pin: the row locator is a positional nth-child from the iteration loop.
83
+ * On recycling virtualizers, elements can shift between SmartRow creation and
84
+ * the clone — the nth element may now show a different row's content.
85
+ * Verify via resolveRowIndex and rescan if drifted.
86
+ */
87
+ async function recoverDriftedRow(env, rowIndex) {
88
+ const inBatch = isBatchRow(env.row);
89
+ let recovered = await rescanForRow(env, rowIndex);
90
+ if (!recovered && !inBatch)
91
+ recovered = await scrollBackAndRescan(env, rowIndex);
92
+ if (recovered)
93
+ return recovered;
94
+ throw new Error(`[SmartTable] toJSON({ atomic: true }): row ${rowIndex} recycled out of the DOM and could not be recovered. ` +
95
+ recycledRowHint(inBatch, `(During a map/forEach batch, scroll-back recovery is disabled to avoid disrupting sibling rows.)`, `Ensure a viewport.scrollToRow strategy can restore the row.`));
96
+ }
97
+ /** The element to clone: the row itself, re-pinned if it drifted, after `contentReady` settles. */
98
+ async function resolveCloneTarget(env) {
99
+ const { config, rowIndex } = env;
100
+ const canRepin = !!config.strategies.resolveRowIndex && typeof rowIndex === 'number' && env.row._selfHealing !== true;
101
+ let cloneTarget = env.rowLocator;
102
+ if (canRepin && await logicalIndexOf(env, cloneTarget) !== rowIndex) {
103
+ cloneTarget = await recoverDriftedRow(env, rowIndex);
104
+ }
105
+ // Wait for async content renders to settle before cloning.
106
+ if (config.strategies.contentReady) {
107
+ await config.strategies.contentReady(cloneTarget, env.rootLocator.page());
108
+ // Revalidate: the element may have recycled during the await.
109
+ if (canRepin && await logicalIndexOf(env, cloneTarget) !== rowIndex) {
110
+ const reFound = await rescanForRow(env, rowIndex);
111
+ if (reFound)
112
+ cloneTarget = reFound;
113
+ }
114
+ }
115
+ return cloneTarget;
116
+ }
117
+ /**
118
+ * Clones the row in browser memory (NOT in the DOM) and extracts text + row HTML.
119
+ * Uses textContent (not innerText) because the clone is detached — innerText
120
+ * falls back to textContent on detached nodes per HTML spec, so be explicit.
121
+ */
122
+ async function cloneRow(target, cellSel) {
123
+ const cloneHandle = await target.evaluateHandle(el => el.cloneNode(true));
124
+ try {
125
+ return await cloneHandle.evaluate((row, sel) => {
126
+ const cells = row.querySelectorAll(sel);
127
+ return {
128
+ rowHTML: row.outerHTML,
129
+ cells: [...cells].map(c => ({
130
+ text: (c.textContent || '').trim(),
131
+ })),
132
+ };
133
+ }, cellSel);
134
+ }
135
+ finally {
136
+ await cloneHandle.dispose();
137
+ }
138
+ }
139
+ /**
140
+ * `toJSON({ atomic: true })`: snapshot the row in a single evaluate, then apply column
141
+ * overrides against a frozen reconstruction. Zero inter-column stagger — even in-place
142
+ * React re-renders can't affect it because overrides read from a detached clone.
143
+ */
144
+ async function readRowAtomic(env, columns) {
145
+ var _a, _b;
146
+ const { config, map, rootLocator } = env;
147
+ const cellSel = config.cellSelector;
148
+ if (typeof cellSel !== 'string') {
149
+ throw new Error('[SmartTable] toJSON({ atomic: true }) requires cellSelector to be a CSS string, not a function.');
150
+ }
151
+ const page = rootLocator.page();
152
+ const snapshot = await cloneRow(await resolveCloneTarget(env), cellSel);
153
+ // Determine which columns have overrides (need lazy materialization)
154
+ const columnsToProcess = [...map.entries()].filter(([col]) => !columns || columns.includes(col));
155
+ const hasAnyOverride = columnsToProcess.some(([col]) => { var _a, _b; return !!((_b = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b.read); });
156
+ // Lazily materialize the row if any override needs a Locator.
157
+ // Uses position:fixed off-screen (NOT display:none — that breaks innerText).
158
+ const snapId = `__st_snap_${Math.random().toString(36).slice(2, 10)}`;
159
+ const snapCell = (colIdx) => page.locator(`#${snapId}`).locator(cellSel).nth(colIdx);
160
+ const getCell = (colName) => {
161
+ const colIdx = map.get(colName);
162
+ if (colIdx === undefined)
163
+ throw new Error(`[SmartTable] getCell: column "${colName}" not found`);
164
+ return snapCell(colIdx);
165
+ };
166
+ if (hasAnyOverride) {
167
+ await page.evaluate(([html, id]) => {
168
+ const c = document.createElement('div');
169
+ c.id = id;
170
+ c.style.cssText = 'position:fixed;left:-99999px;top:-99999px;opacity:0;pointer-events:none';
171
+ // Wrap in <table><tbody> so the browser's HTML parser keeps <tr>/<td> intact.
172
+ c.innerHTML = `<table><tbody>${html}</tbody></table>`;
173
+ document.body.appendChild(c);
174
+ }, [snapshot.rowHTML, snapId]);
175
+ }
176
+ try {
177
+ const result = {};
178
+ for (const [col, idx] of columnsToProcess) {
179
+ const mapper = (_b = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b.read;
180
+ if (mapper) {
181
+ try {
182
+ result[col] = await mapper(snapCell(idx), { row: env.row, columnName: col, columnIndex: idx, getCell });
183
+ }
184
+ catch (err) {
185
+ const wrapped = new Error(`[SmartTable] toJSON({ atomic: true }): column override for "${col}" failed — ${err.message}`);
186
+ wrapped.cause = err;
187
+ throw wrapped;
188
+ }
189
+ }
190
+ else if (idx < snapshot.cells.length) {
191
+ result[col] = snapshot.cells[idx].text;
192
+ }
193
+ }
194
+ await computeSyntheticColumns(env, result, columns);
195
+ return result;
196
+ }
197
+ finally {
198
+ if (hasAnyOverride)
199
+ await page.evaluate(id => { var _a; return (_a = document.getElementById(id)) === null || _a === void 0 ? void 0 : _a.remove(); }, snapId);
200
+ }
201
+ }
202
+ // ─── Live read ───────────────────────────────────────────────────────────────
203
+ /**
204
+ * #366 / #392: on virtualized tables the row's DOM node can recycle between the
205
+ * per-column awaits of a live read. When a self-healing locator is in use (the row locator was
206
+ * rebuilt with a CSS selector, e.g. `[data-rowindex="47"]`), the locator automatically
207
+ * re-queries the DOM — no rescan needed. For plain-number strategies (JS-only, no
208
+ * selector), fall back to rescan-then-scrollToRow.
209
+ *
210
+ * Returns `resolveStableRow` (call before each column) and `current` (last known-good row).
211
+ */
212
+ function createRowPinner(env) {
213
+ const { config, rowLocator, rowIndex } = env;
214
+ const resolveRI = config.strategies.resolveRowIndex;
215
+ const canPin = !!resolveRI && typeof rowIndex === 'number' && env.row._selfHealing !== true;
216
+ let lastGood = rowLocator;
217
+ const resolveStableRow = async () => {
218
+ // Self-healing locator: always re-queries the DOM by attribute — no rescan needed.
219
+ if (!canPin)
220
+ return rowLocator;
221
+ const result = await resolveRI(lastGood);
222
+ if (result !== undefined && (0, rowResolution_1.normalizeRowIndexResult)(result).index === rowIndex)
223
+ return lastGood; // still correct
224
+ // Drifted (recycled). Re-locate the row carrying the expected logical index.
225
+ lastGood = await relocateRecycledRow(env, rowIndex);
226
+ return lastGood;
227
+ };
228
+ return { resolveStableRow, current: () => lastGood };
229
+ }
230
+ /** Mounted row whose logical index is `rowIndex` (strict: resolveRowIndex errors propagate). */
231
+ async function rescanStrict(env, rowIndex) {
232
+ const resolveRI = env.config.strategies.resolveRowIndex;
233
+ const rows = await env.resolve(env.config.rowSelector, env.rootLocator).all();
234
+ for (const r of rows) {
235
+ const rResult = await resolveRI(r);
236
+ if (rResult !== undefined && (0, rowResolution_1.normalizeRowIndexResult)(rResult).index === rowIndex)
237
+ return r;
238
+ }
239
+ return null;
240
+ }
241
+ /** Live-read recovery: rescan, then (outside batches) scrollToRow and rescan once more. */
242
+ async function relocateRecycledRow(env, rowIndex) {
243
+ var _a;
244
+ const { config, rootLocator, resolve } = env;
245
+ const inBatch = isBatchRow(env.row);
246
+ let recovered = await rescanStrict(env, rowIndex);
247
+ const scrollToRow = (_a = config.strategies.viewport) === null || _a === void 0 ? void 0 : _a.scrollToRow;
248
+ if (!recovered && !inBatch && scrollToRow) {
249
+ await scrollToRow({ root: rootLocator, config, page: rootLocator.page(), resolve }, rowIndex);
250
+ recovered = await rescanStrict(env, rowIndex);
251
+ }
252
+ if (recovered)
253
+ return recovered;
254
+ throw new Error(`[SmartTable] toJSON: row ${rowIndex} recycled out of the DOM mid-read and could not be recovered — ` +
255
+ `the returned object would mix fields from different rows. ` +
256
+ recycledRowHint(inBatch, `(During a map/forEach batch, scroll-back recovery is disabled to avoid disrupting sibling rows; the row must stay mounted for the read.)`, `Ensure a viewport.scrollToRow can restore the row.`));
257
+ }
258
+ function cellIn(env, row, colName, colIdx) {
259
+ const { config, rootLocator } = env;
260
+ if (config.strategies.getCellLocator) {
261
+ return config.strategies.getCellLocator({
262
+ row, root: rootLocator, columnName: colName,
263
+ columnIndex: colIdx, rowIndex: env.rowIndex, page: rootLocator.page(), config,
264
+ });
265
+ }
266
+ return env.resolve(config.cellSelector, row).nth(colIdx);
267
+ }
268
+ /** undefined = unset (read as-is immediately); 0 = no wait (immediate check); >0 = wait up to N ms */
269
+ function cellLoadingTimeoutOf(config) {
270
+ var _a;
271
+ const raw = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.cellLoadingTimeout;
272
+ return raw !== undefined && Number.isFinite(raw) && raw >= 0 ? raw : undefined;
273
+ }
274
+ /** Polls `check` every `intervalMs` until it returns true or `deadline` passes. */
275
+ async function pollUntil(page, deadline, intervalMs, check, initial) {
276
+ let done = initial;
277
+ while (!done && Date.now() < deadline) {
278
+ await page.waitForTimeout(intervalMs);
279
+ done = await check();
280
+ }
281
+ return done;
282
+ }
283
+ /**
284
+ * First-paint race: the cell node can attach a tick before the loading
285
+ * indicator mounts. A single false-negative then reads "" and skips
286
+ * onCellLoadingTimeout. Only grace when the cell still looks empty.
287
+ */
288
+ async function graceForLoadingIndicator(page, targetCell, isLoading, timeout) {
289
+ const peek = await targetCell.evaluate((el) => (el.textContent || '').trim()).catch(() => '');
290
+ if (peek !== '')
291
+ return false;
292
+ return pollUntil(page, Date.now() + Math.min(250, timeout), 25, isLoading, false);
293
+ }
294
+ /** What to do when a cell is still loading after `cellLoadingTimeout`. */
295
+ async function onStillLoading(env, targetCell, col, timeout) {
296
+ var _a;
297
+ var _b;
298
+ const { config, row: smart } = env;
299
+ const onCellLoadingTimeout = (_b = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.onCellLoadingTimeout) !== null && _b !== void 0 ? _b : 'read-as-is';
300
+ (0, debugUtils_1.logDebug)(config, 'verbose', `toJSON: cell "${col}" — still loading after ${timeout}ms, action: ${typeof onCellLoadingTimeout === 'function' ? 'callback' : onCellLoadingTimeout}`);
301
+ if (typeof onCellLoadingTimeout === 'function') {
302
+ return { read: false, value: await onCellLoadingTimeout(targetCell, col, smart) };
303
+ }
304
+ if (onCellLoadingTimeout === 'skip')
305
+ return { read: false, value: '' };
306
+ if (onCellLoadingTimeout === 'throw') {
307
+ throw new Error(`[SmartTable] Cell "${col}" (row ${env.rowIndex}) did not finish loading within ${timeout}ms`);
308
+ }
309
+ // 'read-as-is': fall through to normal read
310
+ return { read: true };
311
+ }
312
+ /**
313
+ * Applies `loading.isCellLoading` / `cellLoadingTimeout` / `onCellLoadingTimeout` to one cell.
314
+ * Returns `{ read: true }` to read the cell normally, or the value to use instead.
315
+ */
316
+ async function waitForCellLoaded(env, targetCell, col) {
317
+ var _a;
318
+ const { config, row: smart } = env;
319
+ const isCellLoading = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isCellLoading;
320
+ if (!isCellLoading)
321
+ return { read: true };
322
+ const page = env.rootLocator.page();
323
+ const isLoading = () => isCellLoading(targetCell, col, smart);
324
+ const timeout = cellLoadingTimeoutOf(config);
325
+ let loading = await isLoading();
326
+ if (!loading && timeout !== undefined && timeout > 0) {
327
+ loading = await graceForLoadingIndicator(page, targetCell, isLoading, timeout);
328
+ }
329
+ if (!loading || timeout === undefined)
330
+ return { read: true };
331
+ (0, debugUtils_1.logDebug)(config, 'verbose', `toJSON: cell "${col}" — waiting up to ${timeout}ms`);
332
+ const deadline = Date.now() + timeout;
333
+ const isLoaded = async () => !(await isLoading());
334
+ const cellResolved = await pollUntil(page, deadline, 100, isLoaded, await isLoaded()); // immediate check (handles timeout=0)
335
+ return cellResolved ? { read: true } : onStillLoading(env, targetCell, col, timeout);
336
+ }
337
+ /**
338
+ * Default `toJSON()`: reads each column from the live DOM, navigating to it first
339
+ * (keeps synchronized batches in lock-step) and re-pinning the row before each read.
340
+ */
341
+ async function readRowLive(env, columns) {
342
+ var _a, _b, _c;
343
+ const { config, map, rootLocator, rowIndex } = env;
344
+ const result = {};
345
+ const page = rootLocator.page();
346
+ const getHeaderCell = headerCellGetter(env);
347
+ const pinner = createRowPinner(env);
348
+ // getCell for column overrides: a live cell Locator on the last-known-good row,
349
+ // as fresh as possible, but raw (not override-processed).
350
+ const getCellLive = (colName) => {
351
+ const colIdx = map.get(colName);
352
+ if (colIdx === undefined)
353
+ throw new Error(`[SmartTable] getCell: column "${colName}" not found`);
354
+ return cellIn(env, pinner.current(), colName, colIdx);
355
+ };
356
+ for (const [col, idx] of map.entries()) {
357
+ if (columns && !columns.includes(col))
358
+ continue;
359
+ // Re-pin to the correct logical row before reading this column (#366).
360
+ const stableRow = await pinner.resolveStableRow();
361
+ const mapper = (_b = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b.read;
362
+ const cell = cellIn(env, stableRow, col, idx);
363
+ // Always navigate to keep Lock-Step synchronization even if the cell is
364
+ // already present (navigateToCell handles the barrier check-in).
365
+ const navigatedCell = await (0, cellNavigation_1.navigateToCell)({
366
+ config,
367
+ rootLocator,
368
+ page,
369
+ resolve: env.resolve,
370
+ getHeaders: (_c = env.table) === null || _c === void 0 ? void 0 : _c.getHeaders,
371
+ column: col,
372
+ index: idx,
373
+ rowLocator: stableRow,
374
+ rowIndex,
375
+ renderWindowPosition: env.renderWindowPosition,
376
+ barrier: env.row._barrier,
377
+ allowMissingCell: !!mapper,
378
+ });
379
+ const targetCell = navigatedCell !== null && navigatedCell !== void 0 ? navigatedCell : cell;
380
+ // Fires for BOTH columnOverrides.read and the default innerText path.
381
+ if (config.strategies.beforeCellRead) {
382
+ await config.strategies.beforeCellRead({
383
+ cell: targetCell,
384
+ columnName: col,
385
+ columnIndex: idx,
386
+ row: stableRow,
387
+ page,
388
+ root: rootLocator,
389
+ getHeaderCell,
390
+ });
391
+ }
392
+ const loadOutcome = await waitForCellLoaded(env, targetCell, col);
393
+ if (!loadOutcome.read) {
394
+ result[col] = loadOutcome.value;
395
+ continue;
396
+ }
397
+ if (mapper) {
398
+ result[col] = await mapper(targetCell, { row: env.row, columnName: col, columnIndex: idx, getCell: getCellLive });
399
+ }
400
+ else {
401
+ const text = await targetCell.innerText();
402
+ result[col] = (text || '').trim();
403
+ }
404
+ }
405
+ await computeSyntheticColumns(env, result, columns);
406
+ return result;
407
+ }
@@ -21,10 +21,10 @@ import type { FinalTableConfig } from '../types';
21
21
  * | findRow | yes | options.useBulkPagination | yes | DOM/post filters only |
22
22
  * | countRows | yes | no (single-step) | yes | count-only / post filters |
23
23
  * | async iterator | yes | no | via runMap path | same as map |
24
- * | findRowByIndex | no* | no | n/a | scroll/search, not a full scan |
24
+ * | findRowByIndex | no | no | no | stops once the target index is mounted* |
25
25
  * | bringIntoView | n/a | path planner | n/a | not a page scan |
26
26
  *
27
- * \* findRowByIndex stops when the target index is mounted; it does not collect rows.
27
+ * \* findRowByIndex only falls back to scanPages after the mounted / viewport.scrollToRow paths miss.
28
28
  */
29
29
  export type ScanDecision = 'continue' | 'stop';
30
30
  export interface ScanPageContext {
@@ -0,0 +1,11 @@
1
+ import type { Page } from '@playwright/test';
2
+ import type { FinalTableConfig, StrategyContext } from '../types';
3
+ export interface SortingEnv<T = any> {
4
+ config: FinalTableConfig<T>;
5
+ getPage: () => Page;
6
+ /** Strategy context including `getHeaderCell`. */
7
+ getContext: () => StrategyContext;
8
+ }
9
+ /** Drives the sorting strategy until `columnName` reports `direction`, retrying up to 3 times. */
10
+ export declare function applySort<T>(env: SortingEnv<T>, columnName: string, direction: 'asc' | 'desc'): Promise<void>;
11
+ export declare function getSortState<T>(env: SortingEnv<T>, columnName: string): Promise<'asc' | 'desc' | 'none'>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applySort = applySort;
4
+ exports.getSortState = getSortState;
5
+ const debugUtils_1 = require("../utils/debugUtils");
6
+ const loadingWait_1 = require("../utils/loadingWait");
7
+ const MAX_SORT_ATTEMPTS = 3;
8
+ function requireSorting(config) {
9
+ if (!config.strategies.sorting)
10
+ throw new Error('No sorting strategy has been configured.');
11
+ return config.strategies.sorting;
12
+ }
13
+ /** Waits for the table to settle after `doSort`: `isTableLoading` when configured, otherwise a fixed delay. */
14
+ async function waitForSortToSettle(env, context) {
15
+ var _a, _b, _c, _d;
16
+ const loading = env.config.strategies.loading;
17
+ if (loading === null || loading === void 0 ? void 0 : loading.isTableLoading) {
18
+ const timeout = (_b = (_a = loading.sortStabilizationTimeout) !== null && _a !== void 0 ? _a : loading.loadingTimeout) !== null && _b !== void 0 ? _b : 10000;
19
+ const pollInterval = (_c = loading.sortStabilizationPollInterval) !== null && _c !== void 0 ? _c : 100;
20
+ await (0, loadingWait_1.waitWhileTableLoading)(env.config, context, env.getPage(), 'sorting.apply', { timeout, pollInterval });
21
+ }
22
+ else {
23
+ await env.getPage().waitForTimeout((_d = loading === null || loading === void 0 ? void 0 : loading.sortStabilizationFallbackDelay) !== null && _d !== void 0 ? _d : 200);
24
+ }
25
+ }
26
+ /** Drives the sorting strategy until `columnName` reports `direction`, retrying up to 3 times. */
27
+ async function applySort(env, columnName, direction) {
28
+ const sorting = requireSorting(env.config);
29
+ (0, debugUtils_1.logDebug)(env.config, 'verbose', `Applying sort for column "${columnName}" (${direction})`);
30
+ const context = env.getContext();
31
+ for (let i = 0; i < MAX_SORT_ATTEMPTS; i++) {
32
+ const currentState = await sorting.getSortState({ columnName, context });
33
+ if (currentState === direction) {
34
+ (0, debugUtils_1.logDebug)(env.config, 'verbose', `Sort for "${columnName}" is already "${direction}".`);
35
+ return;
36
+ }
37
+ await sorting.doSort({ columnName, direction, context });
38
+ await waitForSortToSettle(env, context);
39
+ await (0, debugUtils_1.debugDelay)(env.config, 'default');
40
+ const newState = await sorting.getSortState({ columnName, context });
41
+ if (newState === direction) {
42
+ (0, debugUtils_1.logDebug)(env.config, 'verbose', `Successfully sorted "${columnName}" to "${direction}".`);
43
+ return;
44
+ }
45
+ }
46
+ throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${MAX_SORT_ATTEMPTS} attempts.`);
47
+ }
48
+ async function getSortState(env, columnName) {
49
+ const sorting = requireSorting(env.config);
50
+ return sorting.getSortState({ columnName, context: env.getContext() });
51
+ }
@@ -3,4 +3,4 @@
3
3
  * Generated by scripts/embed-version.mjs from package.json
4
4
  */
5
5
  /** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
6
- export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.25.0-next.c784f2c";
6
+ export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.25.0-next.c81cb3d";
@@ -6,4 +6,4 @@ exports.PLAYWRIGHT_SMART_TABLE_VERSION = void 0;
6
6
  * Generated by scripts/embed-version.mjs from package.json
7
7
  */
8
8
  /** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
9
- exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.25.0-next.c784f2c";
9
+ exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.25.0-next.c81cb3d";