@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/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.useTable = exports.SortingStrategies = exports.TableStrategies = exports.PaginationStrategies = void 0;
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
- * A collection of pre-built pagination strategies.
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
- 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);
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 _suggestColumnName = (colName, availableColumns) => {
48
- // Simple fuzzy matching - find columns with similar names
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
- return `. Did you mean "${suggestions[0]}"?`;
69
+ suggestion = `. Did you mean "${suggestions[0]}"?`;
55
70
  }
56
- // Show similar column names (first 3)
57
- if (availableColumns.length > 0 && availableColumns.length <= 10) {
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
- return `. Available columns (first 5): ${availableColumns.slice(0, 5).map(c => `"${c}"`).join(', ')}, ...`;
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
- // 1. Fetch data efficiently
88
- const texts = yield headerLoc.allInnerTexts();
89
- const locators = yield headerLoc.all();
90
- // 2. Map Headers (Async)
91
- const entries = yield Promise.all(texts.map((t, i) => __awaiter(void 0, void 0, void 0, function* () {
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: locators[i]
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
- const _makeSmart = (rowLocator, map) => {
107
- const smart = rowLocator;
108
- smart.getCell = (colName) => {
109
- const idx = map.get(colName);
110
- if (idx === undefined) {
111
- const availableColumns = Array.from(map.keys());
112
- const suggestion = _suggestColumnName(colName, availableColumns);
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
- const matchedRows = _applyFilters(allRows, filters, map, options.exact || false);
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
- // Try to get sample row data to help user identify the issue
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
- // If we can't extract sample data, that's okay - continue without it
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'); // Inline styles are noise
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
- root: rootLocator, config, page: rootLocator.page(), resolve
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
- // Throw error if not initialized (sync methods require explicit init)
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 = _applyFilters(allRows, filters, _headerMap, (options === null || options === void 0 ? void 0 : options.exact) || false);
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
- getAllRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
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 = _applyFilters(rowLocators, options.filter, _headerMap, options.exact || false);
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'. Generate config for:\n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n${separator}\n`;
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. Please add a `sorting` strategy to your useTable config.');
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
- root: rootLocator,
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. Please add a `sorting` strategy to your useTable config.');
482
- }
483
- logDebug(`Getting sort state for column "${columnName}"`);
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
- // Determine pagination strategy
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. Either set pagination in options or in table config.');
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
- getAllRows: result.getAllRows,
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; // Will be set after first pagination attempt
527
- let seenKeys = null; // Track seen keys across iterations for deduplication
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
- // Call onFirst hook if applicable
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
- // Attempt pagination (before checking if we should continue)
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
- // Call onLast hook if applicable (after we know pagination failed or we're at max iterations)
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}, isLastDueToMax: ${isLastDueToMax})`);
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;