@rickcedwhat/playwright-smart-table 3.0.0 → 3.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 +84 -37
- package/dist/strategies/columns.d.ts +30 -0
- package/dist/strategies/columns.js +53 -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/typeContext.d.ts +1 -1
- package/dist/typeContext.js +92 -34
- package/dist/types.d.ts +104 -33
- package/dist/useTable.d.ts +6 -8
- package/dist/useTable.js +115 -120
- package/package.json +1 -1
package/dist/useTable.js
CHANGED
|
@@ -9,26 +9,23 @@ 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.ColumnStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.SortingStrategies = exports.TableStrategies = 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, "ColumnStrategies", { enumerable: true, get: function () { return columns_1.ColumnStrategies; } });
|
|
16
22
|
/**
|
|
17
|
-
*
|
|
23
|
+
* Main hook to interact with a table.
|
|
18
24
|
*/
|
|
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
25
|
const useTable = (rootLocator, configOptions = {}) => {
|
|
29
26
|
// Store whether pagination was explicitly provided in config
|
|
30
27
|
const hasPaginationInConfig = configOptions.pagination !== undefined;
|
|
31
|
-
const config = Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }), maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, onReset: () => __awaiter(void 0, void 0, void 0, function* () { console.warn("⚠️ .reset() called but no 'onReset' strategy defined in config."); }) }, configOptions);
|
|
28
|
+
const config = Object.assign({ rowSelector: "tbody tr", headerSelector: "th", cellSelector: "td", pagination: () => __awaiter(void 0, void 0, void 0, function* () { return false; }), maxPages: 1, headerTransformer: ({ text, index, locator }) => text, autoScroll: true, debug: false, fillStrategy: fill_1.FillStrategies.default, headerStrategy: headers_1.HeaderStrategies.visible, columnStrategy: columns_1.ColumnStrategies.default, onReset: () => __awaiter(void 0, void 0, void 0, function* () { console.warn("⚠️ .reset() called but no 'onReset' strategy defined in config."); }) }, configOptions);
|
|
32
29
|
const resolve = (item, parent) => {
|
|
33
30
|
if (typeof item === 'string')
|
|
34
31
|
return parent.locator(item);
|
|
@@ -84,17 +81,33 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
84
81
|
yield headerLoc.first().waitFor({ state: 'visible', timeout: headerTimeout });
|
|
85
82
|
}
|
|
86
83
|
catch (e) { /* Ignore hydration */ }
|
|
87
|
-
// 1. Fetch
|
|
88
|
-
const
|
|
89
|
-
|
|
84
|
+
// 1. Fetch headers using strategy
|
|
85
|
+
const strategy = config.headerStrategy || headers_1.HeaderStrategies.visible;
|
|
86
|
+
// We need to construct context - but wait, HeaderStrategies definition I made imports StrategyContext
|
|
87
|
+
// We need to import HeaderStrategies in useTable.ts first (it was imported as HeaderStrategies?).
|
|
88
|
+
// Wait, useTable.ts imports `FillStrategies`.
|
|
89
|
+
// I need to import `HeaderStrategies`.
|
|
90
|
+
const context = {
|
|
91
|
+
root: rootLocator,
|
|
92
|
+
config: config,
|
|
93
|
+
page: rootLocator.page(),
|
|
94
|
+
resolve: resolve
|
|
95
|
+
};
|
|
96
|
+
const rawHeaders = yield strategy(context);
|
|
90
97
|
// 2. Map Headers (Async)
|
|
91
|
-
|
|
98
|
+
// Note: We lose locator access here for transformer unless strategy provides it.
|
|
99
|
+
// For now assuming transformer handles missing locator or we don't pass it if generic strategy.
|
|
100
|
+
const entries = yield Promise.all(rawHeaders.map((t, i) => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
101
|
let text = t.trim() || `__col_${i}`;
|
|
93
102
|
if (config.headerTransformer) {
|
|
94
103
|
text = yield config.headerTransformer({
|
|
95
104
|
text,
|
|
96
105
|
index: i,
|
|
97
|
-
locator:
|
|
106
|
+
locator: rootLocator.locator(config.headerSelector).nth(i) // Best effort lazy locator?
|
|
107
|
+
// Danger: scanning strategy implies nth(i) might not map to visual i if scrolled.
|
|
108
|
+
// But map index maps to logical index.
|
|
109
|
+
// If scanning returns 100 headers, nth(99) might not exist in DOM.
|
|
110
|
+
// Passing fallback locator or stub.
|
|
98
111
|
});
|
|
99
112
|
}
|
|
100
113
|
return [text, i];
|
|
@@ -103,8 +116,12 @@ 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
|
-
|
|
119
|
+
// Placeholder for the final table object, to be captured by closure
|
|
120
|
+
let finalTable = null;
|
|
121
|
+
const _makeSmart = (rowLocator, map, rowIndex) => {
|
|
107
122
|
const smart = rowLocator;
|
|
123
|
+
smart.getRequestIndex = () => rowIndex;
|
|
124
|
+
smart.rowIndex = rowIndex;
|
|
108
125
|
smart.getCell = (colName) => {
|
|
109
126
|
const idx = map.get(colName);
|
|
110
127
|
if (idx === undefined) {
|
|
@@ -112,12 +129,10 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
112
129
|
const suggestion = _suggestColumnName(colName, availableColumns);
|
|
113
130
|
throw new Error(`Column "${colName}" not found${suggestion}`);
|
|
114
131
|
}
|
|
115
|
-
if (
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
132
|
+
if (config.cellResolver) {
|
|
133
|
+
return config.cellResolver({ row: rowLocator, columnName: colName, columnIndex: idx, rowIndex });
|
|
120
134
|
}
|
|
135
|
+
return resolve(config.cellSelector, rowLocator).nth(idx);
|
|
121
136
|
};
|
|
122
137
|
smart.toJSON = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
138
|
const result = {};
|
|
@@ -130,8 +145,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
130
145
|
}
|
|
131
146
|
return result;
|
|
132
147
|
});
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
// @ts-ignore - Intentionally overriding Locator's fill method to accept object
|
|
149
|
+
smart.fill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
150
|
logDebug(`Filling row with data: ${JSON.stringify(data)}`);
|
|
136
151
|
// Fill each column
|
|
137
152
|
for (const [colName, value] of Object.entries(data)) {
|
|
@@ -139,83 +154,34 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
139
154
|
if (colIdx === undefined) {
|
|
140
155
|
throw _createColumnError(colName, map, 'in fill data');
|
|
141
156
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
}
|
|
157
|
+
// Execute Column Strategy BEFORE filling
|
|
158
|
+
yield config.columnStrategy({
|
|
159
|
+
config: config,
|
|
160
|
+
root: rootLocator,
|
|
161
|
+
page: rootLocator.page(),
|
|
162
|
+
resolve,
|
|
163
|
+
column: colName,
|
|
164
|
+
index: colIdx,
|
|
165
|
+
rowLocator: rowLocator,
|
|
166
|
+
rowIndex: rowIndex
|
|
167
|
+
});
|
|
168
|
+
// Use configured strategy or default to internal DOM logic
|
|
169
|
+
const strategy = config.fillStrategy || fill_1.FillStrategies.default;
|
|
170
|
+
yield strategy({
|
|
171
|
+
row: smart,
|
|
172
|
+
columnName: colName,
|
|
173
|
+
value,
|
|
174
|
+
index: -1,
|
|
175
|
+
page: rootLocator.page(),
|
|
176
|
+
rootLocator,
|
|
177
|
+
table: finalTable,
|
|
178
|
+
fillOptions
|
|
179
|
+
});
|
|
216
180
|
}
|
|
217
181
|
logDebug('Fill operation completed');
|
|
218
182
|
});
|
|
183
|
+
// Alias for explicit usage avoiding Locator.fill conflict
|
|
184
|
+
smart.smartFill = smart.fill;
|
|
219
185
|
return smart;
|
|
220
186
|
};
|
|
221
187
|
const _applyFilters = (baseRows, filters, map, exact) => {
|
|
@@ -347,6 +313,21 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
347
313
|
_isInitialized = true;
|
|
348
314
|
return result;
|
|
349
315
|
}),
|
|
316
|
+
scrollToColumn: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
317
|
+
const map = yield _getMap();
|
|
318
|
+
const idx = map.get(columnName);
|
|
319
|
+
if (idx === undefined) {
|
|
320
|
+
throw _createColumnError(columnName, map);
|
|
321
|
+
}
|
|
322
|
+
yield config.columnStrategy({
|
|
323
|
+
config: config,
|
|
324
|
+
root: rootLocator,
|
|
325
|
+
page: rootLocator.page(),
|
|
326
|
+
resolve,
|
|
327
|
+
column: columnName,
|
|
328
|
+
index: idx
|
|
329
|
+
});
|
|
330
|
+
}),
|
|
350
331
|
getHeaders: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
351
332
|
if (!_isInitialized || !_headerMap) {
|
|
352
333
|
throw new Error('Table not initialized. Call await table.init() first.');
|
|
@@ -410,23 +391,29 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
410
391
|
}
|
|
411
392
|
return results;
|
|
412
393
|
}),
|
|
413
|
-
|
|
414
|
-
|
|
394
|
+
// @ts-ignore - implementing overload
|
|
395
|
+
getByRow: (filtersOrIndex, options = { exact: false }) => {
|
|
396
|
+
// Throw error if not initialized
|
|
415
397
|
if (!_isInitialized || !_headerMap) {
|
|
416
398
|
throw new Error('Table not initialized. Call await table.init() first.');
|
|
417
399
|
}
|
|
418
|
-
//
|
|
400
|
+
// Handle Index Overload (1-based index)
|
|
401
|
+
if (typeof filtersOrIndex === 'number') {
|
|
402
|
+
const rowIndex = filtersOrIndex - 1;
|
|
403
|
+
const rowLocator = resolve(config.rowSelector, rootLocator).nth(rowIndex);
|
|
404
|
+
return _makeSmart(rowLocator, _headerMap, rowIndex);
|
|
405
|
+
}
|
|
406
|
+
// Handle Filter Logic (Sync - lazy)
|
|
407
|
+
const filters = filtersOrIndex;
|
|
419
408
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
420
|
-
const matchedRows = _applyFilters(allRows, filters, _headerMap,
|
|
421
|
-
// Return first match (or sentinel) - lazy, doesn't check existence
|
|
409
|
+
const matchedRows = _applyFilters(allRows, filters, _headerMap, options.exact || false);
|
|
422
410
|
const rowLocator = matchedRows.first();
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
return smartRow;
|
|
411
|
+
// We pass rowIndex=0 as a fallback.
|
|
412
|
+
// NOTE: Filter-based sync lookup doesn't know the absolute index easily without scanning.
|
|
413
|
+
// If accurate rowIndex is needed for filtered rows, use searchForRow or getAllCurrentRows.
|
|
414
|
+
return _makeSmart(rowLocator, _headerMap, 0);
|
|
428
415
|
},
|
|
429
|
-
|
|
416
|
+
searchForRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
430
417
|
// Auto-init if needed (async methods can auto-init)
|
|
431
418
|
yield _ensureInitialized();
|
|
432
419
|
// Full pagination logic (existing _findRowLocator logic)
|
|
@@ -434,13 +421,9 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
434
421
|
if (!row) {
|
|
435
422
|
row = resolve(config.rowSelector, rootLocator).filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
436
423
|
}
|
|
437
|
-
|
|
438
|
-
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
439
|
-
return smartRow.toJSON();
|
|
440
|
-
}
|
|
441
|
-
return smartRow;
|
|
424
|
+
return _makeSmart(row, _headerMap, 0);
|
|
442
425
|
}),
|
|
443
|
-
|
|
426
|
+
getAllCurrentRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
444
427
|
// Auto-init if needed (async methods can auto-init)
|
|
445
428
|
yield _ensureInitialized();
|
|
446
429
|
let rowLocators = resolve(config.rowSelector, rootLocator);
|
|
@@ -448,16 +431,23 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
448
431
|
rowLocators = _applyFilters(rowLocators, options.filter, _headerMap, options.exact || false);
|
|
449
432
|
}
|
|
450
433
|
const rows = yield rowLocators.all();
|
|
451
|
-
const smartRows = rows.map(loc => _makeSmart(loc, _headerMap));
|
|
434
|
+
const smartRows = rows.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
452
435
|
if (options === null || options === void 0 ? void 0 : options.asJSON) {
|
|
453
436
|
return Promise.all(smartRows.map(r => r.toJSON()));
|
|
454
437
|
}
|
|
455
438
|
return smartRows;
|
|
456
439
|
}),
|
|
440
|
+
/**
|
|
441
|
+
* @deprecated Use getAllCurrentRows instead. This method will be removed in a future major version.
|
|
442
|
+
*/
|
|
443
|
+
getAllRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
444
|
+
console.warn("⚠️ [SmartTable] getAllRows is deprecated. Use getAllCurrentRows instead.");
|
|
445
|
+
return result.getAllCurrentRows(options);
|
|
446
|
+
}),
|
|
457
447
|
generateConfigPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
458
448
|
const html = yield _getCleanHtml(rootLocator);
|
|
459
449
|
const separator = "=".repeat(50);
|
|
460
|
-
const content = `\n${separator}\n🤖 COPY INTO GEMINI/ChatGPT 🤖\n${separator}\nI am using 'playwright-smart-table'.
|
|
450
|
+
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`;
|
|
461
451
|
yield _handlePrompt('Smart Table Config', content, options);
|
|
462
452
|
}),
|
|
463
453
|
generateStrategyPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -518,11 +508,12 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
518
508
|
getHeaders: result.getHeaders,
|
|
519
509
|
getHeaderCell: result.getHeaderCell,
|
|
520
510
|
getByRow: result.getByRow,
|
|
521
|
-
|
|
511
|
+
getAllCurrentRows: result.getAllCurrentRows,
|
|
522
512
|
getColumnValues: result.getColumnValues,
|
|
523
513
|
generateConfigPrompt: result.generateConfigPrompt,
|
|
524
514
|
generateStrategyPrompt: result.generateStrategyPrompt,
|
|
525
515
|
sorting: result.sorting,
|
|
516
|
+
scrollToColumn: result.scrollToColumn, // Add to restricted result as well
|
|
526
517
|
};
|
|
527
518
|
// Default functions
|
|
528
519
|
const getIsFirst = (_b = options === null || options === void 0 ? void 0 : options.getIsFirst) !== null && _b !== void 0 ? _b : (({ index }) => index === 0);
|
|
@@ -537,7 +528,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
537
528
|
while (index < effectiveMaxIterations) {
|
|
538
529
|
// Get current rows
|
|
539
530
|
const rowLocators = yield resolve(config.rowSelector, rootLocator).all();
|
|
540
|
-
let rows = rowLocators.map(loc => _makeSmart(loc, _headerMap));
|
|
531
|
+
let rows = rowLocators.map((loc, i) => _makeSmart(loc, _headerMap, i));
|
|
541
532
|
// Deduplicate if dedupeStrategy provided (across all iterations)
|
|
542
533
|
if ((options === null || options === void 0 ? void 0 : options.dedupeStrategy) && rows.length > 0) {
|
|
543
534
|
if (!seenKeys) {
|
|
@@ -600,6 +591,10 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
600
591
|
return allData;
|
|
601
592
|
}),
|
|
602
593
|
};
|
|
594
|
+
finalTable = result;
|
|
603
595
|
return result;
|
|
604
596
|
};
|
|
605
597
|
exports.useTable = useTable;
|
|
598
|
+
exports.PaginationStrategies = pagination_1.PaginationStrategies;
|
|
599
|
+
exports.TableStrategies = pagination_1.TableStrategies;
|
|
600
|
+
exports.SortingStrategies = sorting_1.SortingStrategies;
|
package/package.json
CHANGED