@rickcedwhat/playwright-smart-table 2.1.3 → 2.3.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 +151 -61
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/strategies/index.d.ts +2 -15
- package/dist/strategies/index.js +16 -109
- package/dist/strategies/pagination.d.ts +32 -0
- package/dist/strategies/pagination.js +72 -0
- package/dist/strategies/sorting.d.ts +12 -0
- package/dist/strategies/sorting.js +68 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +67 -3
- package/dist/types.d.ts +61 -3
- package/dist/useTable.d.ts +37 -1
- package/dist/useTable.js +214 -9
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,12 +20,12 @@ For standard HTML tables (`<table>`, `<tr>`, `<td>`), the library works out of t
|
|
|
20
20
|
|
|
21
21
|
<!-- embed: quick-start -->
|
|
22
22
|
```typescript
|
|
23
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
23
24
|
const table = useTable(page.locator('#example'), {
|
|
24
25
|
headerSelector: 'thead th' // Override for this specific site
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
//
|
|
28
|
-
// If Airi is on Page 2, it handles pagination automatically.
|
|
28
|
+
// Find the row with Name="Airi Satou", then get the Position cell
|
|
29
29
|
const row = await table.getByRow({ Name: 'Airi Satou' });
|
|
30
30
|
|
|
31
31
|
await expect(row.getCell('Position')).toHaveText('Accountant');
|
|
@@ -43,14 +43,15 @@ The `SmartRow` is the core power of this library. Unlike a standard Playwright `
|
|
|
43
43
|
|
|
44
44
|
<!-- embed: smart-row -->
|
|
45
45
|
```typescript
|
|
46
|
-
//
|
|
46
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
47
|
+
|
|
48
|
+
// Get SmartRow via getByRow
|
|
47
49
|
const row = await table.getByRow({ Name: 'Airi Satou' });
|
|
48
50
|
|
|
49
|
-
//
|
|
50
|
-
// ✅ Good: Resilient to column reordering
|
|
51
|
+
// Interact with cell using column name (resilient to column reordering)
|
|
51
52
|
await row.getCell('Position').click();
|
|
52
53
|
|
|
53
|
-
//
|
|
54
|
+
// Extract row data as JSON
|
|
54
55
|
const data = await row.toJSON();
|
|
55
56
|
console.log(data);
|
|
56
57
|
// { Name: "Airi Satou", Position: "Accountant", ... }
|
|
@@ -72,6 +73,7 @@ For tables that span multiple pages, configure a pagination strategy:
|
|
|
72
73
|
|
|
73
74
|
<!-- embed: pagination -->
|
|
74
75
|
```typescript
|
|
76
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
75
77
|
const table = useTable(page.locator('#example'), {
|
|
76
78
|
rowSelector: 'tbody tr',
|
|
77
79
|
headerSelector: 'thead th',
|
|
@@ -97,6 +99,7 @@ Enable debug logging to see exactly what the library is doing:
|
|
|
97
99
|
|
|
98
100
|
<!-- embed: advanced-debug -->
|
|
99
101
|
```typescript
|
|
102
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
100
103
|
const table = useTable(page.locator('#example'), {
|
|
101
104
|
headerSelector: 'thead th',
|
|
102
105
|
debug: true // Enables verbose logging of internal operations
|
|
@@ -115,6 +118,7 @@ If your tests navigate deep into a paginated table, use `.reset()` to return to
|
|
|
115
118
|
|
|
116
119
|
<!-- embed: advanced-reset -->
|
|
117
120
|
```typescript
|
|
121
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
118
122
|
// Navigate deep into the table by searching for a row on a later page
|
|
119
123
|
try {
|
|
120
124
|
await table.getByRow({ Name: 'Angelica Ramos' });
|
|
@@ -135,6 +139,7 @@ Efficiently extract all values from a specific column:
|
|
|
135
139
|
|
|
136
140
|
<!-- embed: advanced-column-scan -->
|
|
137
141
|
```typescript
|
|
142
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
138
143
|
// Quickly grab all text values from the "Office" column
|
|
139
144
|
const offices = await table.getColumnValues('Office');
|
|
140
145
|
expect(offices).toContain('Tokyo');
|
|
@@ -142,6 +147,63 @@ expect(offices.length).toBeGreaterThan(0);
|
|
|
142
147
|
```
|
|
143
148
|
<!-- /embed: advanced-column-scan -->
|
|
144
149
|
|
|
150
|
+
### Filling Row Data
|
|
151
|
+
|
|
152
|
+
Use `fill()` to intelligently populate form fields in a table row. The method automatically detects input types (text inputs, selects, checkboxes, contenteditable divs) and fills them appropriately.
|
|
153
|
+
|
|
154
|
+
<!-- embed: fill-basic -->
|
|
155
|
+
```typescript
|
|
156
|
+
// Find a row and fill it with new data
|
|
157
|
+
const row = await table.getByRow({ ID: '1' });
|
|
158
|
+
|
|
159
|
+
await row.fill({
|
|
160
|
+
Name: 'John Updated',
|
|
161
|
+
Status: 'Inactive',
|
|
162
|
+
Active: false,
|
|
163
|
+
Notes: 'Updated notes here'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Verify the values were filled correctly
|
|
167
|
+
await expect(row.getCell('Name').locator('input')).toHaveValue('John Updated');
|
|
168
|
+
await expect(row.getCell('Status').locator('select')).toHaveValue('Inactive');
|
|
169
|
+
await expect(row.getCell('Active').locator('input[type="checkbox"]')).not.toBeChecked();
|
|
170
|
+
await expect(row.getCell('Notes').locator('textarea')).toHaveValue('Updated notes here');
|
|
171
|
+
```
|
|
172
|
+
<!-- /embed: fill-basic -->
|
|
173
|
+
|
|
174
|
+
**Auto-detection supports:**
|
|
175
|
+
- Text inputs (`input[type="text"]`, `textarea`)
|
|
176
|
+
- Select dropdowns (`select`)
|
|
177
|
+
- Checkboxes/radios (`input[type="checkbox"]`, `input[type="radio"]`, `[role="checkbox"]`)
|
|
178
|
+
- Contenteditable divs (`[contenteditable="true"]`)
|
|
179
|
+
|
|
180
|
+
**Custom input mappers:**
|
|
181
|
+
|
|
182
|
+
For edge cases where auto-detection doesn't work (e.g., custom components, multiple inputs in a cell), use per-column mappers:
|
|
183
|
+
|
|
184
|
+
<!-- embed: fill-custom-mappers -->
|
|
185
|
+
```typescript
|
|
186
|
+
const row = await table.getByRow({ ID: '1' });
|
|
187
|
+
|
|
188
|
+
// Use custom input mappers for specific columns
|
|
189
|
+
await row.fill({
|
|
190
|
+
Name: 'John Updated',
|
|
191
|
+
Status: 'Inactive'
|
|
192
|
+
}, {
|
|
193
|
+
inputMappers: {
|
|
194
|
+
// Name column has multiple inputs - target the primary one
|
|
195
|
+
Name: (cell) => cell.locator('.primary-input'),
|
|
196
|
+
// Status uses standard select, but we could customize if needed
|
|
197
|
+
Status: (cell) => cell.locator('select')
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Verify the values
|
|
202
|
+
await expect(row.getCell('Name').locator('.primary-input')).toHaveValue('John Updated');
|
|
203
|
+
await expect(row.getCell('Status').locator('select')).toHaveValue('Inactive');
|
|
204
|
+
```
|
|
205
|
+
<!-- /embed: fill-custom-mappers -->
|
|
206
|
+
|
|
145
207
|
### Transforming Column Headers
|
|
146
208
|
|
|
147
209
|
Use `headerTransformer` to normalize or rename column headers. This is especially useful for tables with empty headers, inconsistent formatting, or when you want to use cleaner names in your tests.
|
|
@@ -152,6 +214,7 @@ Tables with empty header cells (like Material UI DataGrids) get auto-assigned na
|
|
|
152
214
|
|
|
153
215
|
<!-- embed: header-transformer -->
|
|
154
216
|
```typescript
|
|
217
|
+
// Example from: https://mui.com/material-ui/react-table/
|
|
155
218
|
const table = useTable(page.locator('.MuiDataGrid-root').first(), {
|
|
156
219
|
rowSelector: '.MuiDataGrid-row',
|
|
157
220
|
headerSelector: '.MuiDataGrid-columnHeader',
|
|
@@ -186,6 +249,7 @@ Clean up inconsistent column names (extra spaces, inconsistent casing):
|
|
|
186
249
|
|
|
187
250
|
<!-- embed: header-transformer-normalize -->
|
|
188
251
|
```typescript
|
|
252
|
+
// Example from: https://the-internet.herokuapp.com/tables
|
|
189
253
|
const table = useTable(page.locator('#table1'), {
|
|
190
254
|
// Normalize column names: remove extra spaces, handle inconsistent casing
|
|
191
255
|
headerTransformer: ({ text }) => {
|
|
@@ -215,10 +279,19 @@ await expect(row.getCell("Email")).toHaveText("jdoe@hotmail.com");
|
|
|
215
279
|
- ✅ Returns `SmartRow` if exactly one match
|
|
216
280
|
- ❌ Throws error if multiple matches (ambiguous query)
|
|
217
281
|
- 👻 Returns sentinel locator if no match (allows `.not.toBeVisible()` assertions)
|
|
218
|
-
- 🔄 Auto-paginates if row isn't on current page
|
|
282
|
+
- 🔄 Auto-paginates if row isn't on current page (when `maxPages > 1` and pagination strategy is configured)
|
|
283
|
+
|
|
284
|
+
**Type Signature:**
|
|
285
|
+
```typescript
|
|
286
|
+
getByRow: <T extends { asJSON?: boolean }>(
|
|
287
|
+
filters: Record<string, string | RegExp | number>,
|
|
288
|
+
options?: { exact?: boolean, maxPages?: number } & T
|
|
289
|
+
) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;
|
|
290
|
+
```
|
|
219
291
|
|
|
220
292
|
<!-- embed: get-by-row -->
|
|
221
293
|
```typescript
|
|
294
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
222
295
|
// Find a row where Name is "Airi Satou" AND Office is "Tokyo"
|
|
223
296
|
const row = await table.getByRow({ Name: "Airi Satou", Office: "Tokyo" });
|
|
224
297
|
await expect(row).toBeVisible();
|
|
@@ -228,37 +301,17 @@ await expect(await table.getByRow({ Name: "Ghost User" })).not.toBeVisible();
|
|
|
228
301
|
```
|
|
229
302
|
<!-- /embed: get-by-row -->
|
|
230
303
|
|
|
231
|
-
|
|
232
|
-
<!-- embed
|
|
304
|
+
Get row data as JSON:
|
|
305
|
+
<!-- embed: get-by-row-json -->
|
|
233
306
|
```typescript
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
getByRow: <T extends { asJSON?: boolean }>(
|
|
239
|
-
filters: Record<string, string | RegExp | number>,
|
|
240
|
-
options?: { exact?: boolean, maxPages?: number } & T
|
|
241
|
-
) => Promise<T['asJSON'] extends true ? Record<string, string> : SmartRow>;
|
|
242
|
-
|
|
243
|
-
getAllRows: <T extends { asJSON?: boolean }>(
|
|
244
|
-
options?: { filter?: Record<string, any>, exact?: boolean } & T
|
|
245
|
-
) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
|
|
246
|
-
|
|
247
|
-
generateConfigPrompt: (options?: PromptOptions) => Promise<void>;
|
|
248
|
-
generateStrategyPrompt: (options?: PromptOptions) => Promise<void>;
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Resets the table state (clears cache, flags) and invokes the onReset strategy.
|
|
252
|
-
*/
|
|
253
|
-
reset: () => Promise<void>;
|
|
307
|
+
// Get row data directly as JSON object
|
|
308
|
+
const data = await table.getByRow({ Name: 'Airi Satou' }, { asJSON: true });
|
|
309
|
+
// Returns: { Name: "Airi Satou", Position: "Accountant", Office: "Tokyo", ... }
|
|
254
310
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
*/
|
|
258
|
-
getColumnValues: <V = string>(column: string, options?: { mapper?: (cell: Locator) => Promise<V> | V, maxPages?: number }) => Promise<V[]>;
|
|
259
|
-
}
|
|
311
|
+
expect(data).toHaveProperty('Name', 'Airi Satou');
|
|
312
|
+
expect(data).toHaveProperty('Position');
|
|
260
313
|
```
|
|
261
|
-
<!-- /embed
|
|
314
|
+
<!-- /embed: get-by-row-json -->
|
|
262
315
|
|
|
263
316
|
#### <a name="getallrows"></a>`getAllRows(options?)`
|
|
264
317
|
|
|
@@ -266,8 +319,16 @@ export interface TableResult {
|
|
|
266
319
|
|
|
267
320
|
**Best for:** Checking existence, validating sort order, bulk data extraction.
|
|
268
321
|
|
|
322
|
+
**Type Signature:**
|
|
323
|
+
```typescript
|
|
324
|
+
getAllRows: <T extends { asJSON?: boolean }>(
|
|
325
|
+
options?: { filter?: Record<string, any>, exact?: boolean } & T
|
|
326
|
+
) => Promise<T['asJSON'] extends true ? Record<string, string>[] : SmartRow[]>;
|
|
327
|
+
```
|
|
328
|
+
|
|
269
329
|
<!-- embed: get-all-rows -->
|
|
270
330
|
```typescript
|
|
331
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
271
332
|
// 1. Get ALL rows on the current page
|
|
272
333
|
const allRows = await table.getAllRows();
|
|
273
334
|
expect(allRows.length).toBeGreaterThan(0);
|
|
@@ -286,24 +347,6 @@ expect(data[0]).toHaveProperty('Name');
|
|
|
286
347
|
```
|
|
287
348
|
<!-- /embed: get-all-rows -->
|
|
288
349
|
|
|
289
|
-
#### <a name="getcolumnvalues"></a>`getColumnValues(column, options?)`
|
|
290
|
-
|
|
291
|
-
Scans a specific column across all pages and returns values. Supports custom mappers for extracting non-text data.
|
|
292
|
-
|
|
293
|
-
**Additional Examples:**
|
|
294
|
-
|
|
295
|
-
Get row data as JSON:
|
|
296
|
-
<!-- embed: get-by-row-json -->
|
|
297
|
-
```typescript
|
|
298
|
-
// Get row data directly as JSON object
|
|
299
|
-
const data = await table.getByRow({ Name: 'Airi Satou' }, { asJSON: true });
|
|
300
|
-
// Returns: { Name: "Airi Satou", Position: "Accountant", Office: "Tokyo", ... }
|
|
301
|
-
|
|
302
|
-
expect(data).toHaveProperty('Name', 'Airi Satou');
|
|
303
|
-
expect(data).toHaveProperty('Position');
|
|
304
|
-
```
|
|
305
|
-
<!-- /embed: get-by-row-json -->
|
|
306
|
-
|
|
307
350
|
Filter rows with exact match:
|
|
308
351
|
<!-- embed: get-all-rows-exact -->
|
|
309
352
|
```typescript
|
|
@@ -317,7 +360,33 @@ expect(exactMatches.length).toBeGreaterThan(0);
|
|
|
317
360
|
```
|
|
318
361
|
<!-- /embed: get-all-rows-exact -->
|
|
319
362
|
|
|
320
|
-
|
|
363
|
+
#### <a name="getcolumnvalues"></a>`getColumnValues(column, options?)`
|
|
364
|
+
|
|
365
|
+
Scans a specific column across all pages and returns values. Supports custom mappers for extracting non-text data.
|
|
366
|
+
|
|
367
|
+
**Type Signature:**
|
|
368
|
+
```typescript
|
|
369
|
+
getColumnValues: <V = string>(
|
|
370
|
+
column: string,
|
|
371
|
+
options?: {
|
|
372
|
+
mapper?: (cell: Locator) => Promise<V> | V,
|
|
373
|
+
maxPages?: number
|
|
374
|
+
}
|
|
375
|
+
) => Promise<V[]>;
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Basic usage:
|
|
379
|
+
<!-- embed: advanced-column-scan -->
|
|
380
|
+
```typescript
|
|
381
|
+
// Example from: https://datatables.net/examples/data_sources/dom
|
|
382
|
+
// Quickly grab all text values from the "Office" column
|
|
383
|
+
const offices = await table.getColumnValues('Office');
|
|
384
|
+
expect(offices).toContain('Tokyo');
|
|
385
|
+
expect(offices.length).toBeGreaterThan(0);
|
|
386
|
+
```
|
|
387
|
+
<!-- /embed: advanced-column-scan -->
|
|
388
|
+
|
|
389
|
+
With custom mapper:
|
|
321
390
|
<!-- embed: advanced-column-scan-mapper -->
|
|
322
391
|
```typescript
|
|
323
392
|
// Extract numeric values from a column
|
|
@@ -334,18 +403,33 @@ expect(ages.length).toBeGreaterThan(0);
|
|
|
334
403
|
```
|
|
335
404
|
<!-- /embed: advanced-column-scan-mapper -->
|
|
336
405
|
|
|
337
|
-
#### <a name="reset"></a>`reset()`
|
|
338
|
-
|
|
339
|
-
Resets table state (clears cache, pagination flags) and invokes the `onReset` strategy to return to the first page.
|
|
340
|
-
|
|
341
406
|
#### <a name="getheaders"></a>`getHeaders()`
|
|
342
407
|
|
|
343
408
|
Returns an array of all column names in the table.
|
|
344
409
|
|
|
410
|
+
**Type Signature:**
|
|
411
|
+
```typescript
|
|
412
|
+
getHeaders: () => Promise<string[]>;
|
|
413
|
+
```
|
|
414
|
+
|
|
345
415
|
#### <a name="getheadercell"></a>`getHeaderCell(columnName)`
|
|
346
416
|
|
|
347
417
|
Returns a Playwright `Locator` for the specified header cell.
|
|
348
418
|
|
|
419
|
+
**Type Signature:**
|
|
420
|
+
```typescript
|
|
421
|
+
getHeaderCell: (columnName: string) => Promise<Locator>;
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### <a name="reset"></a>`reset()`
|
|
425
|
+
|
|
426
|
+
Resets table state (clears cache, pagination flags) and invokes the `onReset` strategy to return to the first page.
|
|
427
|
+
|
|
428
|
+
**Type Signature:**
|
|
429
|
+
```typescript
|
|
430
|
+
reset: () => Promise<void>;
|
|
431
|
+
```
|
|
432
|
+
|
|
349
433
|
---
|
|
350
434
|
|
|
351
435
|
## 🧩 Pagination Strategies
|
|
@@ -396,7 +480,7 @@ export type PaginationStrategy = (context: TableContext) => Promise<boolean>;
|
|
|
396
480
|
```typescript
|
|
397
481
|
export interface TableContext {
|
|
398
482
|
root: Locator;
|
|
399
|
-
config:
|
|
483
|
+
config: FinalTableConfig;
|
|
400
484
|
page: Page;
|
|
401
485
|
resolve: (selector: Selector, parent: Locator | Page) => Locator;
|
|
402
486
|
}
|
|
@@ -429,7 +513,7 @@ await table.generateStrategyPrompt({ output: 'console' });
|
|
|
429
513
|
export interface PromptOptions {
|
|
430
514
|
/**
|
|
431
515
|
* Output Strategy:
|
|
432
|
-
* - 'error': Throws an error with the prompt (
|
|
516
|
+
* - 'error': Throws an error with the prompt (useful for platforms that capture error output cleanly).
|
|
433
517
|
* - 'console': Standard console logs (Default).
|
|
434
518
|
*/
|
|
435
519
|
output?: 'console' | 'error';
|
|
@@ -450,9 +534,13 @@ A `SmartRow` extends Playwright's `Locator` with table-aware methods.
|
|
|
450
534
|
|
|
451
535
|
<!-- embed-type: SmartRow -->
|
|
452
536
|
```typescript
|
|
453
|
-
export type SmartRow = Locator & {
|
|
537
|
+
export type SmartRow = Omit<Locator, 'fill'> & {
|
|
454
538
|
getCell(column: string): Locator;
|
|
455
539
|
toJSON(): Promise<Record<string, string>>;
|
|
540
|
+
/**
|
|
541
|
+
* Fills the row with data. Automatically detects input types (text input, select, checkbox, etc.).
|
|
542
|
+
*/
|
|
543
|
+
fill: (data: Record<string, any>, options?: FillOptions) => Promise<void>;
|
|
456
544
|
};
|
|
457
545
|
```
|
|
458
546
|
<!-- /embed-type: SmartRow -->
|
|
@@ -460,6 +548,7 @@ export type SmartRow = Locator & {
|
|
|
460
548
|
**Methods:**
|
|
461
549
|
- `getCell(column: string)`: Returns a `Locator` for the specified cell in this row
|
|
462
550
|
- `toJSON()`: Extracts all cell data as a key-value object
|
|
551
|
+
- `fill(data, options?)`: Intelligently fills form fields in the row. Automatically detects input types or use `inputMappers` for custom control
|
|
463
552
|
|
|
464
553
|
All standard Playwright `Locator` methods (`.click()`, `.isVisible()`, `.textContent()`, etc.) are also available.
|
|
465
554
|
|
|
@@ -474,6 +563,7 @@ export interface TableConfig {
|
|
|
474
563
|
headerSelector?: Selector;
|
|
475
564
|
cellSelector?: Selector;
|
|
476
565
|
pagination?: PaginationStrategy;
|
|
566
|
+
sorting?: SortingStrategy;
|
|
477
567
|
maxPages?: number;
|
|
478
568
|
/**
|
|
479
569
|
* Hook to rename columns dynamically.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,4 +16,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./useTable"), exports);
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
|
-
__exportStar(require("./strategies"), exports);
|
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
/**
|
|
4
|
-
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
5
|
-
*/
|
|
6
|
-
clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
7
|
-
/**
|
|
8
|
-
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
9
|
-
*/
|
|
10
|
-
clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
11
|
-
/**
|
|
12
|
-
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
13
|
-
*/
|
|
14
|
-
infiniteScroll: (timeout?: number) => PaginationStrategy;
|
|
15
|
-
};
|
|
1
|
+
export * from './pagination';
|
|
2
|
+
export * from './sorting';
|
package/dist/strategies/index.js
CHANGED
|
@@ -1,112 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.TableStrategies = void 0;
|
|
13
|
-
/**
|
|
14
|
-
* Internal helper to wait for a condition to be met.
|
|
15
|
-
* Replaces the dependency on 'expect(...).toPass()' to ensure compatibility
|
|
16
|
-
* with environments like QA Wolf where 'expect' is not globally available.
|
|
17
|
-
*/
|
|
18
|
-
const waitForCondition = (predicate, timeout, page // Context page for pauses
|
|
19
|
-
) => __awaiter(void 0, void 0, void 0, function* () {
|
|
20
|
-
const startTime = Date.now();
|
|
21
|
-
while (Date.now() - startTime < timeout) {
|
|
22
|
-
if (yield predicate()) {
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
// Wait 100ms before next check (Standard Polling)
|
|
26
|
-
yield page.waitForTimeout(100).catch(() => new Promise(r => setTimeout(r, 100)));
|
|
27
|
-
}
|
|
28
|
-
return false;
|
|
29
|
-
});
|
|
30
|
-
exports.TableStrategies = {
|
|
31
|
-
/**
|
|
32
|
-
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
33
|
-
*/
|
|
34
|
-
clickNext: (nextButtonSelector, timeout = 5000) => {
|
|
35
|
-
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
36
|
-
const nextBtn = resolve(nextButtonSelector, root).first();
|
|
37
|
-
// Debug log (can be verbose, maybe useful for debugging only)
|
|
38
|
-
// console.log(`[Strategy: clickNext] Checking button...`);
|
|
39
|
-
// Check if button exists/enabled before clicking.
|
|
40
|
-
// We do NOT wait here because if the button isn't visible/enabled,
|
|
41
|
-
// we assume we reached the last page.
|
|
42
|
-
if (!(yield nextBtn.isVisible())) {
|
|
43
|
-
console.log(`[Strategy: clickNext] Button not visible. Stopping pagination.`);
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if (!(yield nextBtn.isEnabled())) {
|
|
47
|
-
console.log(`[Strategy: clickNext] Button disabled. Stopping pagination.`);
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
// 1. Snapshot current state
|
|
51
|
-
const firstRow = resolve(config.rowSelector, root).first();
|
|
52
|
-
const oldText = yield firstRow.innerText().catch(() => "");
|
|
53
|
-
// 2. Click
|
|
54
|
-
console.log(`[Strategy: clickNext] Clicking next button...`);
|
|
55
|
-
try {
|
|
56
|
-
yield nextBtn.click({ timeout: 2000 });
|
|
57
|
-
}
|
|
58
|
-
catch (e) {
|
|
59
|
-
console.warn(`[Strategy: clickNext] Click failed (blocked or detached): ${e}`);
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
// 3. Smart Wait (Polling)
|
|
63
|
-
const success = yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
64
|
-
const newText = yield firstRow.innerText().catch(() => "");
|
|
65
|
-
return newText !== oldText;
|
|
66
|
-
}), timeout, page);
|
|
67
|
-
if (!success) {
|
|
68
|
-
console.warn(`[Strategy: clickNext] Warning: Table content did not change after clicking Next.`);
|
|
69
|
-
}
|
|
70
|
-
return success;
|
|
71
|
-
});
|
|
72
|
-
},
|
|
73
|
-
/**
|
|
74
|
-
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
75
|
-
*/
|
|
76
|
-
clickLoadMore: (buttonSelector, timeout = 5000) => {
|
|
77
|
-
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
78
|
-
const loadMoreBtn = resolve(buttonSelector, root).first();
|
|
79
|
-
if (!(yield loadMoreBtn.isVisible()) || !(yield loadMoreBtn.isEnabled())) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
// 1. Snapshot count
|
|
83
|
-
const rows = resolve(config.rowSelector, root);
|
|
84
|
-
const oldCount = yield rows.count();
|
|
85
|
-
// 2. Click
|
|
86
|
-
yield loadMoreBtn.click();
|
|
87
|
-
// 3. Smart Wait (Polling)
|
|
88
|
-
return yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
-
const newCount = yield rows.count();
|
|
90
|
-
return newCount > oldCount;
|
|
91
|
-
}), timeout, page);
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
/**
|
|
95
|
-
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
96
|
-
*/
|
|
97
|
-
infiniteScroll: (timeout = 5000) => {
|
|
98
|
-
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
99
|
-
const rows = resolve(config.rowSelector, root);
|
|
100
|
-
const oldCount = yield rows.count();
|
|
101
|
-
if (oldCount === 0)
|
|
102
|
-
return false;
|
|
103
|
-
// 1. Trigger Scroll
|
|
104
|
-
yield rows.last().scrollIntoViewIfNeeded();
|
|
105
|
-
// 2. Smart Wait (Polling)
|
|
106
|
-
return yield waitForCondition(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
107
|
-
const newCount = yield rows.count();
|
|
108
|
-
return newCount > oldCount;
|
|
109
|
-
}), timeout, page);
|
|
110
|
-
});
|
|
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]; } };
|
|
111
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);
|
|
112
15
|
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// src/strategies/index.ts
|
|
18
|
+
__exportStar(require("./pagination"), exports);
|
|
19
|
+
__exportStar(require("./sorting"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PaginationStrategy, Selector } from '../types';
|
|
2
|
+
export declare const PaginationStrategies: {
|
|
3
|
+
/**
|
|
4
|
+
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
5
|
+
*/
|
|
6
|
+
clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
7
|
+
/**
|
|
8
|
+
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
9
|
+
*/
|
|
10
|
+
clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
11
|
+
/**
|
|
12
|
+
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
13
|
+
*/
|
|
14
|
+
infiniteScroll: (timeout?: number) => PaginationStrategy;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use `PaginationStrategies` instead. This alias will be removed in a future major version.
|
|
18
|
+
*/
|
|
19
|
+
export declare const TableStrategies: {
|
|
20
|
+
/**
|
|
21
|
+
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
22
|
+
*/
|
|
23
|
+
clickNext: (nextButtonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
24
|
+
/**
|
|
25
|
+
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
26
|
+
*/
|
|
27
|
+
clickLoadMore: (buttonSelector: Selector, timeout?: number) => PaginationStrategy;
|
|
28
|
+
/**
|
|
29
|
+
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
30
|
+
*/
|
|
31
|
+
infiniteScroll: (timeout?: number) => PaginationStrategy;
|
|
32
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TableStrategies = exports.PaginationStrategies = void 0;
|
|
13
|
+
const utils_1 = require("../utils");
|
|
14
|
+
exports.PaginationStrategies = {
|
|
15
|
+
/**
|
|
16
|
+
* Strategy: Clicks a "Next" button and waits for the first row of data to change.
|
|
17
|
+
*/
|
|
18
|
+
clickNext: (nextButtonSelector, timeout = 5000) => {
|
|
19
|
+
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
20
|
+
const nextBtn = resolve(nextButtonSelector, root).first();
|
|
21
|
+
if (!(yield nextBtn.isVisible()) || !(yield nextBtn.isEnabled())) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const firstRow = resolve(config.rowSelector, root).first();
|
|
25
|
+
const oldText = yield firstRow.innerText().catch(() => "");
|
|
26
|
+
yield nextBtn.click({ timeout: 2000 }).catch(() => { });
|
|
27
|
+
const success = yield (0, utils_1.waitForCondition)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
28
|
+
const newText = yield firstRow.innerText().catch(() => "");
|
|
29
|
+
return newText !== oldText;
|
|
30
|
+
}), timeout, page);
|
|
31
|
+
return success;
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Strategy: Clicks a "Load More" button and waits for the row count to increase.
|
|
36
|
+
*/
|
|
37
|
+
clickLoadMore: (buttonSelector, timeout = 5000) => {
|
|
38
|
+
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
39
|
+
const loadMoreBtn = resolve(buttonSelector, root).first();
|
|
40
|
+
if (!(yield loadMoreBtn.isVisible()) || !(yield loadMoreBtn.isEnabled())) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const rows = resolve(config.rowSelector, root);
|
|
44
|
+
const oldCount = yield rows.count();
|
|
45
|
+
yield loadMoreBtn.click();
|
|
46
|
+
return yield (0, utils_1.waitForCondition)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
47
|
+
const newCount = yield rows.count();
|
|
48
|
+
return newCount > oldCount;
|
|
49
|
+
}), timeout, page);
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* Strategy: Scrolls to the bottom and waits for more rows to appear.
|
|
54
|
+
*/
|
|
55
|
+
infiniteScroll: (timeout = 5000) => {
|
|
56
|
+
return (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, config, resolve, page }) {
|
|
57
|
+
const rows = resolve(config.rowSelector, root);
|
|
58
|
+
const oldCount = yield rows.count();
|
|
59
|
+
if (oldCount === 0)
|
|
60
|
+
return false;
|
|
61
|
+
yield rows.last().scrollIntoViewIfNeeded();
|
|
62
|
+
return yield (0, utils_1.waitForCondition)(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
+
const newCount = yield rows.count();
|
|
64
|
+
return newCount > oldCount;
|
|
65
|
+
}), timeout, page);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* @deprecated Use `PaginationStrategies` instead. This alias will be removed in a future major version.
|
|
71
|
+
*/
|
|
72
|
+
exports.TableStrategies = exports.PaginationStrategies;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SortingStrategy } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* A collection of pre-built sorting strategies.
|
|
4
|
+
*/
|
|
5
|
+
export declare const SortingStrategies: {
|
|
6
|
+
/**
|
|
7
|
+
* A sorting strategy that interacts with column headers based on ARIA attributes.
|
|
8
|
+
* - `doSort`: Clicks the header repeatedly until the desired `aria-sort` state is achieved.
|
|
9
|
+
* - `getSortState`: Reads the `aria-sort` attribute from the header.
|
|
10
|
+
*/
|
|
11
|
+
AriaSort: () => SortingStrategy;
|
|
12
|
+
};
|