@rickcedwhat/playwright-smart-table 3.1.0 → 4.0.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 +173 -54
- package/dist/filterEngine.d.ts +11 -0
- package/dist/filterEngine.js +38 -0
- package/dist/smartRow.d.ts +7 -0
- package/dist/smartRow.js +155 -0
- package/dist/strategies/columns.d.ts +52 -0
- package/dist/strategies/columns.js +54 -0
- package/dist/strategies/fill.d.ts +7 -0
- package/dist/strategies/fill.js +88 -0
- package/dist/strategies/headers.d.ts +29 -0
- package/dist/strategies/headers.js +142 -0
- package/dist/strategies/index.d.ts +53 -0
- package/dist/strategies/index.js +22 -1
- package/dist/strategies/pagination.d.ts +1 -1
- package/dist/strategies/pagination.js +2 -2
- package/dist/strategies/resolution.d.ts +22 -0
- package/dist/strategies/resolution.js +30 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +152 -35
- package/dist/types.d.ts +153 -33
- package/dist/useTable.d.ts +10 -9
- package/dist/useTable.js +135 -283
- package/package.json +1 -1
package/dist/useTable.js
CHANGED
|
@@ -9,26 +9,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.Strategies = exports.ResolutionStrategies = exports.ColumnStrategies = exports.CellNavigationStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.SortingStrategies = exports.DeprecatedTableStrategies = 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 fill_1 = require("./strategies/fill");
|
|
17
|
+
Object.defineProperty(exports, "FillStrategies", { enumerable: true, get: function () { return fill_1.FillStrategies; } });
|
|
18
|
+
const headers_1 = require("./strategies/headers");
|
|
19
|
+
Object.defineProperty(exports, "HeaderStrategies", { enumerable: true, get: function () { return headers_1.HeaderStrategies; } });
|
|
20
|
+
const columns_1 = require("./strategies/columns");
|
|
21
|
+
Object.defineProperty(exports, "CellNavigationStrategies", { enumerable: true, get: function () { return columns_1.CellNavigationStrategies; } });
|
|
22
|
+
Object.defineProperty(exports, "ColumnStrategies", { enumerable: true, get: function () { return columns_1.ColumnStrategies; } });
|
|
23
|
+
const smartRow_1 = require("./smartRow");
|
|
24
|
+
const filterEngine_1 = require("./filterEngine");
|
|
25
|
+
const resolution_1 = require("./strategies/resolution");
|
|
26
|
+
Object.defineProperty(exports, "ResolutionStrategies", { enumerable: true, get: function () { return resolution_1.ResolutionStrategies; } });
|
|
27
|
+
const strategies_1 = require("./strategies");
|
|
28
|
+
Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
|
|
16
29
|
/**
|
|
17
|
-
*
|
|
30
|
+
* Main hook to interact with a table.
|
|
18
31
|
*/
|
|
19
|
-
exports.PaginationStrategies = pagination_1.PaginationStrategies;
|
|
20
|
-
/**
|
|
21
|
-
* @deprecated Use `PaginationStrategies` instead. This alias will be removed in a future major version.
|
|
22
|
-
*/
|
|
23
|
-
exports.TableStrategies = pagination_1.TableStrategies;
|
|
24
|
-
/**
|
|
25
|
-
* A collection of pre-built sorting strategies.
|
|
26
|
-
*/
|
|
27
|
-
exports.SortingStrategies = sorting_1.SortingStrategies;
|
|
28
32
|
const useTable = (rootLocator, configOptions = {}) => {
|
|
33
|
+
var _a;
|
|
29
34
|
// Store whether pagination was explicitly provided in config
|
|
30
|
-
const hasPaginationInConfig = configOptions.pagination !== undefined;
|
|
31
|
-
|
|
35
|
+
const hasPaginationInConfig = ((_a = configOptions.strategies) === null || _a === void 0 ? void 0 : _a.pagination) !== undefined;
|
|
36
|
+
// Default strategies
|
|
37
|
+
const defaultStrategies = {
|
|
38
|
+
fill: fill_1.FillStrategies.default,
|
|
39
|
+
header: headers_1.HeaderStrategies.visible,
|
|
40
|
+
cellNavigation: columns_1.CellNavigationStrategies.default,
|
|
41
|
+
pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
42
|
+
};
|
|
43
|
+
const config = Object.assign(Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, onReset: () => __awaiter(void 0, void 0, void 0, function* () { }) }, configOptions), { strategies: Object.assign(Object.assign({}, defaultStrategies), configOptions.strategies) });
|
|
32
44
|
const resolve = (item, parent) => {
|
|
33
45
|
if (typeof item === 'string')
|
|
34
46
|
return parent.locator(item);
|
|
@@ -40,31 +52,28 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
40
52
|
let _headerMap = null;
|
|
41
53
|
let _hasPaginated = false;
|
|
42
54
|
let _isInitialized = false;
|
|
55
|
+
// Helpers
|
|
43
56
|
const logDebug = (msg) => {
|
|
44
57
|
if (config.debug)
|
|
45
58
|
console.log(`🔎 [SmartTable Debug] ${msg}`);
|
|
46
59
|
};
|
|
47
|
-
const
|
|
48
|
-
|
|
60
|
+
const _createColumnError = (colName, map, context) => {
|
|
61
|
+
const availableColumns = Array.from(map.keys());
|
|
62
|
+
// Use Suggestion Logic from ResolutionStrategy (if we had a fuzzy one, for now manual suggest)
|
|
49
63
|
const lowerCol = colName.toLowerCase();
|
|
50
64
|
const suggestions = availableColumns.filter(col => col.toLowerCase().includes(lowerCol) ||
|
|
51
65
|
lowerCol.includes(col.toLowerCase()) ||
|
|
52
66
|
col.toLowerCase().replace(/\s+/g, '') === lowerCol.replace(/\s+/g, ''));
|
|
67
|
+
let suggestion = '.';
|
|
53
68
|
if (suggestions.length > 0 && suggestions[0] !== colName) {
|
|
54
|
-
|
|
69
|
+
suggestion = `. Did you mean "${suggestions[0]}"?`;
|
|
55
70
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return `. Available columns: ${availableColumns.map(c => `"${c}"`).join(', ')}`;
|
|
71
|
+
else if (availableColumns.length > 0 && availableColumns.length <= 10) {
|
|
72
|
+
suggestion = `. Available columns: ${availableColumns.map(c => `"${c}"`).join(', ')}`;
|
|
59
73
|
}
|
|
60
74
|
else if (availableColumns.length > 0) {
|
|
61
|
-
|
|
75
|
+
suggestion = `. Available columns (first 5): ${availableColumns.slice(0, 5).map(c => `"${c}"`).join(', ')}, ...`;
|
|
62
76
|
}
|
|
63
|
-
return '.';
|
|
64
|
-
};
|
|
65
|
-
const _createColumnError = (colName, map, context) => {
|
|
66
|
-
const availableColumns = Array.from(map.keys());
|
|
67
|
-
const suggestion = _suggestColumnName(colName, availableColumns);
|
|
68
77
|
const contextMsg = context ? ` (${context})` : '';
|
|
69
78
|
return new Error(`Column "${colName}" not found${contextMsg}${suggestion}`);
|
|
70
79
|
};
|
|
@@ -84,17 +93,21 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
84
93
|
yield headerLoc.first().waitFor({ state: 'visible', timeout: headerTimeout });
|
|
85
94
|
}
|
|
86
95
|
catch (e) { /* Ignore hydration */ }
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
const strategy = config.strategies.header || headers_1.HeaderStrategies.visible;
|
|
97
|
+
const context = {
|
|
98
|
+
root: rootLocator,
|
|
99
|
+
config: config,
|
|
100
|
+
page: rootLocator.page(),
|
|
101
|
+
resolve: resolve
|
|
102
|
+
};
|
|
103
|
+
const rawHeaders = yield strategy(context);
|
|
104
|
+
const entries = yield Promise.all(rawHeaders.map((t, i) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
105
|
let text = t.trim() || `__col_${i}`;
|
|
93
106
|
if (config.headerTransformer) {
|
|
94
107
|
text = yield config.headerTransformer({
|
|
95
108
|
text,
|
|
96
109
|
index: i,
|
|
97
|
-
locator:
|
|
110
|
+
locator: rootLocator.locator(config.headerSelector).nth(i)
|
|
98
111
|
});
|
|
99
112
|
}
|
|
100
113
|
return [text, i];
|
|
@@ -103,136 +116,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
103
116
|
logDebug(`Mapped ${entries.length} columns: ${JSON.stringify(entries.map(e => e[0]))}`);
|
|
104
117
|
return _headerMap;
|
|
105
118
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
throw new Error(`Column "${colName}" not found${suggestion}`);
|
|
114
|
-
}
|
|
115
|
-
if (typeof config.cellSelector === 'string') {
|
|
116
|
-
return rowLocator.locator(config.cellSelector).nth(idx);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
smart.toJSON = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
-
const result = {};
|
|
124
|
-
const cells = typeof config.cellSelector === 'string'
|
|
125
|
-
? rowLocator.locator(config.cellSelector)
|
|
126
|
-
: resolve(config.cellSelector, rowLocator);
|
|
127
|
-
const texts = yield cells.allInnerTexts();
|
|
128
|
-
for (const [col, idx] of map.entries()) {
|
|
129
|
-
result[col] = (texts[idx] || '').trim();
|
|
130
|
-
}
|
|
131
|
-
return result;
|
|
132
|
-
});
|
|
133
|
-
smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
|
|
134
|
-
var _a;
|
|
135
|
-
logDebug(`Filling row with data: ${JSON.stringify(data)}`);
|
|
136
|
-
// Fill each column
|
|
137
|
-
for (const [colName, value] of Object.entries(data)) {
|
|
138
|
-
const colIdx = map.get(colName);
|
|
139
|
-
if (colIdx === undefined) {
|
|
140
|
-
throw _createColumnError(colName, map, 'in fill data');
|
|
141
|
-
}
|
|
142
|
-
const cell = smart.getCell(colName);
|
|
143
|
-
// Use custom input mapper for this column if provided, otherwise auto-detect
|
|
144
|
-
let inputLocator;
|
|
145
|
-
if ((_a = fillOptions === null || fillOptions === void 0 ? void 0 : fillOptions.inputMappers) === null || _a === void 0 ? void 0 : _a[colName]) {
|
|
146
|
-
inputLocator = fillOptions.inputMappers[colName](cell);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// Auto-detect input type
|
|
150
|
-
// Try different input types in order of commonality
|
|
151
|
-
// Check for text input
|
|
152
|
-
const textInput = cell.locator('input[type="text"], input:not([type]), textarea').first();
|
|
153
|
-
const textInputCount = yield textInput.count().catch(() => 0);
|
|
154
|
-
// Check for select
|
|
155
|
-
const select = cell.locator('select').first();
|
|
156
|
-
const selectCount = yield select.count().catch(() => 0);
|
|
157
|
-
// Check for checkbox/radio
|
|
158
|
-
const checkbox = cell.locator('input[type="checkbox"], input[type="radio"], [role="checkbox"]').first();
|
|
159
|
-
const checkboxCount = yield checkbox.count().catch(() => 0);
|
|
160
|
-
// Check for contenteditable or div-based inputs
|
|
161
|
-
const contentEditable = cell.locator('[contenteditable="true"]').first();
|
|
162
|
-
const contentEditableCount = yield contentEditable.count().catch(() => 0);
|
|
163
|
-
// Determine which input to use (prioritize by commonality)
|
|
164
|
-
if (textInputCount > 0 && selectCount === 0 && checkboxCount === 0) {
|
|
165
|
-
inputLocator = textInput;
|
|
166
|
-
}
|
|
167
|
-
else if (selectCount > 0) {
|
|
168
|
-
inputLocator = select;
|
|
169
|
-
}
|
|
170
|
-
else if (checkboxCount > 0) {
|
|
171
|
-
inputLocator = checkbox;
|
|
172
|
-
}
|
|
173
|
-
else if (contentEditableCount > 0) {
|
|
174
|
-
inputLocator = contentEditable;
|
|
175
|
-
}
|
|
176
|
-
else if (textInputCount > 0) {
|
|
177
|
-
// Fallback to text input even if others exist
|
|
178
|
-
inputLocator = textInput;
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
// No input found - try to click the cell itself (might trigger an editor)
|
|
182
|
-
inputLocator = cell;
|
|
183
|
-
}
|
|
184
|
-
// Warn if multiple inputs found (ambiguous)
|
|
185
|
-
const totalInputs = textInputCount + selectCount + checkboxCount + contentEditableCount;
|
|
186
|
-
if (totalInputs > 1 && config.debug) {
|
|
187
|
-
logDebug(`⚠️ Multiple inputs found in cell "${colName}" (${totalInputs} total). Using first match. Consider using inputMapper option for explicit control.`);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// Fill based on value type and input type
|
|
191
|
-
const inputTag = yield inputLocator.evaluate((el) => el.tagName.toLowerCase()).catch(() => 'unknown');
|
|
192
|
-
const inputType = yield inputLocator.getAttribute('type').catch(() => null);
|
|
193
|
-
const isContentEditable = yield inputLocator.getAttribute('contenteditable').catch(() => null);
|
|
194
|
-
logDebug(`Filling "${colName}" with value "${value}" (input: ${inputTag}, type: ${inputType})`);
|
|
195
|
-
if (inputType === 'checkbox' || inputType === 'radio') {
|
|
196
|
-
// Boolean value for checkbox/radio
|
|
197
|
-
const shouldBeChecked = Boolean(value);
|
|
198
|
-
const isChecked = yield inputLocator.isChecked().catch(() => false);
|
|
199
|
-
if (isChecked !== shouldBeChecked) {
|
|
200
|
-
yield inputLocator.click();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
else if (inputTag === 'select') {
|
|
204
|
-
// Select dropdown
|
|
205
|
-
yield inputLocator.selectOption(String(value));
|
|
206
|
-
}
|
|
207
|
-
else if (isContentEditable === 'true') {
|
|
208
|
-
// Contenteditable div
|
|
209
|
-
yield inputLocator.click();
|
|
210
|
-
yield inputLocator.fill(String(value));
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
// Text input, textarea, or generic
|
|
214
|
-
yield inputLocator.fill(String(value));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
logDebug('Fill operation completed');
|
|
218
|
-
});
|
|
219
|
-
return smart;
|
|
220
|
-
};
|
|
221
|
-
const _applyFilters = (baseRows, filters, map, exact) => {
|
|
222
|
-
let filtered = baseRows;
|
|
223
|
-
const page = rootLocator.page();
|
|
224
|
-
for (const [colName, value] of Object.entries(filters)) {
|
|
225
|
-
const colIndex = map.get(colName);
|
|
226
|
-
if (colIndex === undefined) {
|
|
227
|
-
throw _createColumnError(colName, map, 'in filter');
|
|
228
|
-
}
|
|
229
|
-
const filterVal = typeof value === 'number' ? String(value) : value;
|
|
230
|
-
const cellTemplate = resolve(config.cellSelector, page);
|
|
231
|
-
filtered = filtered.filter({
|
|
232
|
-
has: cellTemplate.nth(colIndex).getByText(filterVal, { exact }),
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
return filtered;
|
|
119
|
+
// Placeholder for the final table object
|
|
120
|
+
let finalTable = null;
|
|
121
|
+
const filterEngine = new filterEngine_1.FilterEngine(config, resolve);
|
|
122
|
+
// Helper factory
|
|
123
|
+
const _makeSmart = (rowLocator, map, rowIndex) => {
|
|
124
|
+
// Use the wrapped SmartRow logic
|
|
125
|
+
return (0, smartRow_1.createSmartRow)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable);
|
|
236
126
|
};
|
|
237
127
|
const _findRowLocator = (filters_1, ...args_1) => __awaiter(void 0, [filters_1, ...args_1], void 0, function* (filters, options = {}) {
|
|
238
128
|
var _a;
|
|
@@ -242,11 +132,12 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
242
132
|
logDebug(`Looking for row: ${JSON.stringify(filters)} (MaxPages: ${effectiveMaxPages})`);
|
|
243
133
|
while (true) {
|
|
244
134
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
245
|
-
|
|
135
|
+
// Use FilterEngine
|
|
136
|
+
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page());
|
|
246
137
|
const count = yield matchedRows.count();
|
|
247
138
|
logDebug(`Page ${currentPage}: Found ${count} matches.`);
|
|
248
139
|
if (count > 1) {
|
|
249
|
-
//
|
|
140
|
+
// Sample data logic (simplified for refactor, kept inline or moved to util if needed)
|
|
250
141
|
const sampleData = [];
|
|
251
142
|
try {
|
|
252
143
|
const firstFewRows = yield matchedRows.all();
|
|
@@ -256,12 +147,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
256
147
|
sampleData.push(JSON.stringify(rowData));
|
|
257
148
|
}
|
|
258
149
|
}
|
|
259
|
-
catch (e) {
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
const sampleMsg = sampleData.length > 0
|
|
263
|
-
? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}`
|
|
264
|
-
: '';
|
|
150
|
+
catch (e) { }
|
|
151
|
+
const sampleMsg = sampleData.length > 0 ? `\nSample matching rows:\n${sampleData.map((d, i) => ` ${i + 1}. ${d}`).join('\n')}` : '';
|
|
265
152
|
throw new Error(`Strict Mode Violation: Found ${count} rows matching ${JSON.stringify(filters)} on page ${currentPage}. ` +
|
|
266
153
|
`Expected exactly one match. Try adding more filters to make your query unique.${sampleMsg}`);
|
|
267
154
|
}
|
|
@@ -275,7 +162,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
275
162
|
page: rootLocator.page(),
|
|
276
163
|
resolve: resolve
|
|
277
164
|
};
|
|
278
|
-
const didLoadMore = yield config.pagination(context);
|
|
165
|
+
const didLoadMore = yield config.strategies.pagination(context);
|
|
279
166
|
if (didLoadMore) {
|
|
280
167
|
_hasPaginated = true;
|
|
281
168
|
currentPage++;
|
|
@@ -292,33 +179,27 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
292
179
|
}
|
|
293
180
|
});
|
|
294
181
|
const _handlePrompt = (promptName_1, content_1, ...args_1) => __awaiter(void 0, [promptName_1, content_1, ...args_1], void 0, function* (promptName, content, options = {}) {
|
|
182
|
+
// ... same logic ...
|
|
295
183
|
const { output = 'console', includeTypes = true } = options;
|
|
296
184
|
let finalPrompt = content;
|
|
297
|
-
if (includeTypes)
|
|
185
|
+
if (includeTypes)
|
|
298
186
|
finalPrompt += `\n\n👇 Useful TypeScript Definitions 👇\n\`\`\`typescript\n${typeContext_1.TYPE_CONTEXT}\n\`\`\`\n`;
|
|
299
|
-
}
|
|
300
187
|
if (output === 'error') {
|
|
301
188
|
console.log(`⚠️ Throwing error to display [${promptName}] cleanly...`);
|
|
302
189
|
throw new Error(finalPrompt);
|
|
303
190
|
}
|
|
304
191
|
console.log(finalPrompt);
|
|
305
192
|
});
|
|
306
|
-
// Helper to extract clean HTML for prompts
|
|
307
193
|
const _getCleanHtml = (loc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
308
194
|
return loc.evaluate((el) => {
|
|
309
195
|
const clone = el.cloneNode(true);
|
|
310
|
-
// 1. Remove Heavy/Useless Elements
|
|
311
196
|
const removeSelectors = 'script, style, svg, path, circle, rect, noscript, [hidden]';
|
|
312
197
|
clone.querySelectorAll(removeSelectors).forEach(n => n.remove());
|
|
313
|
-
// 2. Clean Attributes
|
|
314
198
|
const walker = document.createTreeWalker(clone, NodeFilter.SHOW_ELEMENT);
|
|
315
199
|
let currentNode = walker.currentNode;
|
|
316
200
|
while (currentNode) {
|
|
317
|
-
currentNode.removeAttribute('style');
|
|
201
|
+
currentNode.removeAttribute('style');
|
|
318
202
|
currentNode.removeAttribute('data-reactid');
|
|
319
|
-
// 3. Condense Tailwind Classes (Heuristic)
|
|
320
|
-
// If class string is very long (>50 chars), keep the first few tokens and truncate.
|
|
321
|
-
// This preserves "MuiRow" but cuts "text-sm p-4 hover:bg-gray-50 ..."
|
|
322
203
|
const cls = currentNode.getAttribute('class');
|
|
323
204
|
if (cls && cls.length > 80) {
|
|
324
205
|
const tokens = cls.split(' ');
|
|
@@ -331,7 +212,6 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
331
212
|
return clone.outerHTML;
|
|
332
213
|
});
|
|
333
214
|
});
|
|
334
|
-
// Helper to ensure initialization for async methods
|
|
335
215
|
const _ensureInitialized = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
336
216
|
if (!_isInitialized) {
|
|
337
217
|
yield _getMap();
|
|
@@ -340,23 +220,34 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
340
220
|
});
|
|
341
221
|
const result = {
|
|
342
222
|
init: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
343
|
-
if (_isInitialized && _headerMap)
|
|
223
|
+
if (_isInitialized && _headerMap)
|
|
344
224
|
return result;
|
|
345
|
-
}
|
|
346
225
|
yield _getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
347
226
|
_isInitialized = true;
|
|
348
227
|
return result;
|
|
349
228
|
}),
|
|
229
|
+
scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
230
|
+
const map = yield _getMap();
|
|
231
|
+
const idx = map.get(columnName);
|
|
232
|
+
if (idx === undefined)
|
|
233
|
+
throw _createColumnError(columnName, map);
|
|
234
|
+
yield config.strategies.cellNavigation({
|
|
235
|
+
config: config,
|
|
236
|
+
root: rootLocator,
|
|
237
|
+
page: rootLocator.page(),
|
|
238
|
+
resolve,
|
|
239
|
+
column: columnName,
|
|
240
|
+
index: idx
|
|
241
|
+
});
|
|
242
|
+
}),
|
|
350
243
|
getHeaders: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
351
|
-
if (!_isInitialized || !_headerMap)
|
|
244
|
+
if (!_isInitialized || !_headerMap)
|
|
352
245
|
throw new Error('Table not initialized. Call await table.init() first.');
|
|
353
|
-
}
|
|
354
246
|
return Array.from(_headerMap.keys());
|
|
355
247
|
}),
|
|
356
248
|
getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
357
|
-
if (!_isInitialized || !_headerMap)
|
|
249
|
+
if (!_isInitialized || !_headerMap)
|
|
358
250
|
throw new Error('Table not initialized. Call await table.init() first.');
|
|
359
|
-
}
|
|
360
251
|
const idx = _headerMap.get(columnName);
|
|
361
252
|
if (idx === undefined)
|
|
362
253
|
throw _createColumnError(columnName, _headerMap, 'header cell');
|
|
@@ -364,21 +255,21 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
364
255
|
}),
|
|
365
256
|
reset: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
366
257
|
logDebug("Resetting table...");
|
|
367
|
-
const context = {
|
|
368
|
-
root: rootLocator,
|
|
369
|
-
config: config,
|
|
370
|
-
page: rootLocator.page(),
|
|
371
|
-
resolve: resolve
|
|
372
|
-
};
|
|
258
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
373
259
|
yield config.onReset(context);
|
|
374
260
|
_hasPaginated = false;
|
|
375
261
|
_headerMap = null;
|
|
376
262
|
_isInitialized = false;
|
|
377
263
|
logDebug("Table reset complete.");
|
|
378
264
|
}),
|
|
265
|
+
revalidate: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
266
|
+
logDebug("Revalidating table structure...");
|
|
267
|
+
_headerMap = null; // Clear the map to force re-scanning
|
|
268
|
+
yield _getMap(); // Re-scan headers
|
|
269
|
+
logDebug("Table revalidated.");
|
|
270
|
+
}),
|
|
379
271
|
getColumnValues: (column, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
380
272
|
var _a, _b;
|
|
381
|
-
// Auto-init if needed (async methods can auto-init)
|
|
382
273
|
yield _ensureInitialized();
|
|
383
274
|
const colIdx = _headerMap.get(column);
|
|
384
275
|
if (colIdx === undefined)
|
|
@@ -397,10 +288,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
397
288
|
results.push(yield mapper(cell));
|
|
398
289
|
}
|
|
399
290
|
if (currentPage < effectiveMaxPages) {
|
|
400
|
-
const context = {
|
|
401
|
-
|
|
402
|
-
};
|
|
403
|
-
if (yield config.pagination(context)) {
|
|
291
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
292
|
+
if (yield config.strategies.pagination(context)) {
|
|
404
293
|
_hasPaginated = true;
|
|
405
294
|
currentPage++;
|
|
406
295
|
continue;
|
|
@@ -410,46 +299,50 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
410
299
|
}
|
|
411
300
|
return results;
|
|
412
301
|
}),
|
|
413
|
-
getByRow: (filters, options) => {
|
|
414
|
-
|
|
415
|
-
if (!_isInitialized || !_headerMap) {
|
|
302
|
+
getByRow: (filters, options = { exact: false }) => {
|
|
303
|
+
if (!_isInitialized || !_headerMap)
|
|
416
304
|
throw new Error('Table not initialized. Call await table.init() first.');
|
|
417
|
-
}
|
|
418
|
-
// Build locator chain (sync) - current page only
|
|
419
305
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
420
|
-
const matchedRows =
|
|
421
|
-
// Return first match (or sentinel) - lazy, doesn't check existence
|
|
306
|
+
const matchedRows = filterEngine.applyFilters(allRows, filters, _headerMap, options.exact || false, rootLocator.page());
|
|
422
307
|
const rowLocator = matchedRows.first();
|
|
423
|
-
return _makeSmart(rowLocator, _headerMap);
|
|
308
|
+
return _makeSmart(rowLocator, _headerMap, 0); // fallback index 0
|
|
309
|
+
},
|
|
310
|
+
getByRowIndex: (index, options = {}) => {
|
|
311
|
+
if (!_isInitialized || !_headerMap)
|
|
312
|
+
throw new Error('Table not initialized. Call await table.init() first.');
|
|
313
|
+
const rowIndex = index - 1; // Convert 1-based to 0-based
|
|
314
|
+
const rowLocator = resolve(config.rowSelector, rootLocator).nth(rowIndex);
|
|
315
|
+
return _makeSmart(rowLocator, _headerMap, rowIndex);
|
|
424
316
|
},
|
|
425
317
|
searchForRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
426
|
-
// Auto-init if needed (async methods can auto-init)
|
|
427
318
|
yield _ensureInitialized();
|
|
428
|
-
// Full pagination logic (existing _findRowLocator logic)
|
|
429
319
|
let row = yield _findRowLocator(filters, options);
|
|
430
320
|
if (!row) {
|
|
431
321
|
row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
432
322
|
}
|
|
433
|
-
return _makeSmart(row, _headerMap);
|
|
323
|
+
return _makeSmart(row, _headerMap, 0);
|
|
434
324
|
}),
|
|
435
|
-
|
|
436
|
-
// Auto-init if needed (async methods can auto-init)
|
|
325
|
+
getAllCurrentRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
437
326
|
yield _ensureInitialized();
|
|
438
327
|
let rowLocators = resolve(config.rowSelector, rootLocator);
|
|
439
328
|
if (options === null || options === void 0 ? void 0 : options.filter) {
|
|
440
|
-
rowLocators =
|
|
329
|
+
rowLocators = filterEngine.applyFilters(rowLocators, options.filter, _headerMap, options.exact || false, rootLocator.page());
|
|
441
330
|
}
|
|
442
331
|
const rows = yield rowLocators.all();
|
|
443
|
-
const smartRows = rows.map(loc => _makeSmart(loc, _headerMap));
|
|
332
|
+
const smartRows = rows.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
444
333
|
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
445
334
|
return Promise.all(smartRows.map(r => r.toJSON()));
|
|
446
335
|
}
|
|
447
336
|
return smartRows;
|
|
448
337
|
}),
|
|
338
|
+
getAllRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
339
|
+
console.warn("⚠️ [SmartTable] getAllRows is deprecated. Use getAllCurrentRows instead.");
|
|
340
|
+
return result.getAllCurrentRows(options);
|
|
341
|
+
}),
|
|
449
342
|
generateConfigPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
450
343
|
const html = yield _getCleanHtml(rootLocator);
|
|
451
344
|
const separator = "=".repeat(50);
|
|
452
|
-
const content = `\n${separator}\n🤖 COPY INTO GEMINI/ChatGPT 🤖\n${separator}\nI am using 'playwright-smart-table'.
|
|
345
|
+
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`;
|
|
453
346
|
yield _handlePrompt('Smart Table Config', content, options);
|
|
454
347
|
}),
|
|
455
348
|
generateStrategyPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -460,81 +353,58 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
460
353
|
}),
|
|
461
354
|
sorting: {
|
|
462
355
|
apply: (columnName, direction) => __awaiter(void 0, void 0, void 0, function* () {
|
|
463
|
-
// Auto-init if needed (async methods can auto-init)
|
|
464
356
|
yield _ensureInitialized();
|
|
465
|
-
if (!config.sorting)
|
|
466
|
-
throw new Error('No sorting strategy has been configured.
|
|
467
|
-
}
|
|
357
|
+
if (!config.strategies.sorting)
|
|
358
|
+
throw new Error('No sorting strategy has been configured.');
|
|
468
359
|
logDebug(`Applying sort for column "${columnName}" (${direction})`);
|
|
469
|
-
const context = {
|
|
470
|
-
|
|
471
|
-
config: config,
|
|
472
|
-
page: rootLocator.page(),
|
|
473
|
-
resolve: resolve
|
|
474
|
-
};
|
|
475
|
-
yield config.sorting.doSort({ columnName, direction, context });
|
|
360
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
361
|
+
yield config.strategies.sorting.doSort({ columnName, direction, context });
|
|
476
362
|
}),
|
|
477
363
|
getState: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
478
|
-
// Auto-init if needed (async methods can auto-init)
|
|
479
364
|
yield _ensureInitialized();
|
|
480
|
-
if (!config.sorting)
|
|
481
|
-
throw new Error('No sorting strategy has been configured.
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const context = {
|
|
485
|
-
root: rootLocator,
|
|
486
|
-
config: config,
|
|
487
|
-
page: rootLocator.page(),
|
|
488
|
-
resolve: resolve
|
|
489
|
-
};
|
|
490
|
-
return config.sorting.getSortState({ columnName, context });
|
|
365
|
+
if (!config.strategies.sorting)
|
|
366
|
+
throw new Error('No sorting strategy has been configured.');
|
|
367
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
368
|
+
return config.strategies.sorting.getSortState({ columnName, context });
|
|
491
369
|
})
|
|
492
370
|
},
|
|
493
371
|
iterateThroughTable: (callback, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
494
372
|
var _a, _b, _c, _d;
|
|
495
|
-
// Auto-init if needed (async methods can auto-init)
|
|
496
373
|
yield _ensureInitialized();
|
|
497
|
-
|
|
498
|
-
const paginationStrategy = (_a = options === null || options === void 0 ? void 0 : options.pagination) !== null && _a !== void 0 ? _a : config.pagination;
|
|
499
|
-
// Check if pagination was explicitly provided in options or config
|
|
374
|
+
const paginationStrategy = (_a = options === null || options === void 0 ? void 0 : options.pagination) !== null && _a !== void 0 ? _a : config.strategies.pagination;
|
|
500
375
|
const hasPaginationInOptions = (options === null || options === void 0 ? void 0 : options.pagination) !== undefined;
|
|
501
|
-
if (!hasPaginationInOptions && !hasPaginationInConfig)
|
|
502
|
-
throw new Error('No pagination strategy provided.
|
|
503
|
-
}
|
|
504
|
-
// Reset to initial page before starting
|
|
376
|
+
if (!hasPaginationInOptions && !hasPaginationInConfig)
|
|
377
|
+
throw new Error('No pagination strategy provided.');
|
|
505
378
|
yield result.reset();
|
|
506
379
|
yield result.init();
|
|
507
|
-
// Create restricted table instance (excludes problematic methods)
|
|
508
380
|
const restrictedTable = {
|
|
509
381
|
init: result.init,
|
|
510
382
|
getHeaders: result.getHeaders,
|
|
511
383
|
getHeaderCell: result.getHeaderCell,
|
|
512
384
|
getByRow: result.getByRow,
|
|
513
|
-
|
|
385
|
+
getByRowIndex: result.getByRowIndex,
|
|
386
|
+
getAllCurrentRows: result.getAllCurrentRows,
|
|
514
387
|
getColumnValues: result.getColumnValues,
|
|
515
388
|
generateConfigPrompt: result.generateConfigPrompt,
|
|
516
389
|
generateStrategyPrompt: result.generateStrategyPrompt,
|
|
517
390
|
sorting: result.sorting,
|
|
391
|
+
scrollToColumn: result.scrollToColumn,
|
|
392
|
+
revalidate: result.revalidate,
|
|
518
393
|
};
|
|
519
|
-
// Default functions
|
|
520
394
|
const getIsFirst = (_b = options === null || options === void 0 ? void 0 : options.getIsFirst) !== null && _b !== void 0 ? _b : (({ index }) => index === 0);
|
|
521
395
|
const getIsLast = (_c = options === null || options === void 0 ? void 0 : options.getIsLast) !== null && _c !== void 0 ? _c : (() => false);
|
|
522
|
-
// Create allData array (persists across iterations)
|
|
523
396
|
const allData = [];
|
|
524
397
|
const effectiveMaxIterations = (_d = options === null || options === void 0 ? void 0 : options.maxIterations) !== null && _d !== void 0 ? _d : config.maxPages;
|
|
525
398
|
let index = 0;
|
|
526
|
-
let paginationResult = true;
|
|
527
|
-
let seenKeys = null;
|
|
399
|
+
let paginationResult = true;
|
|
400
|
+
let seenKeys = null;
|
|
528
401
|
logDebug(`Starting iterateThroughTable (maxIterations: ${effectiveMaxIterations})`);
|
|
529
402
|
while (index < effectiveMaxIterations) {
|
|
530
|
-
// Get current rows
|
|
531
403
|
const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
|
|
532
|
-
let rows = rowLocators.map(loc => _makeSmart(loc, _headerMap));
|
|
533
|
-
// Deduplicate if dedupeStrategy provided (across all iterations)
|
|
404
|
+
let rows = rowLocators.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
534
405
|
if ((options === null || options === void 0 ? void 0 : options.dedupeStrategy) && rows.length > 0) {
|
|
535
|
-
if (!seenKeys)
|
|
406
|
+
if (!seenKeys)
|
|
536
407
|
seenKeys = new Set();
|
|
537
|
-
}
|
|
538
408
|
const deduplicated = [];
|
|
539
409
|
for (const row of rows) {
|
|
540
410
|
const key = yield options.dedupeStrategy(row);
|
|
@@ -546,43 +416,20 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
546
416
|
rows = deduplicated;
|
|
547
417
|
logDebug(`Deduplicated ${rowLocators.length} rows to ${rows.length} unique rows (total seen: ${seenKeys.size})`);
|
|
548
418
|
}
|
|
549
|
-
// Determine flags (isLast will be checked after pagination attempt)
|
|
550
419
|
const isFirst = getIsFirst({ index });
|
|
551
420
|
let isLast = getIsLast({ index, paginationResult });
|
|
552
|
-
// Check if this is the last iteration due to maxIterations (before attempting pagination)
|
|
553
421
|
const isLastDueToMax = index === effectiveMaxIterations - 1;
|
|
554
|
-
|
|
555
|
-
if (isFirst && (options === null || options === void 0 ? void 0 : options.onFirst)) {
|
|
422
|
+
if (isFirst && (options === null || options === void 0 ? void 0 : options.onFirst))
|
|
556
423
|
yield options.onFirst({ index, rows, allData });
|
|
557
|
-
}
|
|
558
|
-
// Call main callback
|
|
559
|
-
const returnValue = yield callback({
|
|
560
|
-
index,
|
|
561
|
-
isFirst,
|
|
562
|
-
isLast,
|
|
563
|
-
rows,
|
|
564
|
-
allData,
|
|
565
|
-
table: restrictedTable,
|
|
566
|
-
});
|
|
567
|
-
// Append return value to allData
|
|
424
|
+
const returnValue = yield callback({ index, isFirst, isLast, rows, allData, table: restrictedTable });
|
|
568
425
|
allData.push(returnValue);
|
|
569
|
-
|
|
570
|
-
const context = {
|
|
571
|
-
root: rootLocator,
|
|
572
|
-
config: config,
|
|
573
|
-
page: rootLocator.page(),
|
|
574
|
-
resolve: resolve
|
|
575
|
-
};
|
|
426
|
+
const context = { root: rootLocator, config, page: rootLocator.page(), resolve };
|
|
576
427
|
paginationResult = yield paginationStrategy(context);
|
|
577
|
-
// Now check isLast with updated paginationResult
|
|
578
428
|
isLast = getIsLast({ index, paginationResult }) || isLastDueToMax;
|
|
579
|
-
|
|
580
|
-
if (isLast && (options === null || options === void 0 ? void 0 : options.onLast)) {
|
|
429
|
+
if (isLast && (options === null || options === void 0 ? void 0 : options.onLast))
|
|
581
430
|
yield options.onLast({ index, rows, allData });
|
|
582
|
-
}
|
|
583
|
-
// Check if we should continue
|
|
584
431
|
if (isLast || !paginationResult) {
|
|
585
|
-
logDebug(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult}
|
|
432
|
+
logDebug(`Reached last iteration (index: ${index}, paginationResult: ${paginationResult})`);
|
|
586
433
|
break;
|
|
587
434
|
}
|
|
588
435
|
index++;
|
|
@@ -592,6 +439,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
592
439
|
return allData;
|
|
593
440
|
}),
|
|
594
441
|
};
|
|
442
|
+
finalTable = result;
|
|
595
443
|
return result;
|
|
596
444
|
};
|
|
597
445
|
exports.useTable = useTable;
|
|
446
|
+
exports.PaginationStrategies = pagination_1.PaginationStrategies;
|
|
447
|
+
/** @deprecated Use Strategies.Pagination instead */
|
|
448
|
+
exports.DeprecatedTableStrategies = pagination_1.DeprecatedPaginationStrategies;
|
|
449
|
+
exports.SortingStrategies = sorting_1.SortingStrategies;
|