@rickcedwhat/playwright-smart-table 4.0.0 → 5.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/types.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import type { Locator, Page } from '@playwright/test';
2
+ /**
3
+ * Flexible selector type - can be a CSS string, function returning a Locator, or Locator itself.
4
+ * @example
5
+ * // String selector
6
+ * rowSelector: 'tbody tr'
7
+ *
8
+ * // Function selector
9
+ * rowSelector: (root) => root.locator('[role="row"]')
10
+ */
2
11
  export type Selector = string | ((root: Locator | Page) => Locator);
3
12
  /**
4
13
  * Function to get a cell locator given row, column info.
@@ -21,24 +30,67 @@ export type GetActiveCellFn = (args: TableContext) => Promise<{
21
30
  columnName?: string;
22
31
  locator: Locator;
23
32
  } | null>;
33
+ /**
34
+ * SmartRow - A Playwright Locator with table-aware methods.
35
+ *
36
+ * Extends all standard Locator methods (click, isVisible, etc.) with table-specific functionality.
37
+ *
38
+ * @example
39
+ * const row = table.getRow({ Name: 'John Doe' });
40
+ * await row.click(); // Standard Locator method
41
+ * const email = row.getCell('Email'); // Table-aware method
42
+ * const data = await row.toJSON(); // Extract all row data
43
+ * await row.smartFill({ Name: 'Jane', Status: 'Active' }); // Fill form fields
44
+ */
24
45
  export type SmartRow<T = any> = Locator & {
25
- getRequestIndex(): number | undefined;
46
+ /** Optional row index (0-based) if known */
26
47
  rowIndex?: number;
48
+ /**
49
+ * Get a cell locator by column name.
50
+ * @param column - Column name (case-sensitive)
51
+ * @returns Locator for the cell
52
+ * @example
53
+ * const emailCell = row.getCell('Email');
54
+ * await expect(emailCell).toHaveText('john@example.com');
55
+ */
27
56
  getCell(column: string): Locator;
57
+ /**
58
+ * Extract all cell data as a key-value object.
59
+ * @param options - Optional configuration
60
+ * @param options.columns - Specific columns to extract (extracts all if not specified)
61
+ * @returns Promise resolving to row data
62
+ * @example
63
+ * const data = await row.toJSON();
64
+ * // { Name: 'John', Email: 'john@example.com', ... }
65
+ *
66
+ * const partial = await row.toJSON({ columns: ['Name', 'Email'] });
67
+ * // { Name: 'John', Email: 'john@example.com' }
68
+ */
28
69
  toJSON(options?: {
29
70
  columns?: string[];
30
71
  }): Promise<T>;
31
72
  /**
32
73
  * Scrolls/paginates to bring this row into view.
33
- * Only works if rowIndex is known.
74
+ * Only works if rowIndex is known (e.g., from getRowByIndex).
75
+ * @throws Error if rowIndex is unknown
34
76
  */
35
77
  bringIntoView(): Promise<void>;
36
78
  /**
37
- * Fills the row with data. Automatically detects input types.
38
- */
39
- fill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;
40
- /**
41
- * Alias for fill() to avoid conflict with Locator.fill()
79
+ * Intelligently fills form fields in the row.
80
+ * Automatically detects input types (text, select, checkbox, contenteditable).
81
+ *
82
+ * @param data - Column-value pairs to fill
83
+ * @param options - Optional configuration
84
+ * @param options.inputMappers - Custom input selectors per column
85
+ * @example
86
+ * // Auto-detection
87
+ * await row.smartFill({ Name: 'John', Status: 'Active', Subscribe: true });
88
+ *
89
+ * // Custom input mappers
90
+ * await row.smartFill(
91
+ * { Name: 'John' },
92
+ * { inputMappers: { Name: (cell) => cell.locator('.custom-input') } }
93
+ * );
42
94
  */
43
95
  smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;
44
96
  };
@@ -191,13 +243,18 @@ export interface TableResult<T = any> {
191
243
  init(options?: {
192
244
  timeout?: number;
193
245
  }): Promise<TableResult>;
246
+ /**
247
+ * SYNC: Checks if the table has been initialized.
248
+ * @returns true if init() has been called and completed, false otherwise
249
+ */
250
+ isInitialized(): boolean;
194
251
  getHeaders: () => Promise<string[]>;
195
252
  getHeaderCell: (columnName: string) => Promise<Locator>;
196
253
  /**
197
254
  * Finds a row by filters on the current page only. Returns immediately (sync).
198
255
  * Throws error if table is not initialized.
199
256
  */
200
- getByRow: (filters: Record<string, string | RegExp | number>, options?: {
257
+ getRow: (filters: Record<string, string | RegExp | number>, options?: {
201
258
  exact?: boolean;
202
259
  }) => SmartRow;
203
260
  /**
@@ -206,38 +263,46 @@ export interface TableResult<T = any> {
206
263
  * @param index 1-based row index
207
264
  * @param options Optional settings including bringIntoView
208
265
  */
209
- getByRowIndex: (index: number, options?: {
266
+ getRowByIndex: (index: number, options?: {
210
267
  bringIntoView?: boolean;
211
268
  }) => SmartRow;
212
269
  /**
213
- * Searches for a row across all available data using the configured strategy (pagination, scroll, etc.).
214
- * Auto-initializes if needed.
270
+ * ASYNC: Searches for a single row across pages using pagination.
271
+ * Auto-initializes the table if not already initialized.
272
+ * @param filters - The filter criteria to match
273
+ * @param options - Search options including exact match and max pages
215
274
  */
216
- searchForRow: (filters: Record<string, string | RegExp | number>, options?: {
275
+ findRow: (filters: Record<string, string | RegExp | number>, options?: {
217
276
  exact?: boolean;
218
277
  maxPages?: number;
219
278
  }) => Promise<SmartRow>;
220
279
  /**
221
- * Navigates to a specific column using the configured CellNavigationStrategy.
280
+ * ASYNC: Searches for all matching rows across pages using pagination.
281
+ * Auto-initializes the table if not already initialized.
282
+ * @param filters - The filter criteria to match
283
+ * @param options - Search options including exact match, max pages, and asJSON
222
284
  */
223
- scrollToColumn: (columnName: string) => Promise<void>;
224
- getAllCurrentRows: <T extends {
285
+ findRows: <R extends {
225
286
  asJSON?: boolean;
226
- }>(options?: {
227
- filter?: Record<string, any>;
287
+ }>(filters: Record<string, string | RegExp | number>, options?: {
228
288
  exact?: boolean;
229
- } & T) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
289
+ maxPages?: number;
290
+ } & R) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
291
+ /**
292
+ * Navigates to a specific column using the configured CellNavigationStrategy.
293
+ */
294
+ scrollToColumn: (columnName: string) => Promise<void>;
230
295
  /**
231
- * @deprecated Use getAllCurrentRows instead. This method will be removed in a future major version.
296
+ * ASYNC: Gets all rows on the current page only (does not paginate).
297
+ * Auto-initializes the table if not already initialized.
298
+ * @param options - Filter and formatting options
232
299
  */
233
- getAllRows: <T extends {
300
+ getRows: <R extends {
234
301
  asJSON?: boolean;
235
302
  }>(options?: {
236
303
  filter?: Record<string, any>;
237
304
  exact?: boolean;
238
- } & T) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
239
- generateConfigPrompt: (options?: PromptOptions) => Promise<void>;
240
- generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;
305
+ } & R) => Promise<R['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
241
306
  /**
242
307
  * Resets the table state (clears cache, flags) and invokes the onReset strategy.
243
308
  */
@@ -308,4 +373,4 @@ export interface TableResult<T = any> {
308
373
  /**
309
374
  * Restricted table result that excludes methods that shouldn't be called during iteration.
310
375
  */
311
- export type RestrictedTableResult<T = any> = Omit<TableResult<T>, 'searchForRow' | 'iterateThroughTable' | 'reset' | 'getAllRows'>;
376
+ export type RestrictedTableResult<T = any> = Omit<TableResult<T>, 'searchForRow' | 'iterateThroughTable' | 'reset'>;
@@ -2,7 +2,7 @@ import type { Locator } from '@playwright/test';
2
2
  import { TableConfig, Selector, TableResult, PaginationStrategy } from './types';
3
3
  import { FillStrategies } from './strategies/fill';
4
4
  import { HeaderStrategies } from './strategies/headers';
5
- import { CellNavigationStrategies, ColumnStrategies } from './strategies/columns';
5
+ import { CellNavigationStrategies } from './strategies/columns';
6
6
  import { ResolutionStrategies } from './strategies/resolution';
7
7
  import { Strategies } from './strategies';
8
8
  /**
@@ -11,16 +11,9 @@ import { Strategies } from './strategies';
11
11
  export declare const useTable: <T = any>(rootLocator: Locator, configOptions?: TableConfig) => TableResult<T>;
12
12
  export declare const PaginationStrategies: {
13
13
  clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
14
- clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
15
- infiniteScroll: (timeout?: number) => PaginationStrategy;
16
- };
17
- /** @deprecated Use Strategies.Pagination instead */
18
- export declare const DeprecatedTableStrategies: {
19
- clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
20
- clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
21
14
  infiniteScroll: (timeout?: number) => PaginationStrategy;
22
15
  };
23
16
  export declare const SortingStrategies: {
24
17
  AriaSort: () => import("./types").SortingStrategy;
25
18
  };
26
- export { FillStrategies, HeaderStrategies, CellNavigationStrategies, ColumnStrategies, ResolutionStrategies, Strategies };
19
+ export { FillStrategies, HeaderStrategies, CellNavigationStrategies, ResolutionStrategies, Strategies };
package/dist/useTable.js CHANGED
@@ -9,7 +9,7 @@ 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.ColumnStrategies = exports.CellNavigationStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.SortingStrategies = exports.DeprecatedTableStrategies = exports.PaginationStrategies = exports.useTable = void 0;
12
+ exports.Strategies = exports.ResolutionStrategies = exports.CellNavigationStrategies = exports.HeaderStrategies = exports.FillStrategies = exports.SortingStrategies = 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");
@@ -19,13 +19,13 @@ const headers_1 = require("./strategies/headers");
19
19
  Object.defineProperty(exports, "HeaderStrategies", { enumerable: true, get: function () { return headers_1.HeaderStrategies; } });
20
20
  const columns_1 = require("./strategies/columns");
21
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
22
  const smartRow_1 = require("./smartRow");
24
23
  const filterEngine_1 = require("./filterEngine");
25
24
  const resolution_1 = require("./strategies/resolution");
26
25
  Object.defineProperty(exports, "ResolutionStrategies", { enumerable: true, get: function () { return resolution_1.ResolutionStrategies; } });
27
26
  const strategies_1 = require("./strategies");
28
27
  Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
28
+ const validation_1 = require("./strategies/validation");
29
29
  /**
30
30
  * Main hook to interact with a table.
31
31
  */
@@ -162,7 +162,8 @@ const useTable = (rootLocator, configOptions = {}) => {
162
162
  page: rootLocator.page(),
163
163
  resolve: resolve
164
164
  };
165
- const didLoadMore = yield config.strategies.pagination(context);
165
+ const paginationResult = yield config.strategies.pagination(context);
166
+ const didLoadMore = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
166
167
  if (didLoadMore) {
167
168
  _hasPaginated = true;
168
169
  currentPage++;
@@ -242,7 +243,7 @@ const useTable = (rootLocator, configOptions = {}) => {
242
243
  }),
243
244
  getHeaders: () => __awaiter(void 0, void 0, void 0, function* () {
244
245
  if (!_isInitialized || !_headerMap)
245
- throw new Error('Table not initialized. Call await table.init() first.');
246
+ throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
246
247
  return Array.from(_headerMap.keys());
247
248
  }),
248
249
  getHeaderCell: (columnName) => __awaiter(void 0, void 0, void 0, function* () {
@@ -299,22 +300,22 @@ const useTable = (rootLocator, configOptions = {}) => {
299
300
  }
300
301
  return results;
301
302
  }),
302
- getByRow: (filters, options = { exact: false }) => {
303
+ getRow: (filters, options = { exact: false }) => {
303
304
  if (!_isInitialized || !_headerMap)
304
- throw new Error('Table not initialized. Call await table.init() first.');
305
+ throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
305
306
  const allRows = resolve(config.rowSelector, rootLocator);
306
307
  const matchedRows = filterEngine.applyFilters(allRows, filters, _headerMap, options.exact || false, rootLocator.page());
307
308
  const rowLocator = matchedRows.first();
308
309
  return _makeSmart(rowLocator, _headerMap, 0); // fallback index 0
309
310
  },
310
- getByRowIndex: (index, options = {}) => {
311
+ getRowByIndex: (index, options = {}) => {
311
312
  if (!_isInitialized || !_headerMap)
312
- throw new Error('Table not initialized. Call await table.init() first.');
313
+ throw new Error('Table not initialized. Call await table.init() first, or use async methods like table.findRow() or table.getRows() which auto-initialize.');
313
314
  const rowIndex = index - 1; // Convert 1-based to 0-based
314
315
  const rowLocator = resolve(config.rowSelector, rootLocator).nth(rowIndex);
315
316
  return _makeSmart(rowLocator, _headerMap, rowIndex);
316
317
  },
317
- searchForRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
318
+ findRow: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
318
319
  yield _ensureInitialized();
319
320
  let row = yield _findRowLocator(filters, options);
320
321
  if (!row) {
@@ -322,7 +323,7 @@ const useTable = (rootLocator, configOptions = {}) => {
322
323
  }
323
324
  return _makeSmart(row, _headerMap, 0);
324
325
  }),
325
- getAllCurrentRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
326
+ getRows: (options) => __awaiter(void 0, void 0, void 0, function* () {
326
327
  yield _ensureInitialized();
327
328
  let rowLocators = resolve(config.rowSelector, rootLocator);
328
329
  if (options === null || options === void 0 ? void 0 : options.filter) {
@@ -335,22 +336,44 @@ const useTable = (rootLocator, configOptions = {}) => {
335
336
  }
336
337
  return smartRows;
337
338
  }),
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
- }),
342
- generateConfigPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
343
- const html = yield _getCleanHtml(rootLocator);
344
- const separator = "=".repeat(50);
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`;
346
- yield _handlePrompt('Smart Table Config', content, options);
347
- }),
348
- generateStrategyPrompt: (options) => __awaiter(void 0, void 0, void 0, function* () {
349
- const container = rootLocator.locator('xpath=..');
350
- const html = yield _getCleanHtml(container);
351
- const content = `\n==================================================\n🤖 COPY INTO GEMINI/ChatGPT TO WRITE A STRATEGY 🤖\n==================================================\nI need a custom Pagination Strategy for 'playwright-smart-table'.\nContainer HTML:\n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n`;
352
- yield _handlePrompt('Smart Table Strategy', content, options);
339
+ findRows: (filters, options) => __awaiter(void 0, void 0, void 0, function* () {
340
+ var _a, _b, _c, _d;
341
+ yield _ensureInitialized();
342
+ const allRows = [];
343
+ 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;
344
+ let pageCount = 0;
345
+ // Collect rows from current page
346
+ let rowLocators = resolve(config.rowSelector, rootLocator);
347
+ rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false, rootLocator.page());
348
+ let rows = yield rowLocators.all();
349
+ allRows.push(...rows.map((loc, i) => _makeSmart(loc, _headerMap, i)));
350
+ // Paginate and collect more rows
351
+ while (pageCount < effectiveMaxPages && config.strategies.pagination) {
352
+ const paginationResult = yield config.strategies.pagination({
353
+ root: rootLocator,
354
+ config,
355
+ resolve,
356
+ page: rootLocator.page()
357
+ });
358
+ const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
359
+ if (!didPaginate)
360
+ break;
361
+ pageCount++;
362
+ _hasPaginated = true;
363
+ // Collect rows from new page
364
+ rowLocators = resolve(config.rowSelector, rootLocator);
365
+ rowLocators = filterEngine.applyFilters(rowLocators, filters, _headerMap, (_d = options === null || options === void 0 ? void 0 : options.exact) !== null && _d !== void 0 ? _d : false, rootLocator.page());
366
+ rows = yield rowLocators.all();
367
+ allRows.push(...rows.map((loc, i) => _makeSmart(loc, _headerMap, i)));
368
+ }
369
+ if (options === null || options === void 0 ? void 0 : options.asJSON) {
370
+ return Promise.all(allRows.map(r => r.toJSON()));
371
+ }
372
+ return allRows;
353
373
  }),
374
+ isInitialized: () => {
375
+ return _isInitialized;
376
+ },
354
377
  sorting: {
355
378
  apply: (columnName, direction) => __awaiter(void 0, void 0, void 0, function* () {
356
379
  yield _ensureInitialized();
@@ -381,12 +404,13 @@ const useTable = (rootLocator, configOptions = {}) => {
381
404
  init: result.init,
382
405
  getHeaders: result.getHeaders,
383
406
  getHeaderCell: result.getHeaderCell,
384
- getByRow: result.getByRow,
385
- getByRowIndex: result.getByRowIndex,
386
- getAllCurrentRows: result.getAllCurrentRows,
407
+ getRow: result.getRow,
408
+ getRowByIndex: result.getRowByIndex,
409
+ findRow: result.findRow,
410
+ getRows: result.getRows,
411
+ findRows: result.findRows,
387
412
  getColumnValues: result.getColumnValues,
388
- generateConfigPrompt: result.generateConfigPrompt,
389
- generateStrategyPrompt: result.generateStrategyPrompt,
413
+ isInitialized: result.isInitialized,
390
414
  sorting: result.sorting,
391
415
  scrollToColumn: result.scrollToColumn,
392
416
  revalidate: result.revalidate,
@@ -444,6 +468,4 @@ const useTable = (rootLocator, configOptions = {}) => {
444
468
  };
445
469
  exports.useTable = useTable;
446
470
  exports.PaginationStrategies = pagination_1.PaginationStrategies;
447
- /** @deprecated Use Strategies.Pagination instead */
448
- exports.DeprecatedTableStrategies = pagination_1.DeprecatedPaginationStrategies;
449
471
  exports.SortingStrategies = sorting_1.SortingStrategies;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rickcedwhat/playwright-smart-table",
3
- "version": "4.0.0",
4
- "description": "A smart table utility for Playwright with built-in pagination strategies that are fully extensible.",
3
+ "version": "5.0.0",
4
+ "description": "Production-ready table testing for Playwright with smart column-aware locators. Core library with plugin support for custom table implementations.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/rickcedwhat/playwright-smart-table.git"