@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/README.md CHANGED
@@ -26,7 +26,7 @@ const table = await useTable(page.locator('#example'), {
26
26
  }).init();
27
27
 
28
28
  // Find the row with Name="Airi Satou", then get the Position cell
29
- const row = table.getByRow({ Name: 'Airi Satou' });
29
+ const row = table.getRow({ Name: 'Airi Satou' });
30
30
 
31
31
  const positionCell = row.getCell('Position');
32
32
  await expect(positionCell).toHaveText('Accountant');
@@ -35,7 +35,7 @@ await expect(positionCell).toHaveText('Accountant');
35
35
 
36
36
  **What's happening here?**
37
37
  - `useTable()` creates a smart table wrapper around your table locator
38
- - `getByRow()` finds a specific row by column values
38
+ - `getRow()` finds a specific row by column values
39
39
  - The returned `SmartRow` knows its column structure, so `.getCell('Position')` works directly
40
40
 
41
41
  ### Step 2: Understanding SmartRow
@@ -47,7 +47,7 @@ The `SmartRow` is the core power of this library. Unlike a standard Playwright `
47
47
  // Example from: https://datatables.net/examples/data_sources/dom
48
48
 
49
49
  // Get SmartRow via getByRow
50
- const row = table.getByRow({ Name: 'Airi Satou' });
50
+ const row = table.getRow({ Name: 'Airi Satou' });
51
51
 
52
52
  // Interact with cell using column name (resilient to column reordering)
53
53
  const positionCell = row.getCell('Position');
@@ -93,8 +93,8 @@ await table.init();
93
93
  // ✅ Verify Colleen is NOT visible initially
94
94
  await expect(page.getByText("Colleen Hurst")).not.toBeVisible();
95
95
 
96
- // Use searchForRow for pagination
97
- await expect(await table.searchForRow({ Name: "Colleen Hurst" })).toBeVisible();
96
+ // Use findRow for pagination
97
+ await expect(await table.findRow({ Name: "Colleen Hurst" })).toBeVisible();
98
98
  // NOTE: We're now on the page where Colleen Hurst exists (typically Page 2)
99
99
  ```
100
100
  <!-- /embed: pagination -->
@@ -112,7 +112,7 @@ const table = useTable(page.locator('#example'), {
112
112
  });
113
113
  await table.init();
114
114
 
115
- const row = table.getByRow({ Name: 'Airi Satou' });
115
+ const row = table.getRow({ Name: 'Airi Satou' });
116
116
  await expect(row).toBeVisible();
117
117
  ```
118
118
  <!-- /embed: advanced-debug -->
@@ -121,14 +121,14 @@ This will log header mappings, row scans, and pagination triggers to help troubl
121
121
 
122
122
  ### Resetting Table State
123
123
 
124
- If your tests navigate deep into a paginated table, use `.reset()` to return to the first page:
124
+ If your tests navigate deep into a paginated table, use `.reset()` to return to the first page. You can also configure an `onReset` hook to define custom reset behavior (e.g., clicking a "First Page" button):
125
125
 
126
126
  <!-- embed: advanced-reset -->
127
127
  ```typescript
128
128
  // Example from: https://datatables.net/examples/data_sources/dom
129
129
  // Navigate deep into the table by searching for a row on a later page
130
130
  try {
131
- await table.searchForRow({ Name: 'Angelica Ramos' });
131
+ await table.findRow({ Name: 'Angelica Ramos' });
132
132
  } catch (e) { }
133
133
 
134
134
  // Reset internal state (and potentially UI) to initial page
@@ -136,11 +136,28 @@ await table.reset();
136
136
  await table.init(); // Re-init after reset
137
137
 
138
138
  // Now subsequent searches start from the beginning
139
- const currentPageRow = table.getByRow({ Name: 'Airi Satou' });
139
+ const currentPageRow = table.getRow({ Name: 'Airi Satou' });
140
140
  await expect(currentPageRow).toBeVisible();
141
141
  ```
142
142
  <!-- /embed: advanced-reset -->
143
143
 
144
+ **Custom Reset Behavior:**
145
+
146
+ Use the `onReset` configuration option to define what happens when `table.reset()` is called:
147
+
148
+ ```typescript
149
+ const table = useTable(page.locator('#example'), {
150
+ strategies: {
151
+ pagination: Strategies.Pagination.clickNext(() => page.getByRole('link', { name: 'Next' }))
152
+ },
153
+ // Define custom reset logic
154
+ onReset: async ({ page }) => {
155
+ // Click "First" button to return to page 1
156
+ await page.getByRole('link', { name: 'First' }).click();
157
+ }
158
+ });
159
+ ```
160
+
144
161
  ### Column Scanning
145
162
 
146
163
  Efficiently extract all values from a specific column:
@@ -162,7 +179,7 @@ Use `smartFill()` to intelligently populate form fields in a table row. The meth
162
179
  <!-- embed: fill-basic -->
163
180
  ```typescript
164
181
  // Find a row and fill it with new data
165
- const row = table.getByRow({ ID: '1' });
182
+ const row = table.getRow({ ID: '1' });
166
183
 
167
184
  await row.smartFill({
168
185
  Name: 'John Updated',
@@ -254,11 +271,11 @@ expect(headers).toContain('Actions');
254
271
 
255
272
  // Use the renamed column
256
273
  // First check it's not on the current page
257
- const currentPageRow = table.getByRow({ "Last name": "Melisandre" });
274
+ const currentPageRow = table.getRow({ "Last name": "Melisandre" });
258
275
  await expect(currentPageRow).not.toBeVisible();
259
276
 
260
277
  // Then find it across pages
261
- const row = await table.searchForRow({ "Last name": "Melisandre" });
278
+ const row = await table.findRow({ "Last name": "Melisandre" });
262
279
  const actionsCell = row.getCell('Actions');
263
280
  await actionsCell.getByLabel("Select row").click();
264
281
  ```
@@ -282,34 +299,119 @@ const table = useTable(page.locator('#table1'), {
282
299
  await table.init();
283
300
 
284
301
  // Now column names are consistent
285
- const row = table.getByRow({ "Last Name": "Doe" });
302
+ const row = table.getRow({ "Last Name": "Doe" });
286
303
  const emailCell = row.getCell("Email");
287
304
  await expect(emailCell).toHaveText("jdoe@hotmail.com");
288
305
  ```
289
306
  <!-- /embed: header-transformer-normalize -->
290
307
 
308
+ ### Iterating Through Paginated Tables
309
+
310
+ Use `iterateThroughTable()` to process all rows across multiple pages. This is perfect for data scraping, validation, or bulk operations.
311
+
312
+ <!-- embed: iterate-through-table -->
313
+ ```typescript
314
+ // Iterate through all pages and collect data
315
+ const allNames = await table.iterateThroughTable(async ({ rows, index }) => {
316
+ // Return names from this iteration - automatically appended to allData
317
+ return await Promise.all(rows.map(r => r.getCell('Name').innerText()));
318
+ });
319
+
320
+ // allNames contains all names from all iterations
321
+ // Verify sorting across allNames
322
+ expect(allNames.flat().length).toBeGreaterThan(10);
323
+ ```
324
+ <!-- /embed: iterate-through-table -->
325
+
326
+ **With Deduplication (for infinite scroll):**
327
+
328
+ <!-- embed: iterate-through-table-dedupe -->
329
+ ```typescript
330
+ // Scrape all data with deduplication (useful for infinite scroll)
331
+ const allData = await table.iterateThroughTable(
332
+ async ({ rows }) => {
333
+ // Return row data - automatically appended to allData
334
+ return await Promise.all(rows.map(r => r.toJSON()));
335
+ },
336
+ {
337
+ dedupeStrategy: (row) => row.getCell(dedupeColumn).innerText(),
338
+ getIsLast: ({ paginationResult }) => !paginationResult
339
+ }
340
+ );
341
+
342
+ // allData contains all row data from all iterations (deduplicated at row level)
343
+ expect(allData.flat().length).toBeGreaterThan(0);
344
+ ```
345
+ <!-- /embed: iterate-through-table-dedupe -->
346
+
347
+ **With Hooks:**
348
+
349
+ <!-- embed: iterate-through-table-hooks -->
350
+ ```typescript
351
+ const allData = await table.iterateThroughTable(
352
+ async ({ rows, index, isFirst, isLast }) => {
353
+ // Normal logic for each iteration - return value appended to allData
354
+ return await Promise.all(rows.map(r => r.toJSON()));
355
+ },
356
+ {
357
+ getIsLast: ({ paginationResult }) => !paginationResult,
358
+ onFirst: async ({ allData }) => {
359
+ console.log('Starting data collection...');
360
+ // Could perform setup actions
361
+ },
362
+ onLast: async ({ allData }) => {
363
+ console.log(`Collected ${allData.length} total items`);
364
+ // Could perform cleanup or final actions
365
+ }
366
+ }
367
+ );
368
+ ```
369
+ <!-- /embed: iterate-through-table-hooks -->
370
+
371
+ **Hook Timing:**
372
+ - `onFirst`: Runs **before** your callback processes the first page
373
+ - `onLast`: Runs **after** your callback processes the last page
374
+ - Both are optional and receive `{ index, rows, allData }`
375
+
291
376
  ---
292
377
 
293
378
  ## 📖 API Reference
294
379
 
380
+ ### Method Comparison
381
+
382
+ Quick reference for choosing the right method:
383
+
384
+ | Method | Async/Sync | Paginates? | Returns | Use When |
385
+ |--------|------------|------------|---------|----------|
386
+ | `getRow()` | **Sync** | ❌ No | Single `SmartRow` | Finding row on current page only |
387
+ | `findRow()` | **Async** | ✅ Yes | Single `SmartRow` | Searching across pages |
388
+ | `getRows()` | **Async** | ❌ No | `SmartRow[]` | Getting all rows on current page |
389
+ | `findRows()` | **Async** | ✅ Yes | `SmartRow[]` | Getting all matching rows across pages |
390
+ | `iterateThroughTable()` | **Async** | ✅ Yes | `T[]` | Processing/scraping all pages with custom logic |
391
+
392
+ **Naming Pattern:**
393
+ - `get*` = Current page only (fast, no pagination)
394
+ - `find*` = Search across pages (slower, uses pagination)
395
+
295
396
  ### Table Methods
296
397
 
297
- #### <a name="getbyrow"></a>`getByRow(filters, options?)`
398
+ #### <a name="getrow"></a>`getRow(filters, options?)`
298
399
 
299
- **Purpose:** Strict retrieval - finds exactly one row matching the filters.
400
+ **Purpose:** Strict retrieval - finds exactly one row matching the filters on the **current page**.
300
401
 
301
402
  **Behavior:**
302
403
  - ✅ Returns `SmartRow` if exactly one match
303
404
  - ❌ Throws error if multiple matches (ambiguous query)
304
405
  - 👻 Returns sentinel locator if no match (allows `.not.toBeVisible()` assertions)
305
- - 🔄 Auto-paginates if row isn't on current page (when `maxPages > 1` and pagination strategy is configured)
406
+ - ℹ️ **Sync method**: Returns immediate locator result.
407
+ - 🔍 **Filtering**: Uses "contains" matching by default (e.g., "Tokyo" matches "Tokyo Office"). Set `exact: true` for strict equality.
306
408
 
307
409
  **Type Signature:**
308
410
  ```typescript
309
- getByRow: <T extends { asJSON?: boolean }>(
411
+ getRow: <T extends { asJSON?: boolean }>(
310
412
  filters: Record<string, string | RegExp | number>,
311
- options?: { exact?: boolean, maxPages?: number } & T
312
- ) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;
413
+ options?: { exact?: boolean } & T
414
+ ) => SmartRow;
313
415
  ```
314
416
 
315
417
  <!-- embed: get-by-row -->
@@ -319,11 +421,11 @@ const table = useTable(page.locator('#example'), { headerSelector: 'thead th' })
319
421
  await table.init();
320
422
 
321
423
  // Find a row where Name is "Airi Satou" AND Office is "Tokyo"
322
- const row = table.getByRow({ Name: "Airi Satou", Office: "Tokyo" });
424
+ const row = table.getRow({ Name: "Airi Satou", Office: "Tokyo" });
323
425
  await expect(row).toBeVisible();
324
426
 
325
427
  // Assert it does NOT exist
326
- await expect(table.getByRow({ Name: "Ghost User" })).not.toBeVisible();
428
+ await expect(table.getRow({ Name: "Ghost User" })).not.toBeVisible();
327
429
  ```
328
430
  <!-- /embed: get-by-row -->
329
431
 
@@ -331,7 +433,7 @@ Get row data as JSON:
331
433
  <!-- embed: get-by-row-json -->
332
434
  ```typescript
333
435
  // Get row data as JSON object
334
- const row = table.getByRow({ Name: 'Airi Satou' });
436
+ const row = table.getRow({ Name: 'Airi Satou' });
335
437
  const data = await row.toJSON();
336
438
  // Returns: { Name: "Airi Satou", Position: "Accountant", Office: "Tokyo", ... }
337
439
 
@@ -344,17 +446,33 @@ expect(partial).toEqual({ Name: 'Airi Satou' });
344
446
  ```
345
447
  <!-- /embed: get-by-row-json -->
346
448
 
347
- #### <a name="getallcurrentrows"></a>`getAllCurrentRows(options?)`
449
+ #### <a name="findrow"></a>`findRow(filters, options?)`
348
450
 
349
- **Purpose:** Inclusive retrieval - gets all rows on the current page matching optional filters.
451
+ **Purpose:** Async retrieval - finds exactly one row matching the filters **across multiple pages** (pagination).
350
452
 
351
- **Best for:** Checking existence, validating sort order, bulk data extraction on the current page.
453
+ **Behavior:**
454
+ - 🔄 Auto-initializes table if needed
455
+ - 🔎 Paginates through data until match is found or `maxPages` reached
456
+ - ✅ Returns `SmartRow` if found
457
+ - 👻 Returns sentinel locator if not found
352
458
 
353
- > **Note:** `getAllRows` is deprecated and will be removed in a future major version. Use `getAllCurrentRows` instead. The deprecated method still works for backwards compatibility.
459
+ **Type Signature:**
460
+ ```typescript
461
+ findRow: (
462
+ filters: Record<string, string | RegExp | number>,
463
+ options?: { exact?: boolean, maxPages?: number }
464
+ ) => Promise<SmartRow>;
465
+ ```
466
+
467
+ #### <a name="getrows"></a>`getRows(options?)`
468
+
469
+ **Purpose:** Inclusive retrieval - gets all rows on the **current page** matching optional filters.
470
+
471
+ **Best for:** Checking existence, validating sort order, bulk data extraction on the current page.
354
472
 
355
473
  **Type Signature:**
356
474
  ```typescript
357
- getAllCurrentRows: <T extends { asJSON?: boolean }>(
475
+ getRows: <T extends { asJSON?: boolean }>(
358
476
  options?: { filter?: Record<string, any>, exact?: boolean } & T
359
477
  ) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
360
478
  ```
@@ -363,17 +481,17 @@ getAllCurrentRows: <T extends { asJSON?: boolean }>(
363
481
  ```typescript
364
482
  // Example from: https://datatables.net/examples/data_sources/dom
365
483
  // 1. Get ALL rows on the current page
366
- const allRows = await table.getAllCurrentRows();
484
+ const allRows = await table.getRows();
367
485
  expect(allRows.length).toBeGreaterThan(0);
368
486
 
369
487
  // 2. Get subset of rows (Filtering)
370
- const tokyoUsers = await table.getAllCurrentRows({
488
+ const tokyoUsers = await table.getRows({
371
489
  filter: { Office: 'Tokyo' }
372
490
  });
373
491
  expect(tokyoUsers.length).toBeGreaterThan(0);
374
492
 
375
493
  // 3. Dump data to JSON
376
- const data = await table.getAllCurrentRows({ asJSON: true });
494
+ const data = await table.getRows({ asJSON: true });
377
495
  console.log(data); // [{ Name: "Airi Satou", ... }, ...]
378
496
  expect(data.length).toBeGreaterThan(0);
379
497
  expect(data[0]).toHaveProperty('Name');
@@ -384,7 +502,7 @@ Filter rows with exact match:
384
502
  <!-- embed: get-all-rows-exact -->
385
503
  ```typescript
386
504
  // Get rows with exact match (default is fuzzy/contains match)
387
- const exactMatches = await table.getAllCurrentRows({
505
+ const exactMatches = await table.getRows({
388
506
  filter: { Office: 'Tokyo' },
389
507
  exact: true // Requires exact string match
390
508
  });
@@ -393,6 +511,22 @@ expect(exactMatches.length).toBeGreaterThan(0);
393
511
  ```
394
512
  <!-- /embed: get-all-rows-exact -->
395
513
 
514
+ #### <a name="findrows"></a>`findRows(filters, options?)`
515
+
516
+ **Purpose:** Async retrieval - finds **all** rows matching filters **across multiple pages**.
517
+
518
+ **Behavior:**
519
+ - 🔄 Paginates and accumulates matches
520
+ - ⚠️ Can be slow on large datasets, use `maxPages` to limit scope
521
+
522
+ **Type Signature:**
523
+ ```typescript
524
+ findRows: (
525
+ filters: Record<string, string | RegExp | number>,
526
+ options?: { exact?: boolean, maxPages?: number }
527
+ ) => Promise<SmartRow[]>;
528
+ ```
529
+
396
530
  #### <a name="getcolumnvalues"></a>`getColumnValues(column, options?)`
397
531
 
398
532
  Scans a specific column across all pages and returns values. Supports custom mappers for extracting non-text data.
@@ -463,6 +597,44 @@ Resets table state (clears cache, pagination flags) and invokes the `onReset` st
463
597
  reset: () => Promise<void>;
464
598
  ```
465
599
 
600
+ #### <a name="sorting"></a>`sorting.apply(column, direction)` & `sorting.getState(column)`
601
+
602
+ If you've configured a sorting strategy, use these methods to sort columns and check their current sort state.
603
+
604
+ **Apply Sort:**
605
+ ```typescript
606
+ // Configure table with sorting strategy
607
+ const table = useTable(page.locator('#sortable-table'), {
608
+ strategies: {
609
+ sorting: Strategies.Sorting.AriaSort()
610
+ }
611
+ });
612
+ await table.init();
613
+
614
+ // Sort by Name column (ascending)
615
+ await table.sorting.apply('Name', 'asc');
616
+
617
+ // Sort by Age column (descending)
618
+ await table.sorting.apply('Age', 'desc');
619
+ ```
620
+
621
+ **Check Sort State:**
622
+ ```typescript
623
+ // Get current sort state of a column
624
+ const nameSort = await table.sorting.getState('Name');
625
+ // Returns: 'asc' | 'desc' | 'none'
626
+
627
+ expect(nameSort).toBe('asc');
628
+ ```
629
+
630
+ **Type Signatures:**
631
+ ```typescript
632
+ sorting: {
633
+ apply: (columnName: string, direction: 'asc' | 'desc') => Promise<void>;
634
+ getState: (columnName: string) => Promise<'asc' | 'desc' | 'none'>;
635
+ }
636
+ ```
637
+
466
638
  ---
467
639
 
468
640
  ## 🧩 Pagination Strategies
@@ -495,12 +667,12 @@ strategies: {
495
667
 
496
668
  #### <a name="tablestrategiesclickloadmore"></a>`Strategies.Pagination.clickLoadMore(selector)`
497
669
 
498
- Best for "Load More" buttons. Clicks and waits for row count to increase.
670
+ Best for "Load More" buttons that may not be part of the table. Clicks and waits for row count to increase.
499
671
 
500
672
  ```typescript
501
673
  strategies: {
502
- pagination: Strategies.Pagination.clickLoadMore((root) =>
503
- root.getByRole('button', { name: 'Load More' })
674
+ pagination: Strategies.Pagination.clickLoadMore((page) =>
675
+ page.getByRole('button', { name: 'Load More' })
504
676
  )
505
677
  }
506
678
  ```
@@ -573,22 +745,81 @@ A `SmartRow` extends Playwright's `Locator` with table-aware methods.
573
745
 
574
746
  <!-- embed-type: SmartRow -->
575
747
  ```typescript
748
+ /**
749
+ * Function to get the currently active/focused cell.
750
+ * Returns null if no cell is active.
751
+ */
752
+ export type GetActiveCellFn = (args: TableContext) => Promise<{
753
+ rowIndex: number;
754
+ columnIndex: number;
755
+ columnName?: string;
756
+ locator: Locator;
757
+ } | null>;
758
+
759
+
760
+ /**
761
+ * SmartRow - A Playwright Locator with table-aware methods.
762
+ *
763
+ * Extends all standard Locator methods (click, isVisible, etc.) with table-specific functionality.
764
+ *
765
+ * @example
766
+ * const row = table.getRow({ Name: 'John Doe' });
767
+ * await row.click(); // Standard Locator method
768
+ * const email = row.getCell('Email'); // Table-aware method
769
+ * const data = await row.toJSON(); // Extract all row data
770
+ * await row.smartFill({ Name: 'Jane', Status: 'Active' }); // Fill form fields
771
+ */
576
772
  export type SmartRow<T = any> = Locator & {
577
- getRequestIndex(): number | undefined;
773
+ /** Optional row index (0-based) if known */
578
774
  rowIndex?: number;
775
+
776
+ /**
777
+ * Get a cell locator by column name.
778
+ * @param column - Column name (case-sensitive)
779
+ * @returns Locator for the cell
780
+ * @example
781
+ * const emailCell = row.getCell('Email');
782
+ * await expect(emailCell).toHaveText('john@example.com');
783
+ */
579
784
  getCell(column: string): Locator;
785
+
786
+ /**
787
+ * Extract all cell data as a key-value object.
788
+ * @param options - Optional configuration
789
+ * @param options.columns - Specific columns to extract (extracts all if not specified)
790
+ * @returns Promise resolving to row data
791
+ * @example
792
+ * const data = await row.toJSON();
793
+ * // { Name: 'John', Email: 'john@example.com', ... }
794
+ *
795
+ * const partial = await row.toJSON({ columns: ['Name', 'Email'] });
796
+ * // { Name: 'John', Email: 'john@example.com' }
797
+ */
580
798
  toJSON(options?: { columns?: string[] }): Promise<T>;
799
+
581
800
  /**
582
801
  * Scrolls/paginates to bring this row into view.
583
- * Only works if rowIndex is known.
802
+ * Only works if rowIndex is known (e.g., from getRowByIndex).
803
+ * @throws Error if rowIndex is unknown
584
804
  */
585
805
  bringIntoView(): Promise<void>;
806
+
586
807
  /**
587
- * Fills the row with data. Automatically detects input types.
588
- */
589
- fill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;
590
- /**
591
- * Alias for fill() to avoid conflict with Locator.fill()
808
+ * Intelligently fills form fields in the row.
809
+ * Automatically detects input types (text, select, checkbox, contenteditable).
810
+ *
811
+ * @param data - Column-value pairs to fill
812
+ * @param options - Optional configuration
813
+ * @param options.inputMappers - Custom input selectors per column
814
+ * @example
815
+ * // Auto-detection
816
+ * await row.smartFill({ Name: 'John', Status: 'Active', Subscribe: true });
817
+ *
818
+ * // Custom input mappers
819
+ * await row.smartFill(
820
+ * { Name: 'John' },
821
+ * { inputMappers: { Name: (cell) => cell.locator('.custom-input') } }
822
+ * );
592
823
  */
593
824
  smartFill: (data: Partial<T> | Record<string, any>, options?: FillOptions) => Promise<void>;
594
825
  };
@@ -598,10 +829,12 @@ export type SmartRow<T = any> = Locator & {
598
829
  **Methods:**
599
830
  - `getCell(column: string)`: Returns a `Locator` for the specified cell in this row
600
831
  - `toJSON()`: Extracts all cell data as a key-value object
601
- - `smartFill(data, options?)`: Intelligently fills form fields in the row. Automatically detects input types or use `inputMappers` for custom control. Use Locator's standard `fill()` for single-input scenarios.
832
+ - `smartFill(data, options?)`: Intelligently fills form fields in the row. Automatically detects input types (text, select, checkbox) or use `inputMappers` option for custom control.
602
833
 
603
834
  All standard Playwright `Locator` methods (`.click()`, `.isVisible()`, `.textContent()`, etc.) are also available.
604
835
 
836
+ **Note**: `getRequestIndex()` is an internal method for row tracking - you typically won't need it.
837
+
605
838
  #### <a name="tableconfig"></a>`TableConfig`
606
839
 
607
840
  Configuration options for `useTable()`.
@@ -684,6 +917,15 @@ Flexible selector type supporting strings, functions, or existing locators.
684
917
 
685
918
  <!-- embed-type: Selector -->
686
919
  ```typescript
920
+ /**
921
+ * Flexible selector type - can be a CSS string, function returning a Locator, or Locator itself.
922
+ * @example
923
+ * // String selector
924
+ * rowSelector: 'tbody tr'
925
+ *
926
+ * // Function selector
927
+ * rowSelector: (root) => root.locator('[role="row"]')
928
+ */
687
929
  export type Selector = string | ((root: Locator | Page) => Locator);
688
930
 
689
931
  /**
@@ -717,66 +959,6 @@ Returns `true` if more data was loaded, `false` if pagination should stop.
717
959
 
718
960
  ---
719
961
 
720
- ## 🔄 Migration Guide
721
-
722
- ### Upgrading from v3.x to v4.0
723
-
724
- **Breaking Change**: Strategy imports are now consolidated under the `Strategies` object.
725
-
726
- #### Import Changes
727
- ```typescript
728
- // ❌ Old (v3.x)
729
- import { PaginationStrategies, SortingStrategies } from '../src/useTable';
730
-
731
- // ✅ New (v4.0)
732
- import { Strategies } from '../src/strategies';
733
- // or
734
- import { useTable, Strategies } from '../src/useTable';
735
- ```
736
-
737
- #### Strategy Usage
738
- ```typescript
739
- // ❌ Old (v3.x)
740
- strategies: {
741
- pagination: PaginationStrategies.clickNext(() => page.locator('#next')),
742
- sorting: SortingStrategies.AriaSort(),
743
- header: HeaderStrategies.scrollRight,
744
- cellNavigation: ColumnStrategies.keyboard
745
- }
746
-
747
- // ✅ New (v4.0)
748
- strategies: {
749
- pagination: Strategies.Pagination.clickNext(() => page.locator('#next')),
750
- sorting: Strategies.Sorting.AriaSort(),
751
- header: Strategies.Header.scrollRight,
752
- cellNavigation: Strategies.Column.keyboard
753
- }
754
- ```
755
-
756
- #### New Features (Optional)
757
-
758
- **Generic Type Support:**
759
- ```typescript
760
- interface User {
761
- Name: string;
762
- Email: string;
763
- Office: string;
764
- }
765
-
766
- const table = useTable<User>(page.locator('#table'), config);
767
- const data = await row.toJSON(); // Type: User (not Record<string, string>)
768
- ```
769
-
770
- **Revalidate Method:**
771
- ```typescript
772
- // Refresh column mappings when table structure changes
773
- await table.revalidate();
774
- ```
775
-
776
- For detailed migration instructions optimized for AI code transformation, see the [AI Migration Guide](./MIGRATION_v4.md).
777
-
778
- ---
779
-
780
962
  ## 🚀 Tips & Best Practices
781
963
 
782
964
  1. **Start Simple**: Try the defaults first - they work for most standard HTML tables
@@ -784,6 +966,8 @@ For detailed migration instructions optimized for AI code transformation, see th
784
966
  3. **Leverage SmartRow**: Use `.getCell()` instead of manual column indices - your tests will be more maintainable
785
967
  4. **Type Safety**: All methods are fully typed - use TypeScript for the best experience
786
968
  5. **Pagination Strategies**: Create reusable strategies for tables with similar pagination patterns
969
+ 6. **Async vs Sync**: Use `findRow()` for paginated searches and `getRow()` for strict, single-page assertions.
970
+ 7. **Sorting**: Use `table.sorting.apply()` to sort columns and `table.sorting.getState()` to check sort state.
787
971
 
788
972
  ---
789
973
 
package/dist/smartRow.js CHANGED
@@ -19,7 +19,6 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
19
19
  const smart = rowLocator;
20
20
  // Attach State
21
21
  smart.rowIndex = rowIndex;
22
- smart.getRequestIndex = () => rowIndex;
23
22
  // Attach Methods
24
23
  smart.getCell = (colName) => {
25
24
  const idx = map.get(colName);
@@ -112,8 +111,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
112
111
  }
113
112
  return result;
114
113
  });
115
- // @ts-ignore
116
- smart.fill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
114
+ smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
117
115
  for (const [colName, value] of Object.entries(data)) {
118
116
  const colIdx = map.get(colName);
119
117
  if (colIdx === undefined) {
@@ -144,12 +142,11 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
144
142
  });
145
143
  smart.bringIntoView = () => __awaiter(void 0, void 0, void 0, function* () {
146
144
  if (rowIndex === undefined) {
147
- throw new Error('Cannot bring row into view - row index is unknown. Use getByRowIndex() instead of getByRow().');
145
+ throw new Error('Cannot bring row into view - row index is unknown. Use getRowByIndex() instead of getRow().');
148
146
  }
149
147
  // Scroll row into view using Playwright's built-in method
150
148
  yield rowLocator.scrollIntoViewIfNeeded();
151
149
  });
152
- smart.smartFill = smart.fill;
153
150
  return smart;
154
151
  };
155
152
  exports.createSmartRow = createSmartRow;
@@ -9,44 +9,10 @@ export type CellNavigationStrategy = (context: StrategyContext & {
9
9
  index: number;
10
10
  rowIndex?: number;
11
11
  }) => Promise<void>;
12
- /** @deprecated Use CellNavigationStrategy instead */
13
- export type ColumnStrategy = CellNavigationStrategy;
14
12
  export declare const CellNavigationStrategies: {
15
13
  /**
16
14
  * Default strategy: Assumes column is accessible or standard scrolling works.
17
15
  * No specific action taken other than what Playwright's default locator handling does.
18
16
  */
19
17
  default: () => Promise<void>;
20
- /**
21
- * Strategy that clicks into the table to establish focus and then uses the Right Arrow key
22
- * to navigate to the target CELL (navigates down to the row, then right to the column).
23
- *
24
- * Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
25
- * or where keyboard navigation is the primary way to move focus.
26
- */
27
- keyboard: (context: StrategyContext & {
28
- column: string;
29
- index: number;
30
- rowIndex?: number;
31
- }) => Promise<void>;
32
- };
33
- /** @deprecated Use CellNavigationStrategies instead */
34
- export declare const ColumnStrategies: {
35
- /**
36
- * Default strategy: Assumes column is accessible or standard scrolling works.
37
- * No specific action taken other than what Playwright's default locator handling does.
38
- */
39
- default: () => Promise<void>;
40
- /**
41
- * Strategy that clicks into the table to establish focus and then uses the Right Arrow key
42
- * to navigate to the target CELL (navigates down to the row, then right to the column).
43
- *
44
- * Useful for canvas-based grids like Glide where DOM scrolling might not be enough for interaction
45
- * or where keyboard navigation is the primary way to move focus.
46
- */
47
- keyboard: (context: StrategyContext & {
48
- column: string;
49
- index: number;
50
- rowIndex?: number;
51
- }) => Promise<void>;
52
18
  };