@rickcedwhat/playwright-smart-table 6.1.0 → 6.2.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/README.md +2 -0
- package/dist/engine/rowFinder.d.ts +26 -0
- package/dist/engine/rowFinder.js +161 -0
- package/dist/engine/tableMapper.d.ts +16 -0
- package/dist/engine/tableMapper.js +136 -0
- package/dist/{examples/glide-strategies → strategies/glide}/columns.d.ts +1 -1
- package/dist/{examples/glide-strategies → strategies/glide}/headers.d.ts +1 -1
- package/dist/{src/strategies → strategies}/glide.js +2 -2
- package/dist/strategies/index.d.ts +33 -2
- package/dist/strategies/index.js +6 -0
- package/dist/{src/strategies → strategies}/loading.d.ts +14 -0
- package/dist/{src/strategies → strategies}/loading.js +31 -0
- package/dist/strategies/pagination.d.ts +26 -4
- package/dist/strategies/pagination.js +52 -23
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +32 -7
- package/dist/types.d.ts +37 -5
- package/dist/useTable.d.ts +32 -3
- package/dist/useTable.js +81 -224
- package/dist/utils/stringUtils.js +12 -3
- package/package.json +10 -5
- package/dist/src/filterEngine.d.ts +0 -11
- package/dist/src/filterEngine.js +0 -39
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -18
- package/dist/src/smartRow.d.ts +0 -7
- package/dist/src/smartRow.js +0 -160
- package/dist/src/strategies/columns.d.ts +0 -18
- package/dist/src/strategies/columns.js +0 -21
- package/dist/src/strategies/fill.d.ts +0 -7
- package/dist/src/strategies/fill.js +0 -88
- package/dist/src/strategies/headers.d.ts +0 -13
- package/dist/src/strategies/headers.js +0 -30
- package/dist/src/strategies/index.d.ts +0 -54
- package/dist/src/strategies/index.js +0 -43
- package/dist/src/strategies/pagination.d.ts +0 -33
- package/dist/src/strategies/pagination.js +0 -79
- package/dist/src/strategies/resolution.d.ts +0 -22
- package/dist/src/strategies/resolution.js +0 -30
- package/dist/src/strategies/sorting.d.ts +0 -12
- package/dist/src/strategies/sorting.js +0 -68
- package/dist/src/strategies/validation.d.ts +0 -22
- package/dist/src/strategies/validation.js +0 -54
- package/dist/src/strategies/virtualizedPagination.d.ts +0 -32
- package/dist/src/strategies/virtualizedPagination.js +0 -80
- package/dist/src/typeContext.d.ts +0 -6
- package/dist/src/typeContext.js +0 -465
- package/dist/src/types.d.ts +0 -458
- package/dist/src/types.js +0 -2
- package/dist/src/useTable.d.ts +0 -44
- package/dist/src/useTable.js +0 -641
- package/dist/src/utils/debugUtils.d.ts +0 -17
- package/dist/src/utils/debugUtils.js +0 -62
- package/dist/src/utils/smartRowArray.d.ts +0 -14
- package/dist/src/utils/smartRowArray.js +0 -22
- package/dist/src/utils/stringUtils.d.ts +0 -22
- package/dist/src/utils/stringUtils.js +0 -73
- package/dist/src/utils.d.ts +0 -7
- package/dist/src/utils.js +0 -29
- package/dist/utils/traceUtils.d.ts +0 -11
- package/dist/utils/traceUtils.js +0 -47
- /package/dist/{src/plugins.d.ts → plugins.d.ts} +0 -0
- /package/dist/{src/plugins.js → plugins.js} +0 -0
- /package/dist/{src/strategies → strategies}/dedupe.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/dedupe.js +0 -0
- /package/dist/{examples/glide-strategies → strategies/glide}/columns.js +0 -0
- /package/dist/{examples/glide-strategies → strategies/glide}/headers.js +0 -0
- /package/dist/{src/strategies → strategies}/glide.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/rdg.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/rdg.js +0 -0
- /package/dist/{src/strategies → strategies}/stabilization.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/stabilization.js +0 -0
package/dist/useTable.js
CHANGED
|
@@ -9,10 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.Strategies = exports.ResolutionStrategies = exports.CellNavigationStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.SortingStrategies = exports.PaginationStrategies = exports.useTable = void 0;
|
|
12
|
+
exports.Strategies = exports.ResolutionStrategies = exports.CellNavigationStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.DedupeStrategies = exports.SortingStrategies = exports.LoadingStrategies = exports.PaginationStrategies = exports.useTable = void 0;
|
|
13
13
|
const typeContext_1 = require("./typeContext");
|
|
14
14
|
const sorting_1 = require("./strategies/sorting");
|
|
15
15
|
const pagination_1 = require("./strategies/pagination");
|
|
16
|
+
const dedupe_1 = require("./strategies/dedupe");
|
|
17
|
+
const loading_1 = require("./strategies/loading");
|
|
16
18
|
const fill_1 = require("./strategies/fill");
|
|
17
19
|
Object.defineProperty(exports, "FillStrategies", { enumerable: true, get: function () { return fill_1.FillStrategies; } });
|
|
18
20
|
const headers_1 = require("./strategies/headers");
|
|
@@ -21,11 +23,12 @@ const columns_1 = require("./strategies/columns");
|
|
|
21
23
|
Object.defineProperty(exports, "CellNavigationStrategies", { enumerable: true, get: function () { return columns_1.CellNavigationStrategies; } });
|
|
22
24
|
const smartRow_1 = require("./smartRow");
|
|
23
25
|
const filterEngine_1 = require("./filterEngine");
|
|
26
|
+
const tableMapper_1 = require("./engine/tableMapper");
|
|
27
|
+
const rowFinder_1 = require("./engine/rowFinder");
|
|
24
28
|
const resolution_1 = require("./strategies/resolution");
|
|
25
29
|
Object.defineProperty(exports, "ResolutionStrategies", { enumerable: true, get: function () { return resolution_1.ResolutionStrategies; } });
|
|
26
30
|
const strategies_1 = require("./strategies");
|
|
27
31
|
Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
|
|
28
|
-
const validation_1 = require("./strategies/validation");
|
|
29
32
|
const debugUtils_1 = require("./utils/debugUtils");
|
|
30
33
|
const smartRowArray_1 = require("./utils/smartRowArray");
|
|
31
34
|
/**
|
|
@@ -41,8 +44,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
41
44
|
header: headers_1.HeaderStrategies.visible,
|
|
42
45
|
cellNavigation: columns_1.CellNavigationStrategies.default,
|
|
43
46
|
pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
47
|
+
loading: {
|
|
48
|
+
isHeaderLoading: loading_1.LoadingStrategies.Headers.stable(200)
|
|
49
|
+
}
|
|
44
50
|
};
|
|
45
|
-
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text
|
|
51
|
+
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "thead th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text }) => text, autoScroll: true, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
|
|
46
52
|
const resolve = (item, parent) => {
|
|
47
53
|
if (typeof item === 'string')
|
|
48
54
|
return parent.locator(item);
|
|
@@ -51,16 +57,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
51
57
|
return item;
|
|
52
58
|
};
|
|
53
59
|
// Internal State
|
|
54
|
-
let _headerMap = null;
|
|
55
60
|
let _hasPaginated = false;
|
|
56
|
-
let _isInitialized = false;
|
|
57
61
|
// Helpers
|
|
58
62
|
const log = (msg) => {
|
|
59
|
-
(0, debugUtils_1.logDebug)(config, 'verbose', msg);
|
|
63
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', msg);
|
|
60
64
|
};
|
|
61
65
|
const _createColumnError = (colName, map, context) => {
|
|
62
66
|
const availableColumns = Array.from(map.keys());
|
|
63
|
-
// Use Suggestion Logic from ResolutionStrategy (if we had a fuzzy one, for now manual suggest)
|
|
64
67
|
const lowerCol = colName.toLowerCase();
|
|
65
68
|
const suggestions = availableColumns.filter(col => col.toLowerCase().includes(lowerCol) ||
|
|
66
69
|
lowerCol.includes(col.toLowerCase()) ||
|
|
@@ -73,130 +76,21 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
73
76
|
suggestion = `. Available columns: ${availableColumns.map(c => `"${c}"`).join(', ')}`;
|
|
74
77
|
}
|
|
75
78
|
else if (availableColumns.length > 0) {
|
|
76
|
-
suggestion = `. Available columns (first
|
|
79
|
+
suggestion = `. Available columns (first 10 of ${availableColumns.length}): ${availableColumns.slice(0, 10).map(c => `"${c}"`).join(', ')}, ...`;
|
|
77
80
|
}
|
|
78
81
|
const contextMsg = context ? ` (${context})` : '';
|
|
79
82
|
return new Error(`Column "${colName}" not found${contextMsg}${suggestion}`);
|
|
80
83
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
log('Mapping headers...');
|
|
85
|
-
const headerTimeout = timeout !== null && timeout !== void 0 ? timeout : 3000;
|
|
86
|
-
if (config.autoScroll) {
|
|
87
|
-
try {
|
|
88
|
-
yield rootLocator.scrollIntoViewIfNeeded({ timeout: 1000 });
|
|
89
|
-
}
|
|
90
|
-
catch (e) { }
|
|
91
|
-
}
|
|
92
|
-
const headerLoc = resolve(config.headerSelector, rootLocator);
|
|
93
|
-
try {
|
|
94
|
-
yield headerLoc.first().waitFor({ state: 'visible', timeout: headerTimeout });
|
|
95
|
-
}
|
|
96
|
-
catch (e) { /* Ignore hydration */ }
|
|
97
|
-
const strategy = config.strategies.header || headers_1.HeaderStrategies.visible;
|
|
98
|
-
const context = {
|
|
99
|
-
root: rootLocator,
|
|
100
|
-
config: config,
|
|
101
|
-
page: rootLocator.page(),
|
|
102
|
-
resolve: resolve
|
|
103
|
-
};
|
|
104
|
-
const rawHeaders = yield strategy(context);
|
|
105
|
-
const entries = yield Promise.all(rawHeaders.map((t, i) => __awaiter(void 0, void 0, void 0, function* () {
|
|
106
|
-
let text = t.trim() || `__col_${i}`;
|
|
107
|
-
if (config.headerTransformer) {
|
|
108
|
-
text = yield config.headerTransformer({
|
|
109
|
-
text,
|
|
110
|
-
index: i,
|
|
111
|
-
locator: rootLocator.locator(config.headerSelector).nth(i)
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
return [text, i];
|
|
115
|
-
})));
|
|
116
|
-
// Validation: Check for empty table
|
|
117
|
-
if (entries.length === 0) {
|
|
118
|
-
throw new Error(`Initialization Error: No columns found using selector "${config.headerSelector}". Check your selector or ensure the table is visible.`);
|
|
119
|
-
}
|
|
120
|
-
// Validation: Check for duplicates
|
|
121
|
-
const seen = new Set();
|
|
122
|
-
const duplicates = new Set();
|
|
123
|
-
for (const [name] of entries) {
|
|
124
|
-
if (seen.has(name)) {
|
|
125
|
-
duplicates.add(name);
|
|
126
|
-
}
|
|
127
|
-
seen.add(name);
|
|
128
|
-
}
|
|
129
|
-
if (duplicates.size > 0) {
|
|
130
|
-
const dupList = Array.from(duplicates).map(d => `"${d}"`).join(', ');
|
|
131
|
-
throw new Error(`Initialization Error: Duplicate column names found: ${dupList}. Use 'headerTransformer' to rename duplicate columns.`);
|
|
132
|
-
}
|
|
133
|
-
_headerMap = new Map(entries);
|
|
134
|
-
log(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
135
|
-
return _headerMap;
|
|
136
|
-
});
|
|
84
|
+
// Engines
|
|
85
|
+
const filterEngine = new filterEngine_1.FilterEngine(config, resolve);
|
|
86
|
+
const tableMapper = new tableMapper_1.TableMapper(rootLocator, config, resolve);
|
|
137
87
|
// Placeholder for the final table object
|
|
138
88
|
let finalTable = null;
|
|
139
|
-
const filterEngine = new filterEngine_1.FilterEngine(config, resolve);
|
|
140
89
|
// Helper factory
|
|
141
90
|
const _makeSmart = (rowLocator, map, rowIndex) => {
|
|
142
|
-
// Use the wrapped SmartRow logic
|
|
143
91
|
return (0, smartRow_1.createSmartRow)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable);
|
|
144
92
|
};
|
|
145
|
-
const
|
|
146
|
-
var _a;
|
|
147
|
-
const map = yield _getMap();
|
|
148
|
-
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
|
|
149
|
-
let currentPage = 1;
|
|
150
|
-
log(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
151
|
-
while (true) {
|
|
152
|
-
const allRows = resolve(config.rowSelector, rootLocator);
|
|
153
|
-
// Use FilterEngine
|
|
154
|
-
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
|
|
155
|
-
const count = yield matchedRows.count();
|
|
156
|
-
log(`Page ${currentPage}: Found ${count} matches.`);
|
|
157
|
-
if (count > 1) {
|
|
158
|
-
// Sample data logic (simplified for refactor, kept inline or moved to util if needed)
|
|
159
|
-
const sampleData = [];
|
|
160
|
-
try {
|
|
161
|
-
const firstFewRows = yield matchedRows.all();
|
|
162
|
-
const sampleCount = Math.min(firstFewRows.length, 3);
|
|
163
|
-
for (let i = 0; i < sampleCount; i++) {
|
|
164
|
-
const rowData = yield _makeSmart(firstFewRows[i], map).toJSON();
|
|
165
|
-
sampleData.push(JSON.stringify(rowData));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
catch (e) { }
|
|
169
|
-
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
170
|
-
throw new Error(`Strict Mode Violation: Found ${count} rows matching ${JSON.stringify(filters)} on page ${currentPage}. ` +
|
|
171
|
-
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
172
|
-
}
|
|
173
|
-
if (count === 1)
|
|
174
|
-
return matchedRows.first();
|
|
175
|
-
if (currentPage < effectiveMaxPages) {
|
|
176
|
-
log(`Page ${currentPage}: Not found. Attempting pagination...`);
|
|
177
|
-
const context = {
|
|
178
|
-
root: rootLocator,
|
|
179
|
-
config: config,
|
|
180
|
-
page: rootLocator.page(),
|
|
181
|
-
resolve: resolve
|
|
182
|
-
};
|
|
183
|
-
const paginationResult = yield config.strategies.pagination(context);
|
|
184
|
-
const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
185
|
-
if (didLoadMore) {
|
|
186
|
-
_hasPaginated = true;
|
|
187
|
-
currentPage++;
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
log(`Page ${currentPage}: Pagination failed (end of data).`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (_hasPaginated) {
|
|
195
|
-
console.warn(`⚠️ [SmartTable] Row not found. The table has been paginated (Current Page: ${currentPage}). You may need to call 'await table.reset()' if the target row is on a previous page.`);
|
|
196
|
-
}
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
});
|
|
93
|
+
const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart);
|
|
200
94
|
const _getCleanHtml = (loc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
201
95
|
return loc.evaluate((el) => {
|
|
202
96
|
const clone = el.cloneNode(true);
|
|
@@ -232,28 +126,21 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
232
126
|
console.log(finalPrompt);
|
|
233
127
|
});
|
|
234
128
|
const _ensureInitialized = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
235
|
-
|
|
236
|
-
yield _getMap();
|
|
237
|
-
_isInitialized = true;
|
|
238
|
-
}
|
|
129
|
+
yield tableMapper.getMap();
|
|
239
130
|
});
|
|
240
131
|
const result = {
|
|
241
132
|
init: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
242
|
-
if (
|
|
133
|
+
if (tableMapper.isInitialized())
|
|
243
134
|
return result;
|
|
244
135
|
(0, debugUtils_1.warnIfDebugInCI)(config);
|
|
245
136
|
(0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
|
|
246
|
-
yield
|
|
247
|
-
|
|
248
|
-
if (_headerMap) {
|
|
249
|
-
(0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${_headerMap.size} columns`, Array.from(_headerMap.keys()));
|
|
250
|
-
// Trace event removed - redundant with debug logging
|
|
251
|
-
}
|
|
137
|
+
const map = yield tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
138
|
+
(0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${map.size} columns`, Array.from(map.keys()));
|
|
252
139
|
yield (0, debugUtils_1.debugDelay)(config, 'default');
|
|
253
140
|
return result;
|
|
254
141
|
}),
|
|
255
142
|
scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
256
|
-
const map = yield
|
|
143
|
+
const map = yield tableMapper.getMap();
|
|
257
144
|
const idx = map.get(columnName);
|
|
258
145
|
if (idx === undefined)
|
|
259
146
|
throw _createColumnError(columnName, map);
|
|
@@ -267,16 +154,14 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
267
154
|
});
|
|
268
155
|
}),
|
|
269
156
|
getHeaders: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return Array.from(_headerMap.keys());
|
|
157
|
+
const map = yield tableMapper.getMap();
|
|
158
|
+
return Array.from(map.keys());
|
|
273
159
|
}),
|
|
274
160
|
getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const idx = _headerMap.get(columnName);
|
|
161
|
+
const map = yield tableMapper.getMap();
|
|
162
|
+
const idx = map.get(columnName);
|
|
278
163
|
if (idx === undefined)
|
|
279
|
-
throw _createColumnError(columnName,
|
|
164
|
+
throw _createColumnError(columnName, map, 'header cell');
|
|
280
165
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
281
166
|
}),
|
|
282
167
|
reset: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -284,22 +169,20 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
284
169
|
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
285
170
|
yield config.onReset(context);
|
|
286
171
|
_hasPaginated = false;
|
|
287
|
-
|
|
288
|
-
_isInitialized = false;
|
|
172
|
+
tableMapper.clear();
|
|
289
173
|
log("Table reset complete.");
|
|
290
174
|
}),
|
|
291
175
|
revalidate: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
292
176
|
log("Revalidating table structure...");
|
|
293
|
-
|
|
294
|
-
yield _getMap(); // Re-scan headers
|
|
177
|
+
yield tableMapper.remapHeaders();
|
|
295
178
|
log("Table revalidated.");
|
|
296
179
|
}),
|
|
297
180
|
getColumnValues: (column, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
298
181
|
var _a, _b;
|
|
299
|
-
yield
|
|
300
|
-
const colIdx =
|
|
182
|
+
const map = yield tableMapper.getMap();
|
|
183
|
+
const colIdx = map.get(column);
|
|
301
184
|
if (colIdx === undefined)
|
|
302
|
-
throw _createColumnError(column,
|
|
185
|
+
throw _createColumnError(column, map);
|
|
303
186
|
const mapper = (_a = options === null || options === void 0 ? void 0 : options.mapper) !== null && _a !== void 0 ? _a : ((c) => c.innerText());
|
|
304
187
|
const effectiveMaxPages = (_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages;
|
|
305
188
|
let currentPage = 1;
|
|
@@ -326,82 +209,33 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
326
209
|
return results;
|
|
327
210
|
}),
|
|
328
211
|
getRow: (filters, options = { exact: false }) => {
|
|
329
|
-
|
|
212
|
+
const map = tableMapper.getMapSync();
|
|
213
|
+
if (!map)
|
|
330
214
|
throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
|
|
331
215
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
332
|
-
const matchedRows = filterEngine.applyFilters(allRows, filters,
|
|
216
|
+
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
|
|
333
217
|
const rowLocator = matchedRows.first();
|
|
334
|
-
return _makeSmart(rowLocator,
|
|
218
|
+
return _makeSmart(rowLocator, map, 0); // fallback index 0
|
|
335
219
|
},
|
|
336
220
|
getRowByIndex: (index, options = {}) => {
|
|
337
|
-
|
|
221
|
+
const map = tableMapper.getMapSync();
|
|
222
|
+
if (!map)
|
|
338
223
|
throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
return _makeSmart(rowLocator, _headerMap, rowIndex);
|
|
224
|
+
const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
|
|
225
|
+
return _makeSmart(rowLocator, map, index);
|
|
342
226
|
},
|
|
343
227
|
findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
344
|
-
|
|
345
|
-
yield _ensureInitialized();
|
|
346
|
-
let row = yield _findRowLocator(filters, options);
|
|
347
|
-
if (row) {
|
|
348
|
-
(0, debugUtils_1.logDebug)(config, 'info', 'Row found');
|
|
349
|
-
yield (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
350
|
-
return _makeSmart(row, _headerMap, 0);
|
|
351
|
-
}
|
|
352
|
-
(0, debugUtils_1.logDebug)(config, 'error', 'Row not found', filters);
|
|
353
|
-
yield (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
354
|
-
// Return sentinel row
|
|
355
|
-
row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
356
|
-
return _makeSmart(row, _headerMap, 0);
|
|
228
|
+
return rowFinder.findRow(filters, options);
|
|
357
229
|
}),
|
|
358
230
|
getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (options === null || options === void 0 ? void 0 : options.filter) {
|
|
362
|
-
rowLocators = filterEngine.applyFilters(rowLocators, options.filter, _headerMap, options.exact || false, rootLocator.page());
|
|
363
|
-
}
|
|
364
|
-
const rows = yield rowLocators.all();
|
|
365
|
-
const smartRows = rows.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
366
|
-
return (0, smartRowArray_1.createSmartRowArray)(smartRows);
|
|
231
|
+
console.warn('DEPRECATED: table.getRows() is deprecated and will be removed in a future version. Use table.findRows() instead.');
|
|
232
|
+
return rowFinder.findRows((options === null || options === void 0 ? void 0 : options.filter) || {}, Object.assign(Object.assign({}, options), { maxPages: 1 }));
|
|
367
233
|
}),
|
|
368
234
|
findRows: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
369
|
-
|
|
370
|
-
yield _ensureInitialized();
|
|
371
|
-
const allRows = [];
|
|
372
|
-
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
373
|
-
let pageCount = 0;
|
|
374
|
-
// Collect rows from current page
|
|
375
|
-
let rowLocators = resolve(config.rowSelector, rootLocator);
|
|
376
|
-
rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false, rootLocator.page());
|
|
377
|
-
let rows = yield rowLocators.all();
|
|
378
|
-
allRows.push(...rows.map((loc, i) => _makeSmart(loc, _headerMap, i)));
|
|
379
|
-
// Paginate and collect more rows
|
|
380
|
-
while (pageCount < effectiveMaxPages && config.strategies.pagination) {
|
|
381
|
-
const paginationResult = yield config.strategies.pagination({
|
|
382
|
-
root: rootLocator,
|
|
383
|
-
config,
|
|
384
|
-
resolve,
|
|
385
|
-
page: rootLocator.page()
|
|
386
|
-
});
|
|
387
|
-
const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
|
|
388
|
-
if (!didPaginate)
|
|
389
|
-
break;
|
|
390
|
-
pageCount++;
|
|
391
|
-
_hasPaginated = true;
|
|
392
|
-
// Collect rows from new page
|
|
393
|
-
rowLocators = resolve(config.rowSelector, rootLocator);
|
|
394
|
-
rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_d = options === null || options === void 0 ? void 0 : options.exact) !== null && _d !== void 0 ? _d : false, rootLocator.page());
|
|
395
|
-
rows = yield rowLocators.all();
|
|
396
|
-
allRows.push(...rows.map((loc, i) => _makeSmart(loc, _headerMap, i)));
|
|
397
|
-
}
|
|
398
|
-
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
399
|
-
return Promise.all(allRows.map(r => r.toJSON()));
|
|
400
|
-
}
|
|
401
|
-
return allRows;
|
|
235
|
+
return rowFinder.findRows(filters, options);
|
|
402
236
|
}),
|
|
403
237
|
isInitialized: () => {
|
|
404
|
-
return
|
|
238
|
+
return tableMapper.isInitialized();
|
|
405
239
|
},
|
|
406
240
|
sorting: {
|
|
407
241
|
apply: (columnName, direction) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -421,7 +255,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
421
255
|
})
|
|
422
256
|
},
|
|
423
257
|
iterateThroughTable: (callback, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
424
|
-
var _a, _b, _c, _d;
|
|
258
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
425
259
|
yield _ensureInitialized();
|
|
426
260
|
const paginationStrategy = (_a = options === null || options === void 0 ? void 0 : options.pagination) !== null && _a !== void 0 ? _a : config.strategies.pagination;
|
|
427
261
|
const hasPaginationInOptions = (options === null || options === void 0 ? void 0 : options.pagination) !== undefined;
|
|
@@ -429,6 +263,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
429
263
|
throw new Error('No pagination strategy provided.');
|
|
430
264
|
yield result.reset();
|
|
431
265
|
yield result.init();
|
|
266
|
+
const map = tableMapper.getMapSync();
|
|
432
267
|
const restrictedTable = {
|
|
433
268
|
init: result.init,
|
|
434
269
|
getHeaders: result.getHeaders,
|
|
@@ -451,6 +286,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
451
286
|
const effectiveMaxIterations = (_d = options === null || options === void 0 ? void 0 : options.maxIterations) !== null && _d !== void 0 ? _d : config.maxPages;
|
|
452
287
|
const batchSize = options === null || options === void 0 ? void 0 : options.batchSize;
|
|
453
288
|
const isBatching = batchSize !== undefined && batchSize > 1;
|
|
289
|
+
const autoFlatten = (_e = options === null || options === void 0 ? void 0 : options.autoFlatten) !== null && _e !== void 0 ? _e : false;
|
|
454
290
|
let index = 0;
|
|
455
291
|
let paginationResult = true;
|
|
456
292
|
let seenKeys = null;
|
|
@@ -459,19 +295,28 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
459
295
|
log(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations}, batchSize: ${batchSize !== null && batchSize !== void 0 ? batchSize : 'none'})`);
|
|
460
296
|
while (index < effectiveMaxIterations) {
|
|
461
297
|
const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
|
|
462
|
-
|
|
463
|
-
|
|
298
|
+
const smartRowsArray = [];
|
|
299
|
+
const isRowLoading = (_f = config.strategies.loading) === null || _f === void 0 ? void 0 : _f.isRowLoading;
|
|
300
|
+
for (let i = 0; i < rowLocators.length; i++) {
|
|
301
|
+
const smartRow = _makeSmart(rowLocators[i], map, i);
|
|
302
|
+
if (isRowLoading && (yield isRowLoading(smartRow)))
|
|
303
|
+
continue;
|
|
304
|
+
smartRowsArray.push(smartRow);
|
|
305
|
+
}
|
|
306
|
+
let rows = (0, smartRowArray_1.createSmartRowArray)(smartRowsArray);
|
|
307
|
+
const dedupeStrategy = (_g = options === null || options === void 0 ? void 0 : options.dedupeStrategy) !== null && _g !== void 0 ? _g : config.strategies.dedupe;
|
|
308
|
+
if (dedupeStrategy && rows.length > 0) {
|
|
464
309
|
if (!seenKeys)
|
|
465
310
|
seenKeys = new Set();
|
|
466
311
|
const deduplicated = [];
|
|
467
312
|
for (const row of rows) {
|
|
468
|
-
const key = yield
|
|
313
|
+
const key = yield dedupeStrategy(row);
|
|
469
314
|
if (!seenKeys.has(key)) {
|
|
470
315
|
seenKeys.add(key);
|
|
471
316
|
deduplicated.push(row);
|
|
472
317
|
}
|
|
473
318
|
}
|
|
474
|
-
rows = deduplicated;
|
|
319
|
+
rows = (0, smartRowArray_1.createSmartRowArray)(deduplicated);
|
|
475
320
|
log(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
|
|
476
321
|
}
|
|
477
322
|
// Add rows to batch if batching is enabled
|
|
@@ -489,7 +334,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
489
334
|
let isLast = getIsLast({ index: callbackIndex, paginationResult });
|
|
490
335
|
const isLastDueToMax = index === effectiveMaxIterations - 1;
|
|
491
336
|
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
492
|
-
yield options.beforeFirst({ index: callbackIndex, rows: callbackRows, allData });
|
|
337
|
+
yield options.beforeFirst({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(callbackRows), allData });
|
|
493
338
|
}
|
|
494
339
|
const batchInfo = isBatching ? {
|
|
495
340
|
startIndex: batchStartIndex,
|
|
@@ -500,12 +345,17 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
500
345
|
index: callbackIndex,
|
|
501
346
|
isFirst,
|
|
502
347
|
isLast,
|
|
503
|
-
rows: callbackRows,
|
|
348
|
+
rows: (0, smartRowArray_1.createSmartRowArray)(callbackRows),
|
|
504
349
|
allData,
|
|
505
350
|
table: restrictedTable,
|
|
506
351
|
batchInfo
|
|
507
352
|
});
|
|
508
|
-
|
|
353
|
+
if (autoFlatten && Array.isArray(returnValue)) {
|
|
354
|
+
allData.push(...returnValue);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
allData.push(returnValue);
|
|
358
|
+
}
|
|
509
359
|
// Determine if this is truly the last iteration
|
|
510
360
|
let finalIsLast = isLastDueToMax;
|
|
511
361
|
if (!isLastIteration) {
|
|
@@ -516,7 +366,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
516
366
|
finalIsLast = getIsLast({ index: callbackIndex, paginationResult }) || !paginationResult;
|
|
517
367
|
}
|
|
518
368
|
if (finalIsLast && (options === null || options === void 0 ? void 0 : options.afterLast)) {
|
|
519
|
-
yield options.afterLast({ index: callbackIndex, rows: callbackRows, allData });
|
|
369
|
+
yield options.afterLast({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(callbackRows), allData });
|
|
520
370
|
}
|
|
521
371
|
if (finalIsLast || !paginationResult) {
|
|
522
372
|
log(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
|
|
@@ -540,7 +390,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
540
390
|
const isFirst = getIsFirst({ index: callbackIndex });
|
|
541
391
|
const isLast = true;
|
|
542
392
|
if (isFirst && (options === null || options === void 0 ? void 0 : options.beforeFirst)) {
|
|
543
|
-
yield options.beforeFirst({ index: callbackIndex, rows: batchRows, allData });
|
|
393
|
+
yield options.beforeFirst({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(batchRows), allData });
|
|
544
394
|
}
|
|
545
395
|
const batchInfo = {
|
|
546
396
|
startIndex: batchStartIndex,
|
|
@@ -551,14 +401,19 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
551
401
|
index: callbackIndex,
|
|
552
402
|
isFirst,
|
|
553
403
|
isLast,
|
|
554
|
-
rows: batchRows,
|
|
404
|
+
rows: (0, smartRowArray_1.createSmartRowArray)(batchRows),
|
|
555
405
|
allData,
|
|
556
406
|
table: restrictedTable,
|
|
557
407
|
batchInfo
|
|
558
408
|
});
|
|
559
|
-
|
|
409
|
+
if (autoFlatten && Array.isArray(returnValue)) {
|
|
410
|
+
allData.push(...returnValue);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
allData.push(returnValue);
|
|
414
|
+
}
|
|
560
415
|
if (options === null || options === void 0 ? void 0 : options.afterLast) {
|
|
561
|
-
yield options.afterLast({ index: callbackIndex, rows: batchRows, allData });
|
|
416
|
+
yield options.afterLast({ index: callbackIndex, rows: (0, smartRowArray_1.createSmartRowArray)(batchRows), allData });
|
|
562
417
|
}
|
|
563
418
|
log(`Pagination failed mid-batch (index: ${index})`);
|
|
564
419
|
break;
|
|
@@ -573,7 +428,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
573
428
|
generateConfigPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
574
429
|
const html = yield _getCleanHtml(rootLocator);
|
|
575
430
|
const separator = "=".repeat(50);
|
|
576
|
-
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
|
|
431
|
+
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`;
|
|
577
432
|
yield _handlePrompt('Smart Table Config', content, options);
|
|
578
433
|
}),
|
|
579
434
|
};
|
|
@@ -581,5 +436,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
581
436
|
return result;
|
|
582
437
|
};
|
|
583
438
|
exports.useTable = useTable;
|
|
584
|
-
exports.PaginationStrategies = pagination_1.PaginationStrategies;
|
|
439
|
+
exports.PaginationStrategies = Object.assign({}, pagination_1.PaginationStrategies);
|
|
440
|
+
exports.LoadingStrategies = loading_1.LoadingStrategies;
|
|
585
441
|
exports.SortingStrategies = sorting_1.SortingStrategies;
|
|
442
|
+
exports.DedupeStrategies = dedupe_1.DedupeStrategies;
|
|
@@ -18,11 +18,19 @@ function levenshteinDistance(a, b) {
|
|
|
18
18
|
}
|
|
19
19
|
for (let i = 1; i <= b.length; i++) {
|
|
20
20
|
for (let j = 1; j <= a.length; j++) {
|
|
21
|
-
|
|
21
|
+
const charB = b.charAt(i - 1);
|
|
22
|
+
const charA = a.charAt(j - 1);
|
|
23
|
+
if (charB === charA) {
|
|
24
|
+
// Exact match
|
|
22
25
|
matrix[i][j] = matrix[i - 1][j - 1];
|
|
23
26
|
}
|
|
24
27
|
else {
|
|
25
|
-
|
|
28
|
+
let cost = 1;
|
|
29
|
+
// If characters match ignoring case, cost is only 0.1 (almost identical)
|
|
30
|
+
if (charB.toLowerCase() === charA.toLowerCase()) {
|
|
31
|
+
cost = 0.1;
|
|
32
|
+
}
|
|
33
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + cost, // substitution
|
|
26
34
|
matrix[i][j - 1] + 1, // insertion
|
|
27
35
|
matrix[i - 1][j] + 1 // deletion
|
|
28
36
|
);
|
|
@@ -36,7 +44,8 @@ function levenshteinDistance(a, b) {
|
|
|
36
44
|
* 1 = identical, 0 = completely different
|
|
37
45
|
*/
|
|
38
46
|
function stringSimilarity(a, b) {
|
|
39
|
-
|
|
47
|
+
// We do NOT modify case here anymore, because levenshteinDistance now handles case weighting
|
|
48
|
+
const distance = levenshteinDistance(a, b);
|
|
40
49
|
const maxLen = Math.max(a.length, b.length);
|
|
41
50
|
return maxLen === 0 ? 1 : 1 - (distance / maxLen);
|
|
42
51
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "Smart, column-aware table interactions for Playwright",
|
|
5
5
|
"author": "Cedrick Catalan",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"docs:build": "vitepress build docs",
|
|
23
23
|
"build": "npm run generate-types && npm run generate-docs && npm run generate-all-api-docs && npm run update-all-api-signatures && tsc",
|
|
24
24
|
"prepublishOnly": "npm run build",
|
|
25
|
-
"test": "npx playwright test",
|
|
25
|
+
"test": "npm run test:unit && npx playwright test",
|
|
26
|
+
"test:unit": "vitest run --reporter=verbose --reporter=html",
|
|
27
|
+
"test:unit:ui": "vitest --ui",
|
|
28
|
+
"test:e2e": "npx playwright test",
|
|
26
29
|
"test:compatibility": "npx playwright test compatibility",
|
|
27
30
|
"prepare": "husky install"
|
|
28
31
|
},
|
|
@@ -38,9 +41,11 @@
|
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@playwright/test": "^1.49.1",
|
|
40
43
|
"@types/node": "^22.10.5",
|
|
44
|
+
"@vitest/ui": "^4.0.18",
|
|
45
|
+
"happy-dom": "^20.6.1",
|
|
41
46
|
"husky": "^9.1.7",
|
|
42
47
|
"typescript": "^5.7.2",
|
|
43
|
-
"vitepress": "^1.6.4"
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
"vitepress": "^1.6.4",
|
|
49
|
+
"vitest": "^4.0.18"
|
|
50
|
+
}
|
|
46
51
|
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Locator, Page } from "@playwright/test";
|
|
2
|
-
import { FinalTableConfig } from "./types";
|
|
3
|
-
export declare class FilterEngine {
|
|
4
|
-
private config;
|
|
5
|
-
private resolve;
|
|
6
|
-
constructor(config: FinalTableConfig, resolve: (selector: any, parent: Locator | Page) => Locator);
|
|
7
|
-
/**
|
|
8
|
-
* Applies filters to a set of rows.
|
|
9
|
-
*/
|
|
10
|
-
applyFilters(baseRows: Locator, filters: Record<string, string | RegExp | number>, map: Map<string, number>, exact: boolean, page: Page): Locator;
|
|
11
|
-
}
|
package/dist/src/filterEngine.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FilterEngine = void 0;
|
|
4
|
-
const stringUtils_1 = require("./utils/stringUtils");
|
|
5
|
-
class FilterEngine {
|
|
6
|
-
constructor(config, resolve) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
this.resolve = resolve;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Applies filters to a set of rows.
|
|
12
|
-
*/
|
|
13
|
-
applyFilters(baseRows, filters, map, exact, page) {
|
|
14
|
-
let filtered = baseRows;
|
|
15
|
-
// Iterate through each filter criteria
|
|
16
|
-
for (const [colName, value] of Object.entries(filters)) {
|
|
17
|
-
// Find column index
|
|
18
|
-
const colIndex = map.get(colName);
|
|
19
|
-
// TODO: Use ColumnStrategy for better resolution error handling
|
|
20
|
-
if (colIndex === undefined) {
|
|
21
|
-
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
22
|
-
}
|
|
23
|
-
const filterVal = typeof value === 'number' ? String(value) : value;
|
|
24
|
-
// Use strategy if provided (For future: configured filter strategies)
|
|
25
|
-
// But for now, we implement the default logic or use custom if we add it to config later
|
|
26
|
-
// Default Filter Logic
|
|
27
|
-
const cellTemplate = this.resolve(this.config.cellSelector, page);
|
|
28
|
-
// This logic assumes 1:1 row-to-cell mapping based on index.
|
|
29
|
-
// filter({ has: ... }) checks if the row *contains* the matching cell.
|
|
30
|
-
// But we need to be specific about WHICH cell.
|
|
31
|
-
// Locator filtering by `has: locator.nth(index)` works if `locator` search is relative to the row.
|
|
32
|
-
filtered = filtered.filter({
|
|
33
|
-
has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return filtered;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
exports.FilterEngine = FilterEngine;
|
package/dist/src/index.d.ts
DELETED