@rickcedwhat/playwright-smart-table 6.9.0 → 6.10.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/engine/rowFinder.js +153 -168
- package/dist/engine/tableIteration.js +105 -120
- package/dist/engine/tableMapper.js +88 -103
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/columns.js +23 -32
- package/dist/presets/glide/headers.js +18 -27
- package/dist/presets/glide/index.d.ts +9 -9
- package/dist/presets/glide/index.js +24 -33
- package/dist/presets/mui.js +197 -124
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +32 -41
- package/dist/smartRow.js +162 -96
- package/dist/strategies/columns.js +2 -11
- package/dist/strategies/dedupe.js +3 -12
- package/dist/strategies/fill.js +20 -29
- package/dist/strategies/headers.js +22 -31
- package/dist/strategies/index.d.ts +4 -0
- package/dist/strategies/index.js +3 -0
- package/dist/strategies/loading.js +24 -33
- package/dist/strategies/pagination.js +21 -30
- package/dist/strategies/sorting.js +18 -31
- package/dist/strategies/stabilization.js +28 -37
- package/dist/strategies/viewport.d.ts +52 -0
- package/dist/strategies/viewport.js +123 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +97 -0
- package/dist/types.d.ts +97 -0
- package/dist/useTable.js +98 -76
- package/dist/utils/debugUtils.js +5 -16
- package/dist/utils/elementTracker.js +27 -40
- package/dist/utils/mutex.js +5 -16
- package/dist/utils/navigationBarrier.js +25 -36
- package/dist/utils/paginationPath.js +102 -115
- package/dist/utils/smartRowArray.js +2 -11
- package/dist/utils.js +4 -13
- package/package.json +9 -5
package/dist/types.d.ts
CHANGED
|
@@ -28,10 +28,13 @@ export type FilterValue = string | RegExp | number | ((cell: Locator) => Locator
|
|
|
28
28
|
*/
|
|
29
29
|
export type GetCellLocatorFn = (args: {
|
|
30
30
|
row: Locator;
|
|
31
|
+
/** The root locator passed to useTable(). Useful for re-querying stale row locators. */
|
|
32
|
+
root: Locator;
|
|
31
33
|
columnName: string;
|
|
32
34
|
columnIndex: number;
|
|
33
35
|
rowIndex?: number;
|
|
34
36
|
page: Page;
|
|
37
|
+
config: FinalTableConfig<any>;
|
|
35
38
|
}) => Locator;
|
|
36
39
|
/**
|
|
37
40
|
* Hook called before each cell value is read in toJSON (and columnOverrides.read).
|
|
@@ -68,6 +71,89 @@ export type GetActiveCellFn = (args: TableContext) => Promise<{
|
|
|
68
71
|
columnName?: string;
|
|
69
72
|
locator: Locator;
|
|
70
73
|
} | null>;
|
|
74
|
+
/**
|
|
75
|
+
* Viewport oracle strategies for virtualized tables.
|
|
76
|
+
*
|
|
77
|
+
* These tell the library *what is currently in the DOM* rather than *how to move*.
|
|
78
|
+
* When present, the cell-reading engine uses them to jump directly to a target
|
|
79
|
+
* row/column instead of stepping blindly with navigation primitives.
|
|
80
|
+
*
|
|
81
|
+
* This is the primary fix for the 2D scroll hazard: scrolling right to bring a
|
|
82
|
+
* column into view can knock rows out of the vertical viewport (and vice versa).
|
|
83
|
+
* With `getVisibleRowRange` the engine detects this and calls `scrollToRow` to
|
|
84
|
+
* restore the row before reading — eliminating the hazard without polling loops.
|
|
85
|
+
*
|
|
86
|
+
* All members are optional. Supply whichever the underlying grid exposes:
|
|
87
|
+
* - Range oracles alone enable hazard detection with graceful fallback to navigation primitives.
|
|
88
|
+
* - Scroll primitives alone enable direct jumps without range awareness.
|
|
89
|
+
* - Both together give the most reliable, fastest path.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // MUI DataGrid via apiRef
|
|
93
|
+
* strategies: {
|
|
94
|
+
* viewport: {
|
|
95
|
+
* getVisibleRowRange: async ({ root }) =>
|
|
96
|
+
* root.evaluate(el => {
|
|
97
|
+
* const api = (el as any).__muiDataGrid__;
|
|
98
|
+
* return { first: api.getFirstVisibleRow(), last: api.getLastVisibleRow() };
|
|
99
|
+
* }),
|
|
100
|
+
* scrollToRow: async ({ root }, rowIndex) =>
|
|
101
|
+
* root.evaluate((el, idx) =>
|
|
102
|
+
* (el as any).__muiDataGrid__.scrollToIndexes({ rowIndex: idx }), rowIndex),
|
|
103
|
+
* scrollToColumn: async ({ root }, colIndex) =>
|
|
104
|
+
* root.evaluate((el, idx) =>
|
|
105
|
+
* (el as any).__muiDataGrid__.scrollToIndexes({ colIndex: idx }), colIndex),
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // aria-rowindex / aria-colindex DOM fallback (works without internal API access)
|
|
111
|
+
* strategies: {
|
|
112
|
+
* viewport: {
|
|
113
|
+
* getVisibleRowRange: async ({ root }) =>
|
|
114
|
+
* root.evaluate(el => {
|
|
115
|
+
* const rows = [...el.querySelectorAll('[role="row"][aria-rowindex]')];
|
|
116
|
+
* const idxs = rows.map(r => Number(r.getAttribute('aria-rowindex')) - 1);
|
|
117
|
+
* return { first: Math.min(...idxs), last: Math.max(...idxs) };
|
|
118
|
+
* }),
|
|
119
|
+
* }
|
|
120
|
+
* }
|
|
121
|
+
*/
|
|
122
|
+
export interface ViewportStrategy {
|
|
123
|
+
/**
|
|
124
|
+
* Returns the 0-based index range of rows currently rendered in the DOM.
|
|
125
|
+
* Used to detect when a target row has been scrolled out of view and needs recovery.
|
|
126
|
+
*/
|
|
127
|
+
getVisibleRowRange?: (context: TableContext) => Promise<{
|
|
128
|
+
first: number;
|
|
129
|
+
last: number;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* Returns the 0-based index range of columns currently rendered in the DOM.
|
|
133
|
+
* Used to detect when a target column is not yet mounted before reading.
|
|
134
|
+
*/
|
|
135
|
+
getVisibleColumnRange?: (context: TableContext) => Promise<{
|
|
136
|
+
first: number;
|
|
137
|
+
last: number;
|
|
138
|
+
}>;
|
|
139
|
+
/**
|
|
140
|
+
* Scrolls or jumps directly to make a row visible by 0-based index.
|
|
141
|
+
* Replaces blind goDown()/goUp() step loops for grids that expose a scroll API.
|
|
142
|
+
*/
|
|
143
|
+
scrollToRow?: (context: TableContext, rowIndex: number) => Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Scrolls or jumps directly to make a column visible by 0-based index.
|
|
146
|
+
* Replaces blind goRight()/goLeft() step loops for grids that expose a scroll API.
|
|
147
|
+
*/
|
|
148
|
+
scrollToColumn?: (context: TableContext, colIndex: number) => Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* When true, disables the automatic memoization of getVisibleColumnRange and
|
|
151
|
+
* getVisibleRowRange results. By default the library caches each range value and
|
|
152
|
+
* invalidates it after the corresponding scroll call, eliminating redundant DOM
|
|
153
|
+
* evaluate() round-trips between scrolls.
|
|
154
|
+
*/
|
|
155
|
+
disableCache?: boolean;
|
|
156
|
+
}
|
|
71
157
|
/**
|
|
72
158
|
* SmartRow - A Playwright Locator with table-aware methods.
|
|
73
159
|
*
|
|
@@ -319,6 +405,17 @@ export interface TableStrategies {
|
|
|
319
405
|
* E.g. after sort/pagination, the engine uses loading.isTableLoading when present.
|
|
320
406
|
*/
|
|
321
407
|
loading?: LoadingStrategy;
|
|
408
|
+
/**
|
|
409
|
+
* Viewport oracle strategies for 2D virtualized tables (e.g. MUI DataGrid, AG Grid,
|
|
410
|
+
* Braintrust-style grids where both rows and columns are virtualized simultaneously).
|
|
411
|
+
*
|
|
412
|
+
* When present, the cell-reading engine uses these to jump directly to the target
|
|
413
|
+
* row/column and to detect the 2D scroll hazard (scrolling to reveal a column can
|
|
414
|
+
* knock rows out of the vertical viewport, and vice versa).
|
|
415
|
+
*
|
|
416
|
+
* Completely optional and additive — existing `navigation` primitives remain the fallback.
|
|
417
|
+
*/
|
|
418
|
+
viewport?: ViewportStrategy;
|
|
322
419
|
}
|
|
323
420
|
export interface TableConfig<T = any> {
|
|
324
421
|
/** Selector for the table headers */
|
package/dist/useTable.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
12
3
|
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
13
4
|
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
@@ -56,7 +47,38 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
56
47
|
isHeaderLoading: loading_1.LoadingStrategies.Headers.stable(200)
|
|
57
48
|
}
|
|
58
49
|
};
|
|
59
|
-
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text }) => text, autoScroll: true, onReset: () =>
|
|
50
|
+
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text }) => text, autoScroll: true, onReset: async () => { } }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
|
|
51
|
+
// Automatically memoize viewport range oracles so user-provided functions are
|
|
52
|
+
// pure DOM queries with no cache bookkeeping. Invalidated after each scroll call.
|
|
53
|
+
if (config.strategies.viewport && !config.strategies.viewport.disableCache) {
|
|
54
|
+
const vp = config.strategies.viewport;
|
|
55
|
+
let colRangeCache = null;
|
|
56
|
+
let rowRangeCache = null;
|
|
57
|
+
config.strategies.viewport = Object.assign(Object.assign({}, vp), { getVisibleColumnRange: vp.getVisibleColumnRange
|
|
58
|
+
? async (ctx) => {
|
|
59
|
+
if (!colRangeCache)
|
|
60
|
+
colRangeCache = await vp.getVisibleColumnRange(ctx);
|
|
61
|
+
return colRangeCache;
|
|
62
|
+
}
|
|
63
|
+
: undefined, getVisibleRowRange: vp.getVisibleRowRange
|
|
64
|
+
? async (ctx) => {
|
|
65
|
+
if (!rowRangeCache)
|
|
66
|
+
rowRangeCache = await vp.getVisibleRowRange(ctx);
|
|
67
|
+
return rowRangeCache;
|
|
68
|
+
}
|
|
69
|
+
: undefined, scrollToColumn: vp.scrollToColumn
|
|
70
|
+
? async (ctx, colIndex) => {
|
|
71
|
+
colRangeCache = null;
|
|
72
|
+
rowRangeCache = null;
|
|
73
|
+
await vp.scrollToColumn(ctx, colIndex);
|
|
74
|
+
}
|
|
75
|
+
: undefined, scrollToRow: vp.scrollToRow
|
|
76
|
+
? async (ctx, rowIndex) => {
|
|
77
|
+
rowRangeCache = null;
|
|
78
|
+
await vp.scrollToRow(ctx, rowIndex);
|
|
79
|
+
}
|
|
80
|
+
: undefined });
|
|
81
|
+
}
|
|
60
82
|
const resolve = (item, parent) => {
|
|
61
83
|
if (typeof item === 'string')
|
|
62
84
|
return parent.locator(item);
|
|
@@ -102,7 +124,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
102
124
|
const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart, tableState);
|
|
103
125
|
/** Builds a full TableContext/StrategyContext with getHeaderCell, getHeaders, scrollToColumn. Set after result is created. */
|
|
104
126
|
let createStrategyContext = () => ({ root: rootLocator, config, page: rootLocator.page(), resolve });
|
|
105
|
-
const _getCleanHtml = (loc) =>
|
|
127
|
+
const _getCleanHtml = async (loc) => {
|
|
106
128
|
return loc.evaluate((el) => {
|
|
107
129
|
const clone = el.cloneNode(true);
|
|
108
130
|
const removeSelectors = 'script, style, svg, path, circle, rect, noscript, [hidden]';
|
|
@@ -123,33 +145,33 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
123
145
|
}
|
|
124
146
|
return clone.outerHTML;
|
|
125
147
|
});
|
|
126
|
-
}
|
|
127
|
-
const _handlePrompt = (promptName, content) =>
|
|
148
|
+
};
|
|
149
|
+
const _handlePrompt = async (promptName, content) => {
|
|
128
150
|
let finalPrompt = content;
|
|
129
151
|
finalPrompt += `\n\n👇 Useful TypeScript Definitions 👇\n\`\`\`typescript\n${minimalConfigContext_1.MINIMAL_CONFIG_CONTEXT}\n\`\`\`\n`;
|
|
130
152
|
console.log(`⚠️ Throwing error to display [${promptName}] cleanly...`);
|
|
131
153
|
throw new Error(finalPrompt);
|
|
132
|
-
}
|
|
133
|
-
const _autoInit = () =>
|
|
134
|
-
|
|
135
|
-
}
|
|
154
|
+
};
|
|
155
|
+
const _autoInit = async () => {
|
|
156
|
+
await tableMapper.getMap();
|
|
157
|
+
};
|
|
136
158
|
// Default: goNext (one page). Pass useBulk true to prefer goNextBulk. "How far" uses numeric return when strategy provides it.
|
|
137
|
-
const _advancePage =
|
|
159
|
+
const _advancePage = async (useBulk = false) => {
|
|
138
160
|
const context = createStrategyContext();
|
|
139
161
|
const pagination = config.strategies.pagination;
|
|
140
162
|
let rawResult;
|
|
141
163
|
let primitive;
|
|
142
164
|
if (useBulk && (pagination === null || pagination === void 0 ? void 0 : pagination.goNextBulk)) {
|
|
143
165
|
primitive = 'goNextBulk';
|
|
144
|
-
rawResult =
|
|
166
|
+
rawResult = await pagination.goNextBulk(context);
|
|
145
167
|
}
|
|
146
168
|
else if (pagination === null || pagination === void 0 ? void 0 : pagination.goNext) {
|
|
147
169
|
primitive = 'goNext';
|
|
148
|
-
rawResult =
|
|
170
|
+
rawResult = await pagination.goNext(context);
|
|
149
171
|
}
|
|
150
172
|
else if (pagination === null || pagination === void 0 ? void 0 : pagination.goNextBulk) {
|
|
151
173
|
primitive = 'goNextBulk (fallback)';
|
|
152
|
-
rawResult =
|
|
174
|
+
rawResult = await pagination.goNextBulk(context);
|
|
153
175
|
}
|
|
154
176
|
else {
|
|
155
177
|
log('_advancePage: no pagination primitive available — returning false');
|
|
@@ -165,47 +187,47 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
165
187
|
log(`_advancePage: ${primitive} returned false — end of data`);
|
|
166
188
|
}
|
|
167
189
|
return didAdvance;
|
|
168
|
-
}
|
|
190
|
+
};
|
|
169
191
|
const result = {
|
|
170
192
|
get currentPageIndex() { return tableState.currentPageIndex; },
|
|
171
193
|
set currentPageIndex(v) { tableState.currentPageIndex = v; },
|
|
172
|
-
init: (options) =>
|
|
194
|
+
init: async (options) => {
|
|
173
195
|
if (tableMapper.isInitialized())
|
|
174
196
|
return result;
|
|
175
197
|
(0, debugUtils_1.warnIfDebugInCI)(config);
|
|
176
198
|
(0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
|
|
177
|
-
const map =
|
|
199
|
+
const map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
178
200
|
(0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${map.size} columns`, Array.from(map.keys()));
|
|
179
|
-
|
|
201
|
+
await (0, debugUtils_1.debugDelay)(config, 'default');
|
|
180
202
|
return result;
|
|
181
|
-
}
|
|
182
|
-
scrollToColumn: (columnName) =>
|
|
183
|
-
const map =
|
|
203
|
+
},
|
|
204
|
+
scrollToColumn: async (columnName) => {
|
|
205
|
+
const map = await tableMapper.getMap();
|
|
184
206
|
const idx = map.get(columnName);
|
|
185
207
|
if (idx === undefined)
|
|
186
208
|
throw _createColumnError(columnName, map);
|
|
187
209
|
// Use header cell for scrolling
|
|
188
210
|
const headerCell = resolve(config.headerSelector, rootLocator).nth(idx);
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
getHeaders: () =>
|
|
192
|
-
const map =
|
|
211
|
+
await headerCell.scrollIntoViewIfNeeded();
|
|
212
|
+
},
|
|
213
|
+
getHeaders: async () => {
|
|
214
|
+
const map = await tableMapper.getMap();
|
|
193
215
|
return Array.from(map.keys());
|
|
194
|
-
}
|
|
195
|
-
getHeaderCell: (columnName) =>
|
|
196
|
-
const map =
|
|
216
|
+
},
|
|
217
|
+
getHeaderCell: async (columnName) => {
|
|
218
|
+
const map = await tableMapper.getMap();
|
|
197
219
|
const idx = map.get(columnName);
|
|
198
220
|
if (idx === undefined)
|
|
199
221
|
throw _createColumnError(columnName, map, 'header cell');
|
|
200
222
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
201
|
-
}
|
|
202
|
-
reset: () =>
|
|
223
|
+
},
|
|
224
|
+
reset: async () => {
|
|
203
225
|
var _a;
|
|
204
226
|
log("Resetting table...");
|
|
205
|
-
|
|
227
|
+
await config.onReset(createStrategyContext());
|
|
206
228
|
if ((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.goToFirst) {
|
|
207
229
|
log("Auto-navigating to first page...");
|
|
208
|
-
|
|
230
|
+
await config.strategies.pagination.goToFirst(createStrategyContext());
|
|
209
231
|
}
|
|
210
232
|
else if (hasPaginationInConfig) {
|
|
211
233
|
log("No goToFirst strategy configured. Table may not be on page 1.");
|
|
@@ -214,13 +236,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
214
236
|
tableState.currentPageIndex = 0;
|
|
215
237
|
tableMapper.clear();
|
|
216
238
|
log("Table reset complete. Calling autoInit to restore state.");
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
revalidate: () =>
|
|
239
|
+
await _autoInit();
|
|
240
|
+
},
|
|
241
|
+
revalidate: async () => {
|
|
220
242
|
log("Revalidating table structure...");
|
|
221
|
-
|
|
243
|
+
await tableMapper.remapHeaders();
|
|
222
244
|
log("Table revalidated.");
|
|
223
|
-
}
|
|
245
|
+
},
|
|
224
246
|
getRow: (filters, options = { exact: false }) => {
|
|
225
247
|
const map = tableMapper.getMapSync();
|
|
226
248
|
if (!map)
|
|
@@ -237,53 +259,53 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
237
259
|
const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
|
|
238
260
|
return _makeSmart(rowLocator, map, index);
|
|
239
261
|
},
|
|
240
|
-
findRow: (filters, options) =>
|
|
262
|
+
findRow: async (filters, options) => {
|
|
241
263
|
return rowFinder.findRow(filters, options);
|
|
242
|
-
}
|
|
243
|
-
findRows: (filters, options) =>
|
|
264
|
+
},
|
|
265
|
+
findRows: async (filters, options) => {
|
|
244
266
|
return rowFinder.findRows(filters !== null && filters !== void 0 ? filters : {}, options);
|
|
245
|
-
}
|
|
267
|
+
},
|
|
246
268
|
isInitialized: () => {
|
|
247
269
|
return tableMapper.isInitialized();
|
|
248
270
|
},
|
|
249
271
|
sorting: {
|
|
250
|
-
apply: (columnName, direction) =>
|
|
272
|
+
apply: async (columnName, direction) => {
|
|
251
273
|
var _a;
|
|
252
|
-
|
|
274
|
+
await _autoInit();
|
|
253
275
|
if (!config.strategies.sorting)
|
|
254
276
|
throw new Error('No sorting strategy has been configured.');
|
|
255
277
|
log(`Applying sort for column "${columnName}" (${direction})`);
|
|
256
278
|
const context = Object.assign(Object.assign({}, createStrategyContext()), { getHeaderCell: result.getHeaderCell });
|
|
257
279
|
const maxRetries = 3;
|
|
258
280
|
for (let i = 0; i < maxRetries; i++) {
|
|
259
|
-
const currentState =
|
|
281
|
+
const currentState = await config.strategies.sorting.getSortState({ columnName, context });
|
|
260
282
|
if (currentState === direction) {
|
|
261
283
|
log(`Sort for "${columnName}" is already "${direction}".`);
|
|
262
284
|
return;
|
|
263
285
|
}
|
|
264
|
-
|
|
286
|
+
await config.strategies.sorting.doSort({ columnName, direction, context });
|
|
265
287
|
if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
|
|
266
|
-
|
|
288
|
+
await config.strategies.loading.isTableLoading(context);
|
|
267
289
|
}
|
|
268
290
|
else {
|
|
269
|
-
|
|
291
|
+
await rootLocator.page().waitForTimeout(200);
|
|
270
292
|
}
|
|
271
|
-
|
|
272
|
-
const newState =
|
|
293
|
+
await (0, debugUtils_1.debugDelay)(config, 'default');
|
|
294
|
+
const newState = await config.strategies.sorting.getSortState({ columnName, context });
|
|
273
295
|
if (newState === direction) {
|
|
274
296
|
log(`Successfully sorted "${columnName}" to "${direction}".`);
|
|
275
297
|
return;
|
|
276
298
|
}
|
|
277
299
|
}
|
|
278
300
|
throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${maxRetries} attempts.`);
|
|
279
|
-
}
|
|
280
|
-
getState: (columnName) =>
|
|
281
|
-
|
|
301
|
+
},
|
|
302
|
+
getState: async (columnName) => {
|
|
303
|
+
await _autoInit();
|
|
282
304
|
if (!config.strategies.sorting)
|
|
283
305
|
throw new Error('No sorting strategy has been configured.');
|
|
284
306
|
const context = Object.assign(Object.assign({}, createStrategyContext()), { getHeaderCell: result.getHeaderCell });
|
|
285
307
|
return config.strategies.sorting.getSortState({ columnName, context });
|
|
286
|
-
}
|
|
308
|
+
}
|
|
287
309
|
},
|
|
288
310
|
// ─── Shared async row iterator ───────────────────────────────────────────
|
|
289
311
|
[Symbol.asyncIterator]() {
|
|
@@ -318,9 +340,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
318
340
|
});
|
|
319
341
|
},
|
|
320
342
|
// ─── Row iteration (delegated to engine/tableIteration) ──────────────────
|
|
321
|
-
forEach:
|
|
322
|
-
|
|
323
|
-
|
|
343
|
+
forEach: async (callback, options = {}) => {
|
|
344
|
+
await _autoInit();
|
|
345
|
+
await (0, tableIteration_1.runForEach)({
|
|
324
346
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
325
347
|
getMap: () => tableMapper.getMapSync(),
|
|
326
348
|
advancePage: _advancePage,
|
|
@@ -329,9 +351,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
329
351
|
config,
|
|
330
352
|
getPage: () => rootLocator.page(),
|
|
331
353
|
}, callback, options);
|
|
332
|
-
}
|
|
333
|
-
map:
|
|
334
|
-
|
|
354
|
+
},
|
|
355
|
+
map: async (callback, options = {}) => {
|
|
356
|
+
await _autoInit();
|
|
335
357
|
return (0, tableIteration_1.runMap)({
|
|
336
358
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
337
359
|
getMap: () => tableMapper.getMapSync(),
|
|
@@ -341,9 +363,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
341
363
|
config,
|
|
342
364
|
getPage: () => rootLocator.page(),
|
|
343
365
|
}, callback, options);
|
|
344
|
-
}
|
|
345
|
-
filter:
|
|
346
|
-
|
|
366
|
+
},
|
|
367
|
+
filter: async (predicate, options = {}) => {
|
|
368
|
+
await _autoInit();
|
|
347
369
|
return (0, tableIteration_1.runFilter)({
|
|
348
370
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
349
371
|
getMap: () => tableMapper.getMapSync(),
|
|
@@ -353,17 +375,17 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
353
375
|
config,
|
|
354
376
|
getPage: () => rootLocator.page(),
|
|
355
377
|
}, predicate, options);
|
|
356
|
-
}
|
|
357
|
-
generateConfig: () =>
|
|
358
|
-
const html =
|
|
378
|
+
},
|
|
379
|
+
generateConfig: async () => {
|
|
380
|
+
const html = await _getCleanHtml(rootLocator);
|
|
359
381
|
const separator = "=".repeat(50);
|
|
360
382
|
const content = `\n${separator} \n🤖 COPY INTO GEMINI / ChatGPT 🤖\n${separator} \nI am using 'playwright-smart-table'.\nTarget Table Locator: ${rootLocator.toString()} \nGenerate config for: \n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n${separator}\n`;
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
generateConfigPrompt: () =>
|
|
383
|
+
await _handlePrompt('Smart Table Config', content);
|
|
384
|
+
},
|
|
385
|
+
generateConfigPrompt: async () => {
|
|
364
386
|
console.warn('⚠️ [playwright-smart-table] generateConfigPrompt() is deprecated and will be removed in v7.0.0. Please use generateConfig() instead.');
|
|
365
387
|
return result.generateConfig();
|
|
366
|
-
}
|
|
388
|
+
},
|
|
367
389
|
};
|
|
368
390
|
createStrategyContext = () => ({
|
|
369
391
|
root: rootLocator,
|
package/dist/utils/debugUtils.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.getDebugDelay = getDebugDelay;
|
|
13
4
|
exports.debugDelay = debugDelay;
|
|
@@ -28,13 +19,11 @@ function getDebugDelay(config, actionType) {
|
|
|
28
19
|
/**
|
|
29
20
|
* Add debug delay for specific action type
|
|
30
21
|
*/
|
|
31
|
-
function debugDelay(config, actionType) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
});
|
|
22
|
+
async function debugDelay(config, actionType) {
|
|
23
|
+
const delay = getDebugDelay(config, actionType);
|
|
24
|
+
if (delay > 0) {
|
|
25
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
26
|
+
}
|
|
38
27
|
}
|
|
39
28
|
/**
|
|
40
29
|
* Log debug message based on log level
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.ElementTracker = void 0;
|
|
13
4
|
class ElementTracker {
|
|
@@ -19,42 +10,38 @@ class ElementTracker {
|
|
|
19
10
|
* in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
|
|
20
11
|
* virtualized DOMs (by text signature if nodes are recycled).
|
|
21
12
|
*/
|
|
22
|
-
getUnseenIndices(locators) {
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
async getUnseenIndices(locators) {
|
|
14
|
+
return await locators.evaluateAll((elements, trackerId) => {
|
|
15
|
+
const win = window;
|
|
16
|
+
if (!win[trackerId]) {
|
|
17
|
+
win[trackerId] = new WeakMap();
|
|
18
|
+
}
|
|
19
|
+
const seenMap = win[trackerId];
|
|
20
|
+
const newIndices = [];
|
|
21
|
+
elements.forEach((el, index) => {
|
|
22
|
+
// Determine a lightweight signature for the row (textContent strips HTML, fast)
|
|
23
|
+
const signature = el.textContent || '';
|
|
24
|
+
// If it's a new element, OR a recycled element with new data
|
|
25
|
+
if (seenMap.get(el) !== signature) {
|
|
26
|
+
seenMap.set(el, signature);
|
|
27
|
+
newIndices.push(index);
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
// Determine a lightweight signature for the row (textContent strips HTML, fast)
|
|
33
|
-
const signature = el.textContent || '';
|
|
34
|
-
// If it's a new element, OR a recycled element with new data
|
|
35
|
-
if (seenMap.get(el) !== signature) {
|
|
36
|
-
seenMap.set(el, signature);
|
|
37
|
-
newIndices.push(index);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
return newIndices;
|
|
41
|
-
}, this.id);
|
|
42
|
-
});
|
|
29
|
+
});
|
|
30
|
+
return newIndices;
|
|
31
|
+
}, this.id);
|
|
43
32
|
}
|
|
44
33
|
/**
|
|
45
34
|
* Cleans up the tracking map from the browser window object.
|
|
46
35
|
*/
|
|
47
|
-
cleanup(page) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
});
|
|
36
|
+
async cleanup(page) {
|
|
37
|
+
try {
|
|
38
|
+
await page.evaluate((trackerId) => {
|
|
39
|
+
delete window[trackerId];
|
|
40
|
+
}, this.id);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
// Ignore context destroyed errors during cleanup
|
|
44
|
+
}
|
|
58
45
|
}
|
|
59
46
|
}
|
|
60
47
|
exports.ElementTracker = ElementTracker;
|
package/dist/utils/mutex.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.Mutex = void 0;
|
|
13
4
|
/**
|
|
@@ -18,14 +9,12 @@ class Mutex {
|
|
|
18
9
|
constructor() {
|
|
19
10
|
this.queue = Promise.resolve();
|
|
20
11
|
}
|
|
21
|
-
run(action) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return yield action();
|
|
25
|
-
}));
|
|
26
|
-
this.queue = next.catch(() => { }); // prevent chain breakage
|
|
27
|
-
return yield next;
|
|
12
|
+
async run(action) {
|
|
13
|
+
const next = this.queue.then(async () => {
|
|
14
|
+
return await action();
|
|
28
15
|
});
|
|
16
|
+
this.queue = next.catch(() => { }); // prevent chain breakage
|
|
17
|
+
return await next;
|
|
29
18
|
}
|
|
30
19
|
}
|
|
31
20
|
exports.Mutex = Mutex;
|