@rickcedwhat/playwright-smart-table 6.6.0 → 6.7.1

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
@@ -76,13 +76,92 @@ const email = await row.getCell('Email').textContent();
76
76
  const allActive = await table.findRows({ Status: 'Active' });
77
77
  ```
78
78
 
79
+ ### Iterating Across Pages
80
+
81
+ ```typescript
82
+ // forEach — sequential, safe for interactions (parallel: false default)
83
+ await table.forEach(async ({ row, rowIndex, stop }) => {
84
+ if (await row.getCell('Status').innerText() === 'Done') stop();
85
+ await row.getCell('Checkbox').click();
86
+ });
87
+
88
+ // map — parallel within page, safe for reads (parallel: true default)
89
+ const emails = await table.map(({ row }) => row.getCell('Email').innerText());
90
+
91
+ // filter — async predicate across all pages, returns SmartRowArray
92
+ const active = await table.filter(async ({ row }) =>
93
+ await row.getCell('Status').innerText() === 'Active'
94
+ );
95
+
96
+ // for await...of — low-level page-by-page iteration
97
+ for await (const { row, rowIndex } of table) {
98
+ console.log(rowIndex, await row.getCell('Name').innerText());
99
+ }
100
+ ```
101
+
102
+ > **`map` + UI interactions:** `map` defaults to `parallel: true`. If your callback opens popovers,
103
+ > fills inputs, or otherwise mutates UI state, pass `{ parallel: false }` to avoid overlapping interactions.
104
+
105
+ ### `filter` vs `findRows`
106
+
107
+ | Use case | Best tool |
108
+ |---|---|
109
+ | Match by column value / regex / locator | `findRows` |
110
+ | Computed value (math, range, derived) | `filter` |
111
+ | Cross-column OR logic | `filter` |
112
+ | Multi-step interaction in predicate (click, read, close) | `filter` |
113
+ | Early exit after N matches | `filter` + `stop()` |
114
+
115
+ **`findRows` is faster** for column-value matches — Playwright evaluates the locator natively with no DOM reads. **`filter` is more flexible** for logic that a CSS selector can't express.
116
+
117
+ ```typescript
118
+ // findRows — structural match, no DOM reads, fast
119
+ const notStarted = await table.findRows({
120
+ Status: (cell) => cell.locator('[class*="gray"]')
121
+ });
122
+
123
+ // filter — arbitrary async logic
124
+ const expensive = await table.filter(async ({ row }) => {
125
+ const price = parseFloat(await row.getCell('Price').innerText());
126
+ const qty = parseFloat(await row.getCell('Qty').innerText());
127
+ return price * qty > 1000;
128
+ });
129
+ ```
130
+
131
+ ### Advanced: `columnOverrides`
132
+
133
+ For complex DOM structures, custom data extraction, or specialized input widgets, use `columnOverrides` to intercept how Smart Table interacts with specific columns:
134
+
135
+ ```typescript
136
+ const table = useTable(page.locator('#table'), {
137
+ columnOverrides: {
138
+ // Override how data is read from the 'Status' column (e.g., for .toJSON())
139
+ Status: {
140
+ read: async (cell) => {
141
+ const isChecked = await cell.locator('input[type="checkbox"]').isChecked();
142
+ return isChecked ? 'Active' : 'Inactive';
143
+ }
144
+ },
145
+ // Override how data is written to the 'Tags' column (for .smartFill())
146
+ Tags: {
147
+ write: async (cell, value) => {
148
+ await cell.click();
149
+ await page.keyboard.type(value);
150
+ await page.keyboard.press('Enter');
151
+ }
152
+ }
153
+ }
154
+ });
155
+ ```
156
+
79
157
  ## Key Features
80
158
 
81
159
  - 🎯 **Smart Locators** - Find rows by content, not position
82
- - 🧠 **Fuzzy Matching** - Smart suggestions for typos (e.g., incorrectly typed "Firstname" suggests "First Name" in error messages)
160
+ - 🧠 **Fuzzy Matching** - Smart suggestions for typos in column names
83
161
  - ⚡ **Smart Initialization** - Handles loading states and dynamic headers automatically
84
162
  - 📄 **Auto-Pagination** - Search across all pages automatically
85
163
  - 🔍 **Column-Aware Access** - Access cells by column name
164
+ - 🔁 **Iteration Methods** - `forEach`, `map`, `filter`, and `for await...of` across all pages
86
165
  - 🛠️ **Debug Mode** - Visual debugging with slow motion and logging
87
166
  - 🔌 **[Extensible Strategies](docs/concepts/strategies.md)** - Support any table implementation
88
167
  - 💪 **Type-Safe** - Full TypeScript support
@@ -108,11 +187,21 @@ const allActive = await table.findRows({ Status: 'Active' });
108
187
 
109
188
  ### ⚠️ Important Note on Pagination & Interactions
110
189
 
111
- When using `findRows` across multiple pages, the returned `SmartRow` locators represent elements that may no longer be attached to the current DOM if the table paginated past them.
190
+ When `findRows` or `filter` paginates across pages, returned `SmartRow` locators point to rows that may be off the current DOM page.
191
+
192
+ - **Data extraction:** Safe — `toJSON()` and cell reads work while the row is visible during iteration.
193
+ - **Interactions after pagination:** Use `await row.bringIntoView()` first — it navigates back to the page the row was originally found on, then you can safely click/fill.
112
194
 
113
- - **Data Extraction:** Safe. You can use `table.iterateThroughTable()` to extract data (`await row.toJSON()`) while the row is visible.
114
- - **Interactions:** Unsafe directly. You cannot do `await row.click()` if the row is on Page 1 but the table is currently showing Page 3.
115
- - **Solution:** If you need to interact with a row found on a previous page, you may be able to use `await row.bringIntoView()` before interacting with it to force the table to paginate back to that row (Note: this specific cross-page interaction flow is currently under testing).
195
+ ```typescript
196
+ const active = await table.filter(async ({ row }) =>
197
+ await row.getCell('Status').innerText() === 'Active'
198
+ );
199
+
200
+ for (const row of active) {
201
+ await row.bringIntoView(); // navigate back to the row's page
202
+ await row.getCell('Checkbox').click(); // safe to interact
203
+ }
204
+ ```
116
205
 
117
206
  ## Documentation
118
207
 
@@ -19,10 +19,7 @@ export declare class RowFinder<T = any> {
19
19
  exact?: boolean;
20
20
  maxPages?: number;
21
21
  }): Promise<SmartRow<T>>;
22
- findRows(filtersOrOptions?: (Partial<T> | Record<string, FilterValue>) & ({
23
- exact?: boolean;
24
- maxPages?: number;
25
- }), legacyOptions?: {
22
+ findRows(filters?: Partial<T> | Record<string, FilterValue>, options?: {
26
23
  exact?: boolean;
27
24
  maxPages?: number;
28
25
  }): Promise<SmartRowArray<T>>;
@@ -8,17 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __rest = (this && this.__rest) || function (s, e) {
12
- var t = {};
13
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
- t[p] = s[p];
15
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
- t[p[i]] = s[p[i]];
19
- }
20
- return t;
21
- };
22
11
  Object.defineProperty(exports, "__esModule", { value: true });
23
12
  exports.RowFinder = void 0;
24
13
  const debugUtils_1 = require("../utils/debugUtils");
@@ -51,46 +40,26 @@ class RowFinder {
51
40
  yield (0, debugUtils_1.debugDelay)(this.config, 'findRow');
52
41
  const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
53
42
  .filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
54
- return this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
43
+ const smartRow = this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
44
+ smartRow._isSentinel = true;
45
+ return smartRow;
55
46
  });
56
47
  }
57
- findRows(filtersOrOptions,
58
- // Deprecated: verify legacy usage pattern support
59
- legacyOptions) {
48
+ findRows(filters, options) {
60
49
  return __awaiter(this, void 0, void 0, function* () {
61
- // Detect argument pattern:
62
- // Pattern A: findRows({ Name: 'Alice' }, { maxPages: 5 })
63
- // Pattern B: findRows({ maxPages: 5 }) <-- No filters, just options
64
- // Pattern C: findRows({ Name: 'Alice' }) <-- Only filters
65
50
  var _a, _b;
66
- let filters = {};
67
- let options = {};
68
- if (legacyOptions) {
69
- // Pattern A
70
- filters = filtersOrOptions;
71
- options = legacyOptions;
72
- }
73
- else {
74
- // Pattern B or C
75
- // We need to separate unknown keys (filters) from known options (exact, maxPages)
76
- // However, filtersOrOptions can be null/undefined
77
- if (filtersOrOptions) {
78
- const _c = filtersOrOptions, { exact, maxPages } = _c, rest = __rest(_c, ["exact", "maxPages"]);
79
- options = { exact, maxPages };
80
- filters = rest;
81
- }
82
- }
51
+ const filtersRecord = filters || {};
83
52
  const map = yield this.tableMapper.getMap();
84
53
  const allRows = [];
85
- const effectiveMaxPages = (_b = (_a = options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
54
+ const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
86
55
  let pagesScanned = 1;
87
56
  const collectMatches = () => __awaiter(this, void 0, void 0, function* () {
88
57
  var _a, _b;
89
58
  // ... logic ...
90
59
  let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
91
60
  // Only apply filters if we have them
92
- if (Object.keys(filters).length > 0) {
93
- rowLocators = this.filterEngine.applyFilters(rowLocators, filters, map, (_a = options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
61
+ if (Object.keys(filtersRecord).length > 0) {
62
+ rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
94
63
  }
95
64
  const currentRows = yield rowLocators.all();
96
65
  const isRowLoading = (_b = this.config.strategies.loading) === null || _b === void 0 ? void 0 : _b.isRowLoading;
@@ -125,7 +94,7 @@ class RowFinder {
125
94
  }
126
95
  paginationResult = yield this.config.strategies.pagination.goNext(context);
127
96
  }
128
- const didPaginate = yield (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
97
+ const didPaginate = (0, validation_1.validatePaginationResult)(paginationResult, 'Pagination Strategy');
129
98
  if (!didPaginate)
130
99
  break;
131
100
  this.tableState.currentPageIndex++;
@@ -105,7 +105,7 @@ class TableMapper {
105
105
  text = yield this.config.headerTransformer({
106
106
  text,
107
107
  index: i,
108
- locator: this.rootLocator.locator(this.config.headerSelector).nth(i),
108
+ locator: this.resolve(this.config.headerSelector, this.rootLocator).nth(i),
109
109
  seenHeaders
110
110
  });
111
111
  }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export * from './useTable';
2
- export * from './types';
3
- export * from './plugins';
4
- export * from './strategies';
1
+ export { useTable } from './useTable';
2
+ export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, } from './types';
3
+ export { Strategies } from './strategies';
4
+ export { Plugins } from './plugins';
package/dist/index.js CHANGED
@@ -1,20 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
2
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./useTable"), exports);
18
- __exportStar(require("./types"), exports);
19
- __exportStar(require("./plugins"), exports);
20
- __exportStar(require("./strategies"), exports);
3
+ exports.Plugins = exports.Strategies = exports.useTable = void 0;
4
+ var useTable_1 = require("./useTable");
5
+ Object.defineProperty(exports, "useTable", { enumerable: true, get: function () { return useTable_1.useTable; } });
6
+ // Export namespace-like strategy collections
7
+ var strategies_1 = require("./strategies");
8
+ Object.defineProperty(exports, "Strategies", { enumerable: true, get: function () { return strategies_1.Strategies; } });
9
+ var plugins_1 = require("./plugins");
10
+ Object.defineProperty(exports, "Plugins", { enumerable: true, get: function () { return plugins_1.Plugins; } });
package/dist/plugins.d.ts CHANGED
@@ -3,7 +3,6 @@ export declare const Plugins: {
3
3
  Strategies: {
4
4
  header: (context: import("./types").TableContext) => Promise<string[]>;
5
5
  getCellLocator: ({ row, columnIndex }: any) => any;
6
- cellNavigation: ({ root, page, index }: any) => Promise<void>;
7
6
  navigation: {
8
7
  goRight: ({ root, page }: any) => Promise<void>;
9
8
  goLeft: ({ root, page }: any) => Promise<void>;
package/dist/smartRow.js CHANGED
@@ -16,7 +16,6 @@ const debugUtils_1 = require("./utils/debugUtils");
16
16
  /**
17
17
  * Internal helper to navigate to a cell with active cell optimization.
18
18
  * Uses navigation primitives (goUp, goDown, goLeft, goRight, goHome) for orchestration.
19
- * Falls back to cellNavigation for backward compatibility.
20
19
  * Returns the target cell locator after navigation.
21
20
  */
22
21
  const _navigateToCell = (params) => __awaiter(void 0, void 0, void 0, function* () {
@@ -91,7 +90,6 @@ const _navigateToCell = (params) => __awaiter(void 0, void 0, void 0, function*
91
90
  }
92
91
  return null;
93
92
  }
94
- ;
95
93
  return null;
96
94
  });
97
95
  /**
@@ -121,24 +119,30 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
121
119
  }
122
120
  return resolve(config.cellSelector, rowLocator).nth(idx);
123
121
  };
122
+ smart.wasFound = () => {
123
+ return !smart._isSentinel;
124
+ };
124
125
  smart.toJSON = (options) => __awaiter(void 0, void 0, void 0, function* () {
125
- var _a, _b;
126
+ var _a;
126
127
  const result = {};
127
128
  const page = rootLocator.page();
129
+ // Build a getHeaderCell helper for the beforeCellRead context.
130
+ // Uses the table reference if available, otherwise falls back to index-based lookup.
131
+ const getHeaderCell = (table === null || table === void 0 ? void 0 : table.getHeaderCell)
132
+ ? table.getHeaderCell.bind(table)
133
+ : (colName) => __awaiter(void 0, void 0, void 0, function* () {
134
+ const idx = map.get(colName);
135
+ if (idx === undefined)
136
+ throw new Error(`Column "${colName}" not found`);
137
+ return resolve(config.headerSelector, rootLocator).nth(idx);
138
+ });
128
139
  for (const [col, idx] of map.entries()) {
129
140
  if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(col)) {
130
141
  continue;
131
142
  }
132
- // Check if we have a column override or data mapper for this column
143
+ // Check if we have a column override for this column
133
144
  const columnOverride = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[col];
134
- const mapper = (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read) || ((_b = config.dataMapper) === null || _b === void 0 ? void 0 : _b[col]);
135
- if (mapper) {
136
- // Use custom mapper
137
- // Ensure we have the cell first (same navigation logic)
138
- // ... wait, the navigation logic below assumes we need to navigate.
139
- // If we have a mapper, we still need the cell locator.
140
- // Let's reuse the navigation logic to get targetCell
141
- }
145
+ const mapper = columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read;
142
146
  // --- Navigation Logic Start ---
143
147
  const cell = config.strategies.getCellLocator
144
148
  ? config.strategies.getCellLocator({
@@ -168,6 +172,19 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
168
172
  }
169
173
  }
170
174
  // --- Navigation Logic End ---
175
+ // Call beforeCellRead hook if configured.
176
+ // Fires for BOTH columnOverrides.read and the default innerText path.
177
+ if (config.strategies.beforeCellRead) {
178
+ yield config.strategies.beforeCellRead({
179
+ cell: targetCell,
180
+ columnName: col,
181
+ columnIndex: idx,
182
+ row: rowLocator,
183
+ page,
184
+ root: rootLocator,
185
+ getHeaderCell,
186
+ });
187
+ }
171
188
  if (mapper) {
172
189
  // Apply mapper
173
190
  const mappedValue = yield mapper(targetCell);
@@ -182,6 +199,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
182
199
  return result;
183
200
  });
184
201
  smart.smartFill = (data, fillOptions) => __awaiter(void 0, void 0, void 0, function* () {
202
+ var _a;
185
203
  (0, debugUtils_1.logDebug)(config, 'info', 'Filling row', data);
186
204
  for (const [colName, value] of Object.entries(data)) {
187
205
  if (value === undefined)
@@ -200,19 +218,35 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
200
218
  rowLocator,
201
219
  rowIndex
202
220
  });
203
- const strategy = config.strategies.fill || fill_1.FillStrategies.default;
204
- (0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
205
- yield strategy({
206
- row: smart,
207
- columnName: colName,
208
- value,
209
- index: rowIndex !== null && rowIndex !== void 0 ? rowIndex : -1,
210
- page: rowLocator.page(),
211
- rootLocator,
212
- config,
213
- table: table,
214
- fillOptions
215
- });
221
+ const columnOverride = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[colName];
222
+ if (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.write) {
223
+ const cellLocator = smart.getCell(colName);
224
+ let currentValue;
225
+ if (columnOverride.read) {
226
+ currentValue = yield columnOverride.read(cellLocator);
227
+ }
228
+ yield columnOverride.write({
229
+ cell: cellLocator,
230
+ targetValue: value,
231
+ currentValue,
232
+ row: smart
233
+ });
234
+ }
235
+ else {
236
+ const strategy = config.strategies.fill || fill_1.FillStrategies.default;
237
+ (0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
238
+ yield strategy({
239
+ row: smart,
240
+ columnName: colName,
241
+ value,
242
+ index: rowIndex !== null && rowIndex !== void 0 ? rowIndex : -1,
243
+ page: rowLocator.page(),
244
+ rootLocator,
245
+ config,
246
+ table: table,
247
+ fillOptions
248
+ });
249
+ }
216
250
  // Delay after filling
217
251
  yield (0, debugUtils_1.debugDelay)(config, 'getCell');
218
252
  }
@@ -8,25 +8,23 @@ export * from './dedupe';
8
8
  export * from './loading';
9
9
  export declare const Strategies: {
10
10
  Pagination: {
11
- clickNext: (nextButtonSelector: import("..").Selector, options?: {
12
- stabilization?: import("./stabilization").StabilizationStrategy;
13
- timeout?: number;
14
- }) => import("..").PaginationStrategy;
15
11
  click: (selectors: {
16
12
  next?: import("..").Selector;
17
13
  previous?: import("..").Selector;
14
+ nextBulk?: import("..").Selector;
15
+ previousBulk?: import("..").Selector;
18
16
  first?: import("..").Selector;
19
17
  }, options?: {
20
18
  stabilization?: import("./stabilization").StabilizationStrategy;
21
19
  timeout?: number;
22
- }) => import("..").PaginationStrategy;
20
+ }) => import("../types").PaginationStrategy;
23
21
  infiniteScroll: (options?: {
24
22
  action?: "scroll" | "js-scroll";
25
23
  scrollTarget?: import("..").Selector;
26
24
  scrollAmount?: number;
27
25
  stabilization?: import("./stabilization").StabilizationStrategy;
28
26
  timeout?: number;
29
- }) => import("..").PaginationStrategy;
27
+ }) => import("../types").PaginationStrategy;
30
28
  };
31
29
  Sorting: {
32
30
  AriaSort: () => import("..").SortingStrategy;
@@ -38,13 +36,13 @@ export declare const Strategies: {
38
36
  visible: ({ config, resolve, root }: import("..").StrategyContext) => Promise<string[]>;
39
37
  };
40
38
  Fill: {
41
- default: ({ row, columnName, value, fillOptions, config, table }: Parameters<import("..").FillStrategy>[0]) => Promise<void>;
39
+ default: ({ row, columnName, value, fillOptions, config, table }: Parameters<import("../types").FillStrategy>[0]) => Promise<void>;
42
40
  };
43
41
  Resolution: {
44
42
  default: import("./resolution").ColumnResolutionStrategy;
45
43
  };
46
44
  Dedupe: {
47
- byTopPosition: (tolerance?: number) => import("..").DedupeStrategy;
45
+ byTopPosition: (tolerance?: number) => import("../types").DedupeStrategy;
48
46
  };
49
47
  Loading: {
50
48
  Table: {
@@ -1,27 +1,11 @@
1
1
  import type { PaginationStrategy, Selector } from '../types';
2
2
  import { StabilizationStrategy } from './stabilization';
3
3
  export declare const PaginationStrategies: {
4
- /**
5
- * Strategy: Clicks a "Next" button and waits for stabilization.
6
- * Backward compatibility for when only a single 'next' selector was needed.
7
- * @deprecated Use `click` with `{ next: selector }` instead.
8
- */
9
- clickNext: (nextButtonSelector: Selector, options?: {
10
- stabilization?: StabilizationStrategy;
11
- timeout?: number;
12
- }) => PaginationStrategy;
13
- /**
14
- * Strategy: Classic Pagination Buttons.
15
- * Clicks 'Next', 'Previous', or 'First' buttons and waits for stabilization.
16
- *
17
- * @param selectors Selectors for pagination buttons.
18
- * @param options.stabilization Strategy to determine when the page has updated.
19
- * Defaults to `contentChanged({ scope: 'first' })`.
20
- * @param options.timeout Timeout for the click action.
21
- */
22
4
  click: (selectors: {
23
5
  next?: Selector;
24
6
  previous?: Selector;
7
+ nextBulk?: Selector;
8
+ previousBulk?: Selector;
25
9
  first?: Selector;
26
10
  }, options?: {
27
11
  stabilization?: StabilizationStrategy;
@@ -12,23 +12,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.PaginationStrategies = void 0;
13
13
  const stabilization_1 = require("./stabilization");
14
14
  exports.PaginationStrategies = {
15
- /**
16
- * Strategy: Clicks a "Next" button and waits for stabilization.
17
- * Backward compatibility for when only a single 'next' selector was needed.
18
- * @deprecated Use `click` with `{ next: selector }` instead.
19
- */
20
- clickNext: (nextButtonSelector, options = {}) => {
21
- return exports.PaginationStrategies.click({ next: nextButtonSelector }, options);
22
- },
23
- /**
24
- * Strategy: Classic Pagination Buttons.
25
- * Clicks 'Next', 'Previous', or 'First' buttons and waits for stabilization.
26
- *
27
- * @param selectors Selectors for pagination buttons.
28
- * @param options.stabilization Strategy to determine when the page has updated.
29
- * Defaults to `contentChanged({ scope: 'first' })`.
30
- * @param options.timeout Timeout for the click action.
31
- */
32
15
  click: (selectors, options = {}) => {
33
16
  var _a;
34
17
  const defaultStabilize = (_a = options.stabilization) !== null && _a !== void 0 ? _a : stabilization_1.StabilizationStrategies.contentChanged({ scope: 'first', timeout: options.timeout });
@@ -49,6 +32,8 @@ exports.PaginationStrategies = {
49
32
  return {
50
33
  goNext: createClicker(selectors.next),
51
34
  goPrevious: createClicker(selectors.previous),
35
+ goNextBulk: createClicker(selectors.nextBulk),
36
+ goPreviousBulk: createClicker(selectors.previousBulk),
52
37
  goToFirst: createClicker(selectors.first)
53
38
  };
54
39
  },
@@ -9,10 +9,6 @@ export declare const scrollRightHeaderRDG: (context: TableContext) => Promise<st
9
9
  * changing during pagination/scrolling.
10
10
  */
11
11
  export declare const rdgGetCellLocator: ({ row, columnIndex }: any) => any;
12
- /**
13
- * Scrolls virtualized columns into view before reading.
14
- */
15
- export declare const rdgCellNavigation: ({ root, page, index }: any) => Promise<void>;
16
12
  /**
17
13
  * Scrolls the grid vertically to load more virtualized rows.
18
14
  */
@@ -27,7 +23,6 @@ export declare const rdgNavigation: {
27
23
  export declare const RDGStrategies: {
28
24
  header: (context: TableContext) => Promise<string[]>;
29
25
  getCellLocator: ({ row, columnIndex }: any) => any;
30
- cellNavigation: ({ root, page, index }: any) => Promise<void>;
31
26
  navigation: {
32
27
  goRight: ({ root, page }: any) => Promise<void>;
33
28
  goLeft: ({ root, page }: any) => Promise<void>;
@@ -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.RDGStrategies = exports.rdgNavigation = exports.rdgPaginationStrategy = exports.rdgCellNavigation = exports.rdgGetCellLocator = exports.scrollRightHeaderRDG = void 0;
12
+ exports.RDGStrategies = exports.rdgNavigation = exports.rdgPaginationStrategy = exports.rdgGetCellLocator = exports.scrollRightHeaderRDG = void 0;
13
13
  /**
14
14
  * Scrolls the grid horizontally to collect all column headers.
15
15
  * Handles empty headers by labeling them (e.g. "Checkbox").
@@ -64,22 +64,6 @@ const rdgGetCellLocator = ({ row, columnIndex }) => {
64
64
  return row.locator(`[role="gridcell"][aria-colindex="${ariaColIndex}"]`);
65
65
  };
66
66
  exports.rdgGetCellLocator = rdgGetCellLocator;
67
- /**
68
- * Scrolls virtualized columns into view before reading.
69
- */
70
- const rdgCellNavigation = (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, page, index }) {
71
- // Check if the column header is visible and scroll horizontally if needed
72
- const headerCell = root.locator(`[role="columnheader"][aria-colindex="${index + 1}"]`);
73
- const isVisible = yield headerCell.isVisible().catch(() => false);
74
- if (!isVisible) {
75
- const estimatedScroll = index * 150;
76
- yield root.evaluate((el, scrollAmount) => {
77
- el.scrollLeft = scrollAmount;
78
- }, estimatedScroll);
79
- yield page.waitForTimeout(300);
80
- }
81
- });
82
- exports.rdgCellNavigation = rdgCellNavigation;
83
67
  /**
84
68
  * Scrolls the grid vertically to load more virtualized rows.
85
69
  */
@@ -136,7 +120,6 @@ exports.rdgNavigation = {
136
120
  exports.RDGStrategies = {
137
121
  header: exports.scrollRightHeaderRDG,
138
122
  getCellLocator: exports.rdgGetCellLocator,
139
- cellNavigation: exports.rdgCellNavigation,
140
123
  navigation: exports.rdgNavigation,
141
124
  pagination: exports.rdgPaginationStrategy
142
125
  };
@@ -23,44 +23,26 @@ exports.SortingStrategies = {
23
23
  return {
24
24
  doSort(_a) {
25
25
  return __awaiter(this, arguments, void 0, function* ({ columnName, direction, context }) {
26
- const { resolve, config, root } = context;
27
- const headerLoc = resolve(config.headerSelector, root);
28
- const headers = yield headerLoc.all();
29
- const headerTexts = yield Promise.all(headers.map(h => h.innerText()));
30
- const columnIndex = headerTexts.findIndex(text => text.trim() === columnName);
31
- if (columnIndex === -1) {
32
- throw new Error(`[AriaSort] Header with text "${columnName}" not found.`);
33
- }
34
- const targetHeader = headers[columnIndex];
35
- // Click repeatedly to cycle through sort states
36
- for (let i = 0; i < 3; i++) { // Max 3 clicks to prevent infinite loops (none -> asc -> desc)
37
- const currentState = yield targetHeader.getAttribute('aria-sort');
38
- const mappedState = currentState === 'ascending' ? 'asc' : currentState === 'descending' ? 'desc' : 'none';
39
- if (mappedState === direction) {
40
- return; // Desired state achieved
41
- }
42
- yield targetHeader.click();
43
- }
44
- throw new Error(`[AriaSort] Could not achieve sort direction "${direction}" for column "${columnName}" after 3 clicks.`);
26
+ // getHeaderCell is always present on TableContext after table is initialized
27
+ const targetHeader = yield context.getHeaderCell(columnName);
28
+ // The table engine handles verify-and-retry. We only provide the trigger here.
29
+ yield targetHeader.click();
45
30
  });
46
31
  },
47
32
  getSortState(_a) {
48
33
  return __awaiter(this, arguments, void 0, function* ({ columnName, context }) {
49
- const { resolve, config, root } = context;
50
- const headerLoc = resolve(config.headerSelector, root);
51
- const headers = yield headerLoc.all();
52
- const headerTexts = yield Promise.all(headers.map(h => h.innerText()));
53
- const columnIndex = headerTexts.findIndex(text => text.trim() === columnName);
54
- if (columnIndex === -1) {
55
- return 'none'; // Header not found, so it's not sorted
34
+ try {
35
+ const targetHeader = yield context.getHeaderCell(columnName);
36
+ const ariaSort = yield targetHeader.getAttribute('aria-sort');
37
+ if (ariaSort === 'ascending')
38
+ return 'asc';
39
+ if (ariaSort === 'descending')
40
+ return 'desc';
41
+ return 'none';
42
+ }
43
+ catch (_b) {
44
+ return 'none'; // Header not found, treat as unsorted
56
45
  }
57
- const targetHeader = headers[columnIndex];
58
- const ariaSort = yield targetHeader.getAttribute('aria-sort');
59
- if (ariaSort === 'ascending')
60
- return 'asc';
61
- if (ariaSort === 'descending')
62
- return 'desc';
63
- return 'none';
64
46
  });
65
47
  },
66
48
  };