@cerios/playwright-table 2.0.2 → 2.1.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 +682 -621
- package/dist/index.d.mts +1913 -417
- package/dist/index.d.ts +1913 -417
- package/dist/index.js +5429 -1105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5417 -1076
- package/dist/index.mjs.map +1 -1
- package/package.json +32 -19
package/dist/index.d.mts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { Locator } from
|
|
2
|
-
|
|
1
|
+
import { Expect, ExpectMatcherState, Locator, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, Selectors, TestType } from "@playwright/test";
|
|
3
2
|
/**
|
|
4
3
|
* Enum representing the possible types of content that can be extracted from a table cell.
|
|
5
4
|
*
|
|
6
|
-
* - `TextContent`:
|
|
7
|
-
* - `InnerText`:
|
|
5
|
+
* - `TextContent`: all text in the DOM of the cell, also the text of hidden elements.
|
|
6
|
+
* - `InnerText`: the text as it is shown, without hidden text and with CSS `text-transform`.
|
|
7
|
+
*
|
|
8
|
+
* `TableLocator` normalizes the text either way (whitespace collapsed, icon glyphs and `ignore`d elements
|
|
9
|
+
* left out), unless you ask for raw text. `PlaywrightTable` trims it.
|
|
8
10
|
*/
|
|
9
|
-
declare enum CellContentType {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
export declare enum CellContentType {
|
|
12
|
+
TextContent = "textContent",
|
|
13
|
+
InnerText = "innerText"
|
|
12
14
|
}
|
|
13
|
-
|
|
14
15
|
type PollingOptions = {
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
timeout?: number;
|
|
17
|
+
interval?: number;
|
|
17
18
|
};
|
|
18
|
-
|
|
19
19
|
/**
|
|
20
20
|
* Represents a single cell value within a table row.
|
|
21
21
|
*
|
|
@@ -23,7 +23,7 @@ type PollingOptions = {
|
|
|
23
23
|
* A `Cell` is a string that holds the textual content of a table cell,
|
|
24
24
|
* whether in the header or body of the table.
|
|
25
25
|
*/
|
|
26
|
-
type Cell = string;
|
|
26
|
+
export type Cell = string;
|
|
27
27
|
/**
|
|
28
28
|
* Represents a row in the table body, defined as an array of `Cell` objects.
|
|
29
29
|
*
|
|
@@ -32,7 +32,7 @@ type Cell = string;
|
|
|
32
32
|
*
|
|
33
33
|
* @see Cell
|
|
34
34
|
*/
|
|
35
|
-
type BodyRow = Cell[];
|
|
35
|
+
export type BodyRow = Cell[];
|
|
36
36
|
/**
|
|
37
37
|
* Represents a row in the table header, defined as an array of `Cell` objects.
|
|
38
38
|
*
|
|
@@ -41,8 +41,7 @@ type BodyRow = Cell[];
|
|
|
41
41
|
*
|
|
42
42
|
* @see Cell
|
|
43
43
|
*/
|
|
44
|
-
type HeaderRow = Cell[];
|
|
45
|
-
|
|
44
|
+
export type HeaderRow = Cell[];
|
|
46
45
|
/**
|
|
47
46
|
* Options for configuring a table header row.
|
|
48
47
|
*
|
|
@@ -52,19 +51,20 @@ type HeaderRow = Cell[];
|
|
|
52
51
|
* @property colspan - Configuration for column spanning in the header row.
|
|
53
52
|
* @property colspan.enabled - If true, enables column spanning.
|
|
54
53
|
* @property colspan.suffix - If true, appends a suffix to spanned columns.
|
|
54
|
+
* @property normalizeWhitespace - If true, collapses line breaks, tabs and runs of whitespace to one space.
|
|
55
|
+
* @property stripIconGlyphs - If true, removes icon-font glyphs (Unicode private use characters).
|
|
55
56
|
*/
|
|
56
57
|
type HeaderRowOptions = {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
cellContentType?: CellContentType;
|
|
59
|
+
emptyCellReplacement?: boolean;
|
|
60
|
+
duplicateSuffix?: boolean;
|
|
61
|
+
colspan?: {
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
suffix?: boolean;
|
|
64
|
+
};
|
|
65
|
+
normalizeWhitespace?: boolean;
|
|
66
|
+
stripIconGlyphs?: boolean;
|
|
66
67
|
};
|
|
67
|
-
|
|
68
68
|
/**
|
|
69
69
|
* Options for loading table data, headers, and rows.
|
|
70
70
|
*
|
|
@@ -80,407 +80,1903 @@ type HeaderRowOptions = {
|
|
|
80
80
|
* };
|
|
81
81
|
* await table.getBodyRows(options);
|
|
82
82
|
*/
|
|
83
|
-
type TableOptions = {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
cellContentType?: CellContentType;
|
|
89
|
-
};
|
|
90
|
-
/** @deprecated Use `bodyRowOptions.cellContentType` instead. */
|
|
83
|
+
export type TableOptions = {
|
|
84
|
+
/** See {@link HeaderRowOptions} for available options. */
|
|
85
|
+
headerRowOptions?: HeaderRowOptions;
|
|
86
|
+
/** Options for parsing body rows. */
|
|
87
|
+
bodyRowOptions?: {
|
|
91
88
|
cellContentType?: CellContentType;
|
|
89
|
+
};
|
|
90
|
+
/** @deprecated Use `bodyRowOptions.cellContentType` instead. */
|
|
91
|
+
cellContentType?: CellContentType;
|
|
92
92
|
};
|
|
93
93
|
/**
|
|
94
94
|
* Represents a table in Playwright, providing methods to interact with its headers, rows, and cells.
|
|
95
95
|
*/
|
|
96
|
-
declare class PlaywrightTable {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
96
|
+
export declare class PlaywrightTable {
|
|
97
|
+
private readonly _tableLocator;
|
|
98
|
+
private readonly _options?;
|
|
99
|
+
private _bodyRowLocator;
|
|
100
|
+
private _bodyRowColumnSelector;
|
|
101
|
+
private _headerRowLocator;
|
|
102
|
+
private _headerColumnSelector;
|
|
103
|
+
/**
|
|
104
|
+
* Creates an instance of the table handler with customizable selectors for headers and rows.
|
|
105
|
+
*
|
|
106
|
+
* @param _tableLocator - The Playwright `Locator` pointing to the root table element.
|
|
107
|
+
* @param _options - Optional configuration for customizing header and row selectors.
|
|
108
|
+
* @param _options.header - Options for header row and columns.
|
|
109
|
+
* @param _options.header.setMainHeaderRow - (Optional) Index of the main header row if multiple header rows exist.
|
|
110
|
+
* @param _options.header.rowSelector - (Optional) CSS selector for locating header rows. Defaults to `"thead>tr"`.
|
|
111
|
+
* @param _options.header.columnSelector - (Optional) CSS selector for locating header columns. Defaults to `"th"`.
|
|
112
|
+
* @param _options.row - Options for body rows and columns.
|
|
113
|
+
* @param _options.row.rowSelector - (Optional) CSS selector for locating body rows. Defaults to `"tbody>tr"`.
|
|
114
|
+
* @param _options.row.columnSelector - (Optional) CSS selector for locating body columns. Defaults to `"td"`.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const table = new PlaywrightTable(
|
|
118
|
+
* page.locator('table.my-table'),
|
|
119
|
+
* {
|
|
120
|
+
* header: {
|
|
121
|
+
* rowSelector: 'thead tr.header',
|
|
122
|
+
* columnSelector: 'th.header-cell'
|
|
123
|
+
* },
|
|
124
|
+
* row: {
|
|
125
|
+
* rowSelector: 'tbody tr.data-row',
|
|
126
|
+
* columnSelector: 'td.data-cell'
|
|
127
|
+
* }
|
|
128
|
+
* }
|
|
129
|
+
* );
|
|
130
|
+
*/
|
|
131
|
+
constructor(_tableLocator: Locator, _options?: {
|
|
132
|
+
header?: {
|
|
133
|
+
setMainHeaderRow?: number;
|
|
134
|
+
rowSelector?: string;
|
|
135
|
+
columnSelector?: string;
|
|
136
|
+
};
|
|
137
|
+
row?: {
|
|
138
|
+
rowSelector?: string;
|
|
139
|
+
columnSelector?: string;
|
|
140
|
+
};
|
|
141
|
+
} | undefined);
|
|
142
|
+
/**
|
|
143
|
+
* Retrieves all header rows of the table.
|
|
144
|
+
* @param options - Optional parameters for header row parsing configuration.
|
|
145
|
+
* @returns A promise that resolves to an array of header rows.
|
|
146
|
+
* @throws Error if the table has no header rows or no body rows.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* // Get headers with colspan enabled
|
|
150
|
+
* const headers = await table.getHeaderRows({
|
|
151
|
+
* colspan: { enabled: true, suffix: true },
|
|
152
|
+
* duplicateSuffix: true
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* @see {@link getMainHeaderRow} for getting only the main header row
|
|
156
|
+
* @see {@link HeaderRowOptions} for all available configuration options
|
|
157
|
+
*/
|
|
158
|
+
getHeaderRows(options?: HeaderRowOptions): Promise<HeaderRow[]>;
|
|
159
|
+
/**
|
|
160
|
+
* Retrieves the main header row of the table.
|
|
161
|
+
* By default, uses the last header row if multiple exist. Can be customized via constructor.
|
|
162
|
+
* @param options - Optional parameters for header row parsing configuration.
|
|
163
|
+
* @returns A promise that resolves to the main header row.
|
|
164
|
+
* @throws Error if the table has no header rows or no body rows.
|
|
165
|
+
* @throws Error if setMainHeaderRow index is out of bounds (negative or >= header row count).
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* // Get main header with custom index set in constructor
|
|
169
|
+
* const table = new PlaywrightTable(page.locator('table'), {
|
|
170
|
+
* header: { setMainHeaderRow: 0 } // Use first row as main header
|
|
171
|
+
* });
|
|
172
|
+
* const mainHeader = await table.getMainHeaderRow();
|
|
173
|
+
*
|
|
174
|
+
* @see {@link getHeaderRows} for getting all header rows
|
|
175
|
+
*/
|
|
176
|
+
getMainHeaderRow(options?: HeaderRowOptions): Promise<HeaderRow>;
|
|
177
|
+
/**
|
|
178
|
+
* Retrieves all body rows of the table.
|
|
179
|
+
* @param options - Optional table or body-row configuration.
|
|
180
|
+
* @param options.cellContentType - Legacy shortcut for body row content extraction. See {@link CellContentType}.
|
|
181
|
+
* @param options.bodyRowOptions - Body row parsing options.
|
|
182
|
+
* @param options.headerRowOptions - Header parsing options used when resolving row/colspan layouts.
|
|
183
|
+
* @returns A promise that resolves to an array of body rows.
|
|
184
|
+
* @throws Error if the table has no header rows or no body rows.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* // Get body rows with TextContent (includes hidden elements)
|
|
188
|
+
* const rows = await table.getBodyRows({
|
|
189
|
+
* cellContentType: CellContentType.TextContent
|
|
190
|
+
* });
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* // Get body rows with default InnerText
|
|
194
|
+
* const rows = await table.getBodyRows();
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* // Use full table options (for example to sanitize noisy treegrid headers)
|
|
198
|
+
* const rows = await table.getBodyRows({
|
|
199
|
+
* bodyRowOptions: { cellContentType: CellContentType.InnerText },
|
|
200
|
+
* headerRowOptions: { normalizeWhitespace: true, stripIconGlyphs: true }
|
|
201
|
+
* });
|
|
202
|
+
*/
|
|
203
|
+
getBodyRows(options?: TableOptions): Promise<BodyRow[]>;
|
|
204
|
+
/**
|
|
205
|
+
* Retrieves a specific cell locator in the body of the table by row and column index.
|
|
206
|
+
* Both are positions in the DOM: `headerPosition` is the index among the cells of the row, so with
|
|
207
|
+
* colspan or rowspan it can differ from the column. The methods that find cells by header name
|
|
208
|
+
* account for spans.
|
|
209
|
+
* @param rowNumber - The row number (0-based index).
|
|
210
|
+
* @param headerPosition - The cell position within the row (0-based index).
|
|
211
|
+
* @returns A Playwright Locator for the specified cell.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* // Get cell at row 1, column 2
|
|
215
|
+
* const cellLocator = table.getBodyCellLocator(1, 2);
|
|
216
|
+
* await expect(cellLocator).toHaveText('Expected Text');
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* // Get first cell in first row
|
|
220
|
+
* const firstCell = table.getBodyCellLocator(0, 0);
|
|
221
|
+
* await firstCell.click();
|
|
222
|
+
*
|
|
223
|
+
* @see {@link getBodyCellLocatorByRowConditions} for finding cells by content matching
|
|
224
|
+
* @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting all cells in a column
|
|
225
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells under a header
|
|
226
|
+
*/
|
|
227
|
+
getBodyCellLocator(rowNumber: number, headerPosition: number): Locator;
|
|
228
|
+
/**
|
|
229
|
+
* Retrieves a cell locator in the body of the table based on row conditions and a target header.
|
|
230
|
+
* Finds the first row where all conditions match, then returns the cell in the target column.
|
|
231
|
+
* @param conditions - An object where keys are header names and values are expected cell contents, or a single [header, value] tuple.
|
|
232
|
+
* @param targetHeader - The header name of the column containing the cell to retrieve.
|
|
233
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
234
|
+
* @returns A promise that resolves to a Playwright Locator for the matching cell.
|
|
235
|
+
* @throws Error if no row matches all specified conditions.
|
|
236
|
+
* @throws Error if any condition header or target header is not found in the table.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* // Find cell with multiple conditions
|
|
240
|
+
* const emailLocator = await table.getBodyCellLocatorByRowConditions(
|
|
241
|
+
* { "First name": "John", "Status": "Active" },
|
|
242
|
+
* "Email"
|
|
243
|
+
* );
|
|
244
|
+
* await expect(emailLocator).toContainText('@example.com');
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* // Find cell with single condition using tuple syntax
|
|
248
|
+
* const actionCell = await table.getBodyCellLocatorByRowConditions(
|
|
249
|
+
* ["Username", "john.doe"],
|
|
250
|
+
* "Actions"
|
|
251
|
+
* );
|
|
252
|
+
* await actionCell.locator('button').click();
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* // Find cell with single condition using object syntax
|
|
256
|
+
* const statusCell = await table.getBodyCellLocatorByRowConditions(
|
|
257
|
+
* { "Username": "john.doe" },
|
|
258
|
+
* "Status"
|
|
259
|
+
* );
|
|
260
|
+
*
|
|
261
|
+
* @see {@link getBodyCellLocator} for getting cells by direct index
|
|
262
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cells in a column
|
|
263
|
+
*/
|
|
264
|
+
getBodyCellLocatorByRowConditions(conditions: Record<string, string> | [string, string], targetHeader: string, options?: TableOptions): Promise<Locator>;
|
|
265
|
+
/**
|
|
266
|
+
* Retrieves all cell locators in the body of the table for a specific header name.
|
|
267
|
+
* Returns locators for every cell in the column identified by the header.
|
|
268
|
+
* @param header - The header name identifying the column.
|
|
269
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
270
|
+
* @returns A promise that resolves to an array of Playwright Locators for all cells in the column.
|
|
271
|
+
* @throws Error if the header is not found, or if the table has no header rows or no body rows.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* // Get all cells in the "Status" column
|
|
275
|
+
* const statusCells = await table.getAllBodyCellLocatorsByHeaderName("Status");
|
|
276
|
+
* for (const cell of statusCells) {
|
|
277
|
+
* await expect(cell).toHaveText(/Active|Inactive/);
|
|
278
|
+
* }
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* // Get text from all cells in "Name" column
|
|
282
|
+
* const nameCells = await table.getAllBodyCellLocatorsByHeaderName("Name");
|
|
283
|
+
* const names = await Promise.all(nameCells.map(cell => cell.textContent()));
|
|
284
|
+
*
|
|
285
|
+
* @see {@link getAllBodyCellLocatorsByHeaderIndex} for getting cells by column index
|
|
286
|
+
* @see {@link getBodyCellLocatorByRowConditions} for finding specific cells by content
|
|
287
|
+
*/
|
|
288
|
+
getAllBodyCellLocatorsByHeaderName(header: string, options?: TableOptions): Promise<Locator[]>;
|
|
289
|
+
/**
|
|
290
|
+
* Retrieves all cell locators in the body of the table for a specific header index.
|
|
291
|
+
* Returns locators for every cell in the column at the specified position.
|
|
292
|
+
* @param headerIndex - The header/column index (0-based).
|
|
293
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
294
|
+
* @returns A promise that resolves to an array of Playwright Locators for all cells in the column.
|
|
295
|
+
* @throws Error if the header index is out of bounds (negative or >= the number of columns, counting colspans).
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* // Get all cells in the third column (index 2)
|
|
299
|
+
* const thirdColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(2);
|
|
300
|
+
* for (const cell of thirdColumnCells) {
|
|
301
|
+
* await expect(cell).toBeVisible();
|
|
302
|
+
* }
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* // Get all cells in first column
|
|
306
|
+
* const firstColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(0);
|
|
307
|
+
* const values = await Promise.all(firstColumnCells.map(c => c.textContent()));
|
|
308
|
+
*
|
|
309
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting cells by header name
|
|
310
|
+
* @see {@link getBodyCellLocator} for getting a specific cell by row and column index
|
|
311
|
+
*/
|
|
312
|
+
getAllBodyCellLocatorsByHeaderIndex(headerIndex: number, options?: TableOptions): Promise<Locator[]>;
|
|
313
|
+
/**
|
|
314
|
+
* Converts the table data into a JSON array where each object represents a row.
|
|
315
|
+
* Keys are header names, values are cell contents.
|
|
316
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
317
|
+
* @returns A promise that resolves to an array of objects representing table rows.
|
|
318
|
+
* @throws Error if no header rows or body rows found.
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* // Convert table to JSON with default options
|
|
322
|
+
* const data = await table.getJson();
|
|
323
|
+
* // Result: [{"Name": "John", "Age": "30"}, {"Name": "Jane", "Age": "25"}]
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* // Convert with colspan enabled and duplicate suffix
|
|
327
|
+
* const data = await table.getJson({
|
|
328
|
+
* headerRowOptions: {
|
|
329
|
+
* colspan: { enabled: true, suffix: true },
|
|
330
|
+
* duplicateSuffix: true,
|
|
331
|
+
* emptyCellReplacement: true
|
|
332
|
+
* }
|
|
333
|
+
* });
|
|
334
|
+
*/
|
|
335
|
+
getJson(options?: TableOptions): Promise<Record<string, string>[]>;
|
|
336
|
+
/**
|
|
337
|
+
* Waits for the table body to be empty (no body rows).
|
|
338
|
+
* @param options - Polling options (`timeout`, default 30000 ms; `interval`, default 100 ms).
|
|
339
|
+
* @returns A promise that resolves when table body is empty.
|
|
340
|
+
* @throws Error if table is not empty within timeout period.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* // Wait for table to clear after delete operation
|
|
344
|
+
* await table.waitForEmpty({ timeout: 5000 });
|
|
345
|
+
*
|
|
346
|
+
* @see {@link waitForNonEmpty} for waiting until table has data
|
|
347
|
+
*
|
|
348
|
+
* @remarks
|
|
349
|
+
* **Alternative:** Consider using Playwright's `expect.poll` for more flexibility. Count the rows with
|
|
350
|
+
* `getRowCount()`: the methods that read the table throw while the body is empty.
|
|
351
|
+
* ```ts
|
|
352
|
+
* await expect.poll(async () => (await table.getRowCount()).body).toBe(0);
|
|
353
|
+
* ```
|
|
354
|
+
*/
|
|
355
|
+
waitForEmpty(options?: PollingOptions): Promise<void>;
|
|
356
|
+
/**
|
|
357
|
+
* Waits for the table body to be non-empty (at least one body row with content).
|
|
358
|
+
* A row is considered valid if it has at least one cell containing text.
|
|
359
|
+
* @param options - Polling options (`timeout`, default 30000 ms; `interval`, default 100 ms).
|
|
360
|
+
* @returns A promise that resolves when table has at least one body row with content.
|
|
361
|
+
* @throws Error if table remains empty or has no rows with content within timeout period.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* // Wait for table to populate after search
|
|
365
|
+
* await table.waitForNonEmpty({ timeout: 5000 });
|
|
366
|
+
*
|
|
367
|
+
* @see {@link waitForEmpty} for waiting until table is cleared
|
|
368
|
+
*
|
|
369
|
+
* @remarks
|
|
370
|
+
* **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:
|
|
371
|
+
* ```ts
|
|
372
|
+
* await expect.poll(async () => (await table.getBodyRows()).length).toBeGreaterThan(0);
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
waitForNonEmpty(options?: PollingOptions): Promise<void>;
|
|
376
|
+
/**
|
|
377
|
+
* Waits for the table body to have exactly N rows.
|
|
378
|
+
* @param count - The exact number of body rows expected.
|
|
379
|
+
* @param options - Polling options (`timeout`, default 30000 ms; `interval`, default 100 ms).
|
|
380
|
+
* @returns A promise that resolves when the table has exactly the specified number of rows.
|
|
381
|
+
* @throws Error if row count doesn't match within timeout period.
|
|
382
|
+
*
|
|
383
|
+
* @example
|
|
384
|
+
* // Wait for exactly 10 rows after pagination
|
|
385
|
+
* await table.waitForExactRowCount(10, { timeout: 5000 });
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* // Verify table has exactly 0 rows (alternative to waitForEmpty)
|
|
389
|
+
* await table.waitForExactRowCount(0);
|
|
390
|
+
*
|
|
391
|
+
* @see {@link getRowCount} for getting current row counts
|
|
392
|
+
*
|
|
393
|
+
* @remarks
|
|
394
|
+
* **Alternative:** Consider using Playwright's `expect.poll` for more flexibility:
|
|
395
|
+
* ```ts
|
|
396
|
+
* await expect.poll(async () => (await table.getBodyRows()).length).toBe(10);
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
waitForExactRowCount(count: number, options?: PollingOptions): Promise<void>;
|
|
400
|
+
/**
|
|
401
|
+
* Gets the count of header and body rows in the table.
|
|
402
|
+
* Lightweight method that doesn't fetch cell data, only counts rows.
|
|
403
|
+
* @returns A promise that resolves to an object with header and body row counts.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* // Get current row counts
|
|
407
|
+
* const counts = await table.getRowCount();
|
|
408
|
+
* console.log(`Headers: ${counts.header}, Body: ${counts.body}`);
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* // Assert specific row count
|
|
412
|
+
* const { body } = await table.getRowCount();
|
|
413
|
+
* expect(body).toBe(10);
|
|
414
|
+
*/
|
|
415
|
+
getRowCount(): Promise<{
|
|
416
|
+
header: number;
|
|
417
|
+
body: number;
|
|
418
|
+
}>;
|
|
419
|
+
/**
|
|
420
|
+
* Gets all distinct (unique) values from a specific column.
|
|
421
|
+
* Returns a sorted array of unique string values, excluding empty values.
|
|
422
|
+
* @param headerName - The header name of the column to extract distinct values from.
|
|
423
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
424
|
+
* @returns A promise that resolves to a sorted array of distinct values.
|
|
425
|
+
* @throws Error if the header is not found, or if the table has no header rows or no body rows.
|
|
426
|
+
*
|
|
427
|
+
* @example
|
|
428
|
+
* // Get all unique status values
|
|
429
|
+
* const statuses = await table.getDistinctColumnValues("Status");
|
|
430
|
+
* // Result: ["Active", "Inactive", "Pending"]
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* // Get distinct countries for filtering
|
|
434
|
+
* const countries = await table.getDistinctColumnValues("Country");
|
|
435
|
+
* console.log(`Available countries: ${countries.join(", ")}`);
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* // Verify expected values exist
|
|
439
|
+
* const roles = await table.getDistinctColumnValues("Role");
|
|
440
|
+
* expect(roles).toContain("Admin");
|
|
441
|
+
* expect(roles).toHaveLength(3);
|
|
442
|
+
*
|
|
443
|
+
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cell locators in a column
|
|
444
|
+
* @see {@link getJson} for getting full table data
|
|
445
|
+
*/
|
|
446
|
+
getDistinctColumnValues(headerName: string, options?: TableOptions): Promise<string[]>;
|
|
447
|
+
/**
|
|
448
|
+
* Finds the index of the first body row matching the specified conditions.
|
|
449
|
+
* Returns -1 if no matching row is found.
|
|
450
|
+
* @param conditions - Record of header names and expected cell values to match.
|
|
451
|
+
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
452
|
+
* @returns A promise that resolves to the 0-based index of the first matching row, or -1 if not found.
|
|
453
|
+
* @throws Error if specified headers don't exist, or if the table has no header rows or no body rows.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* // Find index of row where Status is "Active"
|
|
457
|
+
* const index = await table.findRowIndex({ Status: "Active" });
|
|
458
|
+
* if (index >= 0) {
|
|
459
|
+
* console.log(`Found at row ${index}`);
|
|
460
|
+
* }
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* // Find row and interact with it
|
|
464
|
+
* const rowIndex = await table.findRowIndex({ Username: "john.doe" });
|
|
465
|
+
* if (rowIndex >= 0) {
|
|
466
|
+
* const cell = table.getBodyCellLocator(rowIndex, 0);
|
|
467
|
+
* await cell.click();
|
|
468
|
+
* }
|
|
469
|
+
*
|
|
470
|
+
* @see {@link getBodyCellLocatorByRowConditions} for getting cell locators from matching rows
|
|
471
|
+
*/
|
|
472
|
+
findRowIndex(conditions: Record<string, string>, options?: TableOptions): Promise<number>;
|
|
473
|
+
private getTable;
|
|
474
|
+
/**
|
|
475
|
+
* The locator of the DOM cell at a grid position, so colspan and rowspan don't shift it. A cell that
|
|
476
|
+
* spans rows is located in the row it starts in.
|
|
477
|
+
*/
|
|
478
|
+
private gridCellLocator;
|
|
479
|
+
private normalizeGetBodyRowsOptions;
|
|
480
|
+
private resolveHeaderRowOptionsForJsonKeys;
|
|
481
|
+
private resolveTableOptionsForHeaderKeyMethods;
|
|
482
|
+
private resolveHeaderRowOptions;
|
|
483
|
+
private resolveColspanOptions;
|
|
484
|
+
private mainHeaderRow;
|
|
485
|
+
/**
|
|
486
|
+
* Gets the index of a header in the main header row, throwing an error if not found.
|
|
487
|
+
* @param header - The header name to find.
|
|
488
|
+
* @param mainHeader - The main header row to search in.
|
|
489
|
+
* @returns The 0-based index of the header.
|
|
490
|
+
* @throws Error if the header is not found.
|
|
491
|
+
*/
|
|
492
|
+
private getHeaderIndex;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* A header key accepted by {@link TableLocator}: a plain header name, a colspan continuation
|
|
496
|
+
* (`Name__C1`, `Name__C2`, ...) or a duplicate header (`Name__D1`, `Name__D2`, ...).
|
|
497
|
+
*
|
|
498
|
+
* These are the same keys that `PlaywrightTable.getJson()` produces.
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* type Keys = HeaderKey<"Name" | "Status">;
|
|
502
|
+
* // "Name" | "Status" | `Name__C${number}` | `Status__C${number}` | `Name__D${number}` | `Status__D${number}`
|
|
503
|
+
*/
|
|
504
|
+
type HeaderKey<H extends string> = H | `${H}__C${number}` | `${H}__D${number}`;
|
|
505
|
+
/**
|
|
506
|
+
* A column of a {@link TableLocator}: a {@link HeaderKey}, or a 0-based column index on the visual grid
|
|
507
|
+
* (rowspan and colspan aware). Column indexes also work for tables without header rows.
|
|
508
|
+
*
|
|
509
|
+
* @example
|
|
510
|
+
* table.rowAt(0).cell("Name");
|
|
511
|
+
* table.rowAt(0).cell(2);
|
|
512
|
+
*/
|
|
513
|
+
type ColumnKey<H extends string> = HeaderKey<H> | number;
|
|
514
|
+
/**
|
|
515
|
+
* A declarative cell condition. Exactly one key per condition.
|
|
516
|
+
*
|
|
517
|
+
* Cell text is compared as the user sees it: each line whitespace-collapsed and trimmed, lines joined
|
|
518
|
+
* with one `\n`, without icon glyphs, invisible characters or visually hidden text. Expected values are
|
|
519
|
+
* normalized the same way, so `"Street 1\r\nAmsterdam"` matches a cell showing two lines.
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* table.row({ Name: { contains: "john", ignoreCase: true } });
|
|
523
|
+
* table.row({ Status: { oneOf: ["Active", "Pending"] } });
|
|
524
|
+
* table.row({ Remarks: { empty: true } });
|
|
525
|
+
* // Runs in the browser, so it must not use variables from the test scope.
|
|
526
|
+
* table.row({ Age: { predicate: text => Number(text) >= 18 } });
|
|
527
|
+
* // The second argument is the raw cell text (`innerText` or `textContent`, unnormalized).
|
|
528
|
+
* table.row({ Name: { predicate: (text, raw) => raw.includes("\u00a0") } });
|
|
529
|
+
* // Combinators
|
|
530
|
+
* table.row({ Status: { not: "Deleted" } });
|
|
531
|
+
* table.row({ Email: { anyOf: [{ endsWith: "@example.com" }, /@test\./] } });
|
|
532
|
+
* table.row({ Name: { allOf: [{ startsWith: "J" }, { not: { contains: "Doe" } }] } });
|
|
533
|
+
* // Numeric comparisons on the number in the cell (see {@link NumberCondition})
|
|
534
|
+
* table.row({ Age: { gte: 18 } });
|
|
535
|
+
* table.row({ Price: { between: [10, 20], decimal: "," } });
|
|
536
|
+
*/
|
|
537
|
+
type CellCondition = ExclusiveCondition<{
|
|
538
|
+
equals: string;
|
|
539
|
+
ignoreCase?: boolean;
|
|
540
|
+
}> | ExclusiveCondition<{
|
|
541
|
+
contains: string;
|
|
542
|
+
ignoreCase?: boolean;
|
|
543
|
+
}> | ExclusiveCondition<{
|
|
544
|
+
startsWith: string;
|
|
545
|
+
ignoreCase?: boolean;
|
|
546
|
+
}> | ExclusiveCondition<{
|
|
547
|
+
endsWith: string;
|
|
548
|
+
ignoreCase?: boolean;
|
|
549
|
+
}> | ExclusiveCondition<{
|
|
550
|
+
oneOf: readonly string[];
|
|
551
|
+
ignoreCase?: boolean;
|
|
552
|
+
}> | ExclusiveCondition<{
|
|
553
|
+
empty: boolean;
|
|
554
|
+
}> | ExclusiveCondition<{
|
|
555
|
+
/**
|
|
556
|
+
* Serialized with `toString()` and executed in the page, like a `page.evaluate` function.
|
|
557
|
+
* It must be self-contained: it cannot use variables from the test scope.
|
|
558
|
+
*
|
|
559
|
+
* `text` is the normalized cell text that the other conditions compare; `raw` is the cell text
|
|
560
|
+
* exactly as the browser reads it (`innerText`, or `textContent`), without any normalization.
|
|
561
|
+
*/
|
|
562
|
+
predicate: (text: string, raw: string) => boolean;
|
|
563
|
+
}> | ExclusiveCondition<{
|
|
564
|
+
not: CellMatcher;
|
|
565
|
+
}> | ExclusiveCondition<{
|
|
566
|
+
anyOf: readonly [CellMatcher, ...CellMatcher[]];
|
|
567
|
+
}> | ExclusiveCondition<{
|
|
568
|
+
allOf: readonly [CellMatcher, ...CellMatcher[]];
|
|
569
|
+
}> | NumberCondition;
|
|
570
|
+
/**
|
|
571
|
+
* A numeric cell condition: the cell holds exactly one number, and it compares as asked. `between` is
|
|
572
|
+
* inclusive. A cell without a number, or with more than one, never matches.
|
|
573
|
+
*
|
|
574
|
+
* The number is read like {@link DecimalSeparator} describes: `"€ 1,200.50"` is 1200.5 and, with
|
|
575
|
+
* `decimal: ","`, `"1.234,5 kg"` is 1234.5.
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* table.row({ Age: { gt: 65 } });
|
|
579
|
+
* table.row({ Total: { between: [100, 200] } });
|
|
580
|
+
*/
|
|
581
|
+
type NumberCondition = ExclusiveCondition<{
|
|
582
|
+
gt: number;
|
|
583
|
+
decimal?: DecimalSeparator;
|
|
584
|
+
}> | ExclusiveCondition<{
|
|
585
|
+
gte: number;
|
|
586
|
+
decimal?: DecimalSeparator;
|
|
587
|
+
}> | ExclusiveCondition<{
|
|
588
|
+
lt: number;
|
|
589
|
+
decimal?: DecimalSeparator;
|
|
590
|
+
}> | ExclusiveCondition<{
|
|
591
|
+
lte: number;
|
|
592
|
+
decimal?: DecimalSeparator;
|
|
593
|
+
}> | ExclusiveCondition<{
|
|
594
|
+
between: readonly [number, number];
|
|
595
|
+
decimal?: DecimalSeparator;
|
|
596
|
+
}>;
|
|
597
|
+
/**
|
|
598
|
+
* The decimal separator of the numbers in a table. Defaults to `"."`.
|
|
599
|
+
*
|
|
600
|
+
* A number is the one run of digits in the text, with an optional `-` or `−` in front (a currency symbol
|
|
601
|
+
* may come in between). Digits are grouped by the other separator, `'`, or a (non-breaking) space, in
|
|
602
|
+
* groups of three: with `"."`, `"1,234.5"`, `"1 234.5"` and `"1'234.5"` are all 1234.5. Other text, such
|
|
603
|
+
* as a currency or a unit, is ignored. Text with no number, or with more than one, is not a number.
|
|
604
|
+
*/
|
|
605
|
+
type DecimalSeparator = "." | ",";
|
|
606
|
+
type CellConditionKey = "equals" | "contains" | "startsWith" | "endsWith" | "oneOf" | "empty" | "predicate" | "not" | "anyOf" | "allOf" | "gt" | "gte" | "lt" | "lte" | "between" | "ignoreCase" | "decimal";
|
|
607
|
+
/** Forbids the keys of the other condition kinds, so a condition object has exactly one kind. */
|
|
608
|
+
type ExclusiveCondition<T> = T & { [K in Exclude<CellConditionKey, keyof T>]?: never; };
|
|
609
|
+
/**
|
|
610
|
+
* How a cell is matched: an exact string, a RegExp, or a {@link CellCondition}.
|
|
611
|
+
*/
|
|
612
|
+
type CellMatcher = string | RegExp | CellCondition;
|
|
613
|
+
/**
|
|
614
|
+
* Row conditions keyed by header. Every condition must match for a row to be selected.
|
|
615
|
+
*/
|
|
616
|
+
type RowWhere<H extends string> = { [K in HeaderKey<H>]?: CellMatcher; };
|
|
617
|
+
/**
|
|
618
|
+
* Compile-time guard for {@link RowWhere}: rejects an empty object and keys that are not a
|
|
619
|
+
* {@link HeaderKey} of `H`. Intersect it with the inferred conditions type `W`.
|
|
620
|
+
*/
|
|
621
|
+
type ValidRowWhere<H extends string, W> = ([keyof W] extends [never] ? "row() requires at least one condition" : unknown) & ([Exclude<keyof W, HeaderKey<H>>] extends [never] ? unknown : { [K in Exclude<keyof W, HeaderKey<H>>]: never; });
|
|
622
|
+
/** An ARIA role, as Playwright's `getByRole` accepts it. */
|
|
623
|
+
type AriaRole = Parameters<Locator["getByRole"]>[0];
|
|
624
|
+
/**
|
|
625
|
+
* Which elements an option of {@link TableLocator} selects: a CSS selector, or a descriptor that combines a
|
|
626
|
+
* CSS selector, a role and the text of the element. Every field of a descriptor must match.
|
|
627
|
+
*
|
|
628
|
+
* These options are not Playwright `Locator`s, because the table is read in the browser on every retry of a
|
|
629
|
+
* locator or matcher, and only a CSS selector or a descriptor can be checked there.
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* row: { rowSelector: "tbody>tr.data" }
|
|
633
|
+
* row: { rowSelector: { role: "row", hasNotText: "Loading" } }
|
|
634
|
+
* row: { columnSelector: { role: ["cell", "rowheader"] } }
|
|
635
|
+
* emptyRow: { hasText: "No data" }
|
|
636
|
+
* ignore: [...DEFAULT_IGNORE, { css: "span", hasText: /^sort/ }]
|
|
637
|
+
*/
|
|
638
|
+
type ElementSelector = string | ElementDescriptor;
|
|
639
|
+
/** A descriptor of {@link ElementSelector}. It needs at least one field. */
|
|
640
|
+
type ElementDescriptor = {
|
|
641
|
+
/** A CSS selector the element matches. */
|
|
642
|
+
css?: string;
|
|
643
|
+
/**
|
|
644
|
+
* The role of the element, or one of several roles: its `role` attribute (the first role in it), or
|
|
645
|
+
* else the role of a table element. `<table>` is a `table`, `<thead>`, `<tbody>` and `<tfoot>` are a
|
|
646
|
+
* `rowgroup`, and `<tr>` is a `row`. `<td>` is a `gridcell` in a table with the `grid` or `treegrid` role,
|
|
647
|
+
* and a `cell` otherwise. `<th>` is a `rowheader` with `scope="row"` or in a row that also has a `<td>`
|
|
648
|
+
* (outside a `<thead>`), and a `columnheader` otherwise.
|
|
649
|
+
*/
|
|
650
|
+
role?: AriaRole | readonly [AriaRole, ...AriaRole[]];
|
|
651
|
+
/**
|
|
652
|
+
* Text inside the element, like `Locator.filter({ hasText })`: a string matches case-insensitively,
|
|
653
|
+
* anywhere in the text, with whitespace collapsed; a RegExp is tested on that text. The text is the
|
|
654
|
+
* `textContent` of the element, hidden text included.
|
|
655
|
+
*/
|
|
656
|
+
hasText?: string | RegExp;
|
|
657
|
+
/** Text that is not inside the element, the opposite of `hasText`. */
|
|
658
|
+
hasNotText?: string | RegExp;
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Options for {@link TableLocator}.
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* const options: TableLocatorOptions<"Name" | "Status"> = {
|
|
665
|
+
* headers: ["Name", "Status"],
|
|
666
|
+
* header: { rowSelector: "thead>tr", columnSelector: "th" },
|
|
667
|
+
* row: { rowSelector: "tbody>tr", columnSelector: "td" },
|
|
668
|
+
* cellContentType: CellContentType.InnerText,
|
|
669
|
+
* };
|
|
670
|
+
*/
|
|
671
|
+
type TableLocatorOptions<H extends string> = {
|
|
672
|
+
/**
|
|
673
|
+
* The header names of the table. Used to infer typed header keys and by
|
|
674
|
+
* {@link TableLocator.validateHeaders}. Does not restrict anything at runtime by itself.
|
|
675
|
+
*/
|
|
676
|
+
headers?: readonly H[];
|
|
677
|
+
/**
|
|
678
|
+
* Use ARIA roles instead of HTML table elements for the default selectors, for grids built from
|
|
679
|
+
* other elements (such as AG Grid or MUI Data Grid). Selectors you set yourself still win.
|
|
680
|
+
* Defaults to `false`.
|
|
681
|
+
*
|
|
682
|
+
* - Header rows: `[role=row]` with a `[role=columnheader]` inside; header cells: `[role=columnheader]`.
|
|
683
|
+
* - Body rows: the other `[role=row]`s; body cells: `[role=gridcell]`, `[role=cell]` and `[role=rowheader]`.
|
|
684
|
+
*
|
|
685
|
+
* `aria-colindex`, `aria-rowindex`, `aria-colspan` and `aria-rowspan` are used whenever they are
|
|
686
|
+
* present, with or without this option.
|
|
687
|
+
*/
|
|
688
|
+
aria?: boolean;
|
|
689
|
+
header?: {
|
|
690
|
+
/** Index of the main header row. Defaults to the last header row. */
|
|
691
|
+
setMainHeaderRow?: number;
|
|
415
692
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* @param headerName - The header name of the column to extract distinct values from.
|
|
419
|
-
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
420
|
-
* @returns A promise that resolves to a sorted array of distinct values.
|
|
421
|
-
* @throws Error if the specified header is not found in the table.
|
|
422
|
-
*
|
|
423
|
-
* @example
|
|
424
|
-
* // Get all unique status values
|
|
425
|
-
* const statuses = await table.getDistinctColumnValues("Status");
|
|
426
|
-
* // Result: ["Active", "Inactive", "Pending"]
|
|
427
|
-
*
|
|
428
|
-
* @example
|
|
429
|
-
* // Get distinct countries for filtering
|
|
430
|
-
* const countries = await table.getDistinctColumnValues("Country");
|
|
431
|
-
* console.log(`Available countries: ${countries.join(", ")}`);
|
|
432
|
-
*
|
|
433
|
-
* @example
|
|
434
|
-
* // Verify expected values exist
|
|
435
|
-
* const roles = await table.getDistinctColumnValues("Role");
|
|
436
|
-
* expect(roles).toContain("Admin");
|
|
437
|
-
* expect(roles).toHaveLength(3);
|
|
438
|
-
*
|
|
439
|
-
* @see {@link getAllBodyCellLocatorsByHeaderName} for getting all cell locators in a column
|
|
440
|
-
* @see {@link getJson} for getting full table data
|
|
693
|
+
* Header rows, see {@link ElementSelector}. Defaults to `"thead>tr"`, plus the leading rows of `<th>`
|
|
694
|
+
* cells (and no `<td>`) in the first `<tbody>` of a table without a `<thead>`.
|
|
441
695
|
*/
|
|
442
|
-
|
|
696
|
+
rowSelector?: ElementSelector;
|
|
697
|
+
/** Header cells, see {@link ElementSelector}. Defaults to `"th, td"`. */
|
|
698
|
+
columnSelector?: ElementSelector;
|
|
699
|
+
};
|
|
700
|
+
row?: {
|
|
443
701
|
/**
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
* @param conditions - Record of header names and expected cell values to match.
|
|
447
|
-
* @param options - Optional table loading options. See {@link TableOptions}.
|
|
448
|
-
* @returns A promise that resolves to the 0-based index of the first matching row, or -1 if not found.
|
|
449
|
-
* @throws Error if specified headers don't exist in the table.
|
|
450
|
-
*
|
|
451
|
-
* @example
|
|
452
|
-
* // Find index of row where Status is "Active"
|
|
453
|
-
* const index = await table.findRowIndex({ Status: "Active" });
|
|
454
|
-
* if (index >= 0) {
|
|
455
|
-
* console.log(`Found at row ${index}`);
|
|
456
|
-
* }
|
|
457
|
-
*
|
|
458
|
-
* @example
|
|
459
|
-
* // Find row and interact with it
|
|
460
|
-
* const rowIndex = await table.findRowIndex({ Username: "john.doe" });
|
|
461
|
-
* if (rowIndex >= 0) {
|
|
462
|
-
* const cell = table.getBodyCellLocator(rowIndex, 0);
|
|
463
|
-
* await cell.click();
|
|
464
|
-
* }
|
|
465
|
-
*
|
|
466
|
-
* @see {@link getBodyCellLocatorByRowConditions} for getting cell locators from matching rows
|
|
467
|
-
* @see {@link waitForRowByConditions} for waiting until matching row appears
|
|
702
|
+
* Body rows, see {@link ElementSelector}. Defaults to `"tbody>tr"` (and `<tr>` right inside a
|
|
703
|
+
* `<table>` built without a `<tbody>`), without the header rows of a table without a `<thead>`.
|
|
468
704
|
*/
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
private resolveHeaderRowOptions;
|
|
475
|
-
private mainHeaderRow;
|
|
705
|
+
rowSelector?: ElementSelector;
|
|
706
|
+
/** Body cells, see {@link ElementSelector}. Defaults to `"td, th"`, so row header cells are columns too. */
|
|
707
|
+
columnSelector?: ElementSelector;
|
|
708
|
+
};
|
|
709
|
+
footer?: {
|
|
476
710
|
/**
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
* @returns The 0-based index of the header.
|
|
481
|
-
* @throws Error if the header is not found.
|
|
711
|
+
* Footer rows, such as a totals row, see {@link ElementSelector}. Their cells are found with
|
|
712
|
+
* `row.columnSelector`. Defaults to `"tfoot>tr"`, or no footer rows with `aria: true`. Footer rows are
|
|
713
|
+
* not body rows.
|
|
482
714
|
*/
|
|
483
|
-
|
|
715
|
+
rowSelector?: ElementSelector;
|
|
716
|
+
};
|
|
717
|
+
/**
|
|
718
|
+
* How to go to the next page of a paginated table, so {@link TableLocator.findRow},
|
|
719
|
+
* `toJson({ allRows: true })` and {@link TableLocator.totalRowCount} see every page.
|
|
720
|
+
* See {@link PaginateOptions}.
|
|
721
|
+
*/
|
|
722
|
+
paginate?: PaginateOptions;
|
|
723
|
+
/**
|
|
724
|
+
* How to scroll a virtualized grid (one that only renders the rows in view), so
|
|
725
|
+
* {@link TableLocator.findRow}, `toJson({ allRows: true })` and {@link TableLocator.totalRowCount}
|
|
726
|
+
* see every row. See {@link ScrollOptions}.
|
|
727
|
+
*/
|
|
728
|
+
scroll?: ScrollOptions;
|
|
729
|
+
/**
|
|
730
|
+
* The loading indicator of the table, such as a spinner or an overlay. {@link TableLocator.waitForStable}
|
|
731
|
+
* and the page and scroll navigation wait until it is hidden.
|
|
732
|
+
*/
|
|
733
|
+
loading?: LocatorRef;
|
|
734
|
+
/**
|
|
735
|
+
* Placeholder rows, such as a "No data" row, that are never body rows: `rows`, `row()`, `toJson()`,
|
|
736
|
+
* the matchers and the row counts leave them out, so an empty table has 0 body rows. See
|
|
737
|
+
* {@link ElementSelector}.
|
|
738
|
+
*
|
|
739
|
+
* @example
|
|
740
|
+
* emptyRow: ".no-data"
|
|
741
|
+
* emptyRow: { hasText: "No data" }
|
|
742
|
+
*/
|
|
743
|
+
emptyRow?: ElementSelector;
|
|
744
|
+
/**
|
|
745
|
+
* Read the current value of text inputs and text areas in body cells as cell text, in row conditions,
|
|
746
|
+
* `toJson()`, `columnValues()` and the matchers, for editable grids and cells in edit mode. Text-like
|
|
747
|
+
* inputs only (`text`, `email`, `number`, `search`, `tel`, `url` and the date and time types):
|
|
748
|
+
* checkboxes, buttons, files and passwords stay out, and a `<select>` always reads as its selected option.
|
|
749
|
+
* Header cells never read their inputs, so filter fields don't end up in the header keys. Raw text
|
|
750
|
+
* (`raw: true`, a predicate's `raw`) is not affected. Defaults to `false`, like Playwright's `toHaveText`.
|
|
751
|
+
*
|
|
752
|
+
* @example
|
|
753
|
+
* const people = defineTable(page.locator("#people"), { readInputValues: true });
|
|
754
|
+
* await expect(people).toHaveRow({ Email: "john@example.org" }); // <td><input value="john@example.org"></td>
|
|
755
|
+
*/
|
|
756
|
+
readInputValues?: boolean;
|
|
757
|
+
/**
|
|
758
|
+
* The control that selects a row, from the row locator, for {@link TableRow.select},
|
|
759
|
+
* {@link TableRow.deselect} and {@link TableRow.isSelected}. Defaults to the checkbox in a selection
|
|
760
|
+
* column: a column without header text (such as one with only a "select all" checkbox). A checkbox in a
|
|
761
|
+
* column with header text, such as "Active", is data and never selects the row.
|
|
762
|
+
*
|
|
763
|
+
* @example
|
|
764
|
+
* select: row => row.getByRole("checkbox", { name: "Select row" })
|
|
765
|
+
*/
|
|
766
|
+
select?: (row: Locator) => Locator;
|
|
767
|
+
/**
|
|
768
|
+
* How cell text is read, for both header and body cells. Defaults to `InnerText`: the text as it is
|
|
769
|
+
* shown, without hidden text and with CSS `text-transform`. `TextContent` reads all text in the DOM,
|
|
770
|
+
* hidden text included. Either way, the text is normalized (see {@link CellCondition}).
|
|
771
|
+
*/
|
|
772
|
+
cellContentType?: CellContentType;
|
|
773
|
+
/**
|
|
774
|
+
* Elements inside header and body cells whose text is left out, such as screen reader only text and
|
|
775
|
+
* icons, see {@link ElementSelector}. Defaults to {@link DEFAULT_IGNORE}: `.sr-only`, `.visually-hidden`,
|
|
776
|
+
* the Material Icons and Material Symbols classes, and `[aria-hidden=true]`.
|
|
777
|
+
*
|
|
778
|
+
* Setting it replaces the defaults. Spread them to add your own (`[...DEFAULT_IGNORE, ".sort-icon"]`),
|
|
779
|
+
* or pass `[]` to read all text. Raw values (`toJson({ raw: true })`, a predicate's `raw`) are never
|
|
780
|
+
* filtered.
|
|
781
|
+
*/
|
|
782
|
+
ignore?: readonly ElementSelector[];
|
|
783
|
+
};
|
|
784
|
+
/**
|
|
785
|
+
* A Playwright `Locator`, or a function that finds it from the table root. A function lets an option point
|
|
786
|
+
* inside the table before the root is known, as the grid presets do.
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* const next: LocatorRef = root => root.getByRole("button", { name: "Next page" });
|
|
790
|
+
*/
|
|
791
|
+
type LocatorRef = Locator | ((root: Locator) => Locator);
|
|
792
|
+
/**
|
|
793
|
+
* Options for the `paginate` option of {@link TableLocator}.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* defineTable(page.locator("#users"), {
|
|
797
|
+
* paginate: { next: page.getByRole("button", { name: "Next" }), first: page.getByRole("button", { name: "First" }) },
|
|
798
|
+
* });
|
|
799
|
+
*/
|
|
800
|
+
type PaginateOptions = {
|
|
801
|
+
/** The control that goes to the next page. */
|
|
802
|
+
next: LocatorRef;
|
|
803
|
+
/**
|
|
804
|
+
* The control that goes to the first page. When set, navigation starts by clicking it (unless the table
|
|
805
|
+
* shows the first page), so it always covers every page. Without it, navigation starts at the current
|
|
806
|
+
* page.
|
|
807
|
+
*
|
|
808
|
+
* After the click, navigation waits for the table to change. A control that stays enabled on the first
|
|
809
|
+
* page doesn't change the table, so that wait ends after 2 seconds, or after 0.5 seconds for a table with
|
|
810
|
+
* a `loading` indicator that doesn't show. Set `isFirstPage` to skip the wait altogether.
|
|
811
|
+
*/
|
|
812
|
+
first?: LocatorRef;
|
|
813
|
+
/**
|
|
814
|
+
* Whether the table shows the first page, for a `first` control that stays enabled there. When it says
|
|
815
|
+
* so, `first` is not clicked; after a click, navigation waits until it says so. Defaults to: `first` is
|
|
816
|
+
* missing, hidden or disabled (see `isLastPage`), and after a click, the table changed.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* isFirstPage: async () => (await page.getByRole("button", { name: "1", exact: true }).getAttribute("aria-current")) === "page"
|
|
820
|
+
*/
|
|
821
|
+
isFirstPage?: (first: Locator) => Promise<boolean>;
|
|
822
|
+
/**
|
|
823
|
+
* Whether the table shows the last page. Defaults to: `next` is missing, hidden or disabled (with the
|
|
824
|
+
* `disabled` attribute, `aria-disabled="true"` or the `disabled` class, on `next` or an element around
|
|
825
|
+
* it, such as a Bootstrap `<li class="disabled">`). When `next` matches several elements (a pager above
|
|
826
|
+
* and below the table), the first one is used.
|
|
827
|
+
*/
|
|
828
|
+
isLastPage?: (next: Locator) => Promise<boolean>;
|
|
829
|
+
/**
|
|
830
|
+
* The most pages to visit, a positive integer: `3` reads the first 3 pages, also when the table has
|
|
831
|
+
* more. Defaults to every page. A table that never reaches its last page is stopped by the `timeout`.
|
|
832
|
+
*/
|
|
833
|
+
maxPages?: number;
|
|
834
|
+
};
|
|
835
|
+
/**
|
|
836
|
+
* Options for the `scroll` option of {@link TableLocator}.
|
|
837
|
+
*
|
|
838
|
+
* Navigation scrolls the container from the top to the bottom, one step at a time, and reads the rows in
|
|
839
|
+
* view after each step. Rows are told apart by their key, so a row seen twice is read once.
|
|
840
|
+
*
|
|
841
|
+
* @example
|
|
842
|
+
* defineTable(page.getByRole("grid"), { aria: true, scroll: { container: root => root.locator(".viewport") } });
|
|
843
|
+
*/
|
|
844
|
+
type ScrollOptions = {
|
|
845
|
+
/**
|
|
846
|
+
* The element that scrolls. Defaults to the table root. A container that can never scroll
|
|
847
|
+
* (`overflow-y: visible`) is an error that names the element around or inside it that does.
|
|
848
|
+
*/
|
|
849
|
+
container?: LocatorRef;
|
|
850
|
+
/** How far to scroll per step, in pixels. Defaults to 90% of the container height. */
|
|
851
|
+
step?: number;
|
|
852
|
+
/**
|
|
853
|
+
* The attribute of a row element that identifies the row, such as `"data-id"`. Defaults to the first
|
|
854
|
+
* of `aria-rowindex`, `row-index`, `data-rowindex`, `data-index` and `data-id` that the row has, or
|
|
855
|
+
* else the text of its cells. Rows with the same text are told apart by their order within one view,
|
|
856
|
+
* so a repeated row is not lost, but the overlap between two scroll steps can't tell which of two
|
|
857
|
+
* identical rows it shows: give such rows a key.
|
|
858
|
+
*
|
|
859
|
+
* The position attributes (all of these but `data-id`) also order the rows. Other keys only identify
|
|
860
|
+
* a row, and rows are kept in the order they were shown.
|
|
861
|
+
*/
|
|
862
|
+
rowKey?: string;
|
|
863
|
+
/**
|
|
864
|
+
* How long the table must stay the same after a scroll before it is read, in
|
|
865
|
+
* milliseconds. Defaults to 50; `0` reads the rows two animation frames after the scroll. The `loading`
|
|
866
|
+
* indicator, if set, is always waited for.
|
|
867
|
+
*/
|
|
868
|
+
settle?: number;
|
|
869
|
+
/**
|
|
870
|
+
* The most scroll steps per page, a positive integer. Defaults to scrolling to the bottom. A grid that
|
|
871
|
+
* keeps growing is stopped by the `timeout`.
|
|
872
|
+
*/
|
|
873
|
+
maxSteps?: number;
|
|
874
|
+
};
|
|
875
|
+
/**
|
|
876
|
+
* Options for {@link TableLocator.findRow} and {@link TableLocator.totalRowCount}.
|
|
877
|
+
*/
|
|
878
|
+
type NavigationOptions = {
|
|
879
|
+
/** The time for the whole search, in milliseconds. Defaults to 30000. */
|
|
880
|
+
timeout?: number;
|
|
881
|
+
};
|
|
882
|
+
/**
|
|
883
|
+
* How {@link TableLocator.toJson} converts the text of a column:
|
|
884
|
+
* - `"number"`: a number, read as {@link DecimalSeparator} describes; an empty cell is `null`.
|
|
885
|
+
* - `"date"`: a `Date`, parsed with `Date.parse`; an empty cell is `null`.
|
|
886
|
+
* - A function: called in Node with the cell text, so it can use test variables. Its result is the value.
|
|
887
|
+
*
|
|
888
|
+
* A cell that `"number"` or `"date"` cannot parse, or a function that throws, fails `toJson()` with the
|
|
889
|
+
* row, the column and the text.
|
|
890
|
+
*/
|
|
891
|
+
type ColumnParser = "number" | "date" | ((text: string) => unknown);
|
|
892
|
+
/**
|
|
893
|
+
* `T`, but not a place to infer `T` from: like `NoInfer`, which needs TypeScript 5.4.
|
|
894
|
+
* @internal
|
|
895
|
+
*/
|
|
896
|
+
type NotInferred<T> = [T][T extends unknown ? 0 : never];
|
|
897
|
+
/** The {@link ColumnParser}s of {@link TableJsonOptions.parse}, by header key. */
|
|
898
|
+
type ColumnParsers<C extends string> = { readonly [K in C]?: ColumnParser; };
|
|
899
|
+
/** The value a {@link ColumnParser} produces. */
|
|
900
|
+
type ParsedValue<P> = P extends "number" ? number | null : P extends "date" ? Date | null : P extends ((text: string) => infer R) ? R : string;
|
|
901
|
+
/**
|
|
902
|
+
* One row of {@link TableLocator.toJson}: the text of every column in `C`, or the parsed value for the
|
|
903
|
+
* columns in `P`.
|
|
904
|
+
*/
|
|
905
|
+
type TableJsonRow<C extends string, P = {}> = { [K in C]: K extends keyof P ? ParsedValue<P[K]> : string; };
|
|
906
|
+
/**
|
|
907
|
+
* Options for {@link TableLocator.waitForStable}.
|
|
908
|
+
*
|
|
909
|
+
* @example
|
|
910
|
+
* await users.waitForStable({ quiet: 300, loading: page.getByRole("progressbar") });
|
|
911
|
+
*/
|
|
912
|
+
type StableOptions = {
|
|
913
|
+
/**
|
|
914
|
+
* How long the header keys and body cell texts must stay the same, in milliseconds. Defaults to 500.
|
|
915
|
+
*/
|
|
916
|
+
quiet?: number;
|
|
917
|
+
/** The time to wait in milliseconds. Defaults to 5000. */
|
|
918
|
+
timeout?: number;
|
|
919
|
+
/**
|
|
920
|
+
* A loading indicator, such as a spinner or an overlay. The table is only stable while it is hidden
|
|
921
|
+
* (not attached, or not visible), so a table that is about to refresh isn't read too early.
|
|
922
|
+
*/
|
|
923
|
+
loading?: LocatorRef;
|
|
924
|
+
};
|
|
925
|
+
/**
|
|
926
|
+
* Options for {@link TableLocator.toJson}.
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* await users.toJson({ minRows: 1, timeout: 10_000 });
|
|
930
|
+
*
|
|
931
|
+
* @example
|
|
932
|
+
* // Some columns, with typed values
|
|
933
|
+
* await orders.toJson({ columns: ["Id", "Total"], parse: { Total: "number" }, decimal: "," });
|
|
934
|
+
*/
|
|
935
|
+
type TableJsonOptions<C extends string = string, P = {}> = {
|
|
936
|
+
/**
|
|
937
|
+
* Only these columns, in this order. Defaults to every column of the main header row. A key that is not
|
|
938
|
+
* a header key of the table fails `toJson()` with the available headers.
|
|
939
|
+
*/
|
|
940
|
+
columns?: readonly C[];
|
|
941
|
+
/** Converts the text of some columns to typed values, see {@link ColumnParser}. */
|
|
942
|
+
parse?: P & ColumnParsers<NotInferred<C>>;
|
|
943
|
+
/** The decimal separator of the `"number"` columns of `parse`. Defaults to `"."`. */
|
|
944
|
+
decimal?: DecimalSeparator;
|
|
945
|
+
/**
|
|
946
|
+
* Wait until the table stops changing before reading it, see {@link TableLocator.waitForStable}.
|
|
947
|
+
* `true` uses the default options. The `timeout` of the stable options defaults to (and can't exceed)
|
|
948
|
+
* the time left of `timeout`.
|
|
949
|
+
*/
|
|
950
|
+
stable?: boolean | StableOptions;
|
|
951
|
+
/**
|
|
952
|
+
* Read every row: on every page with the `paginate` option, and from top to bottom with the `scroll`
|
|
953
|
+
* option of the table. `minRows` and `stable` apply to the first view. Defaults to `false`: the rows the
|
|
954
|
+
* table shows now.
|
|
955
|
+
*
|
|
956
|
+
* It leaves the table on its last page, scrolled down; without `paginate.first`, the next read starts
|
|
957
|
+
* there. It reads up to `paginate.maxPages` and `scroll.maxSteps`, if set. Reaching the `timeout` before that
|
|
958
|
+
* is an error that says how many rows were read, so part of the rows is never returned as all of them.
|
|
959
|
+
*/
|
|
960
|
+
allRows?: boolean;
|
|
961
|
+
/**
|
|
962
|
+
* Wait for a header row and at least this many body rows with content (a row where at least one cell
|
|
963
|
+
* has text) before reading. Defaults to `0`: read once, without waiting.
|
|
964
|
+
*/
|
|
965
|
+
minRows?: number;
|
|
966
|
+
/**
|
|
967
|
+
* With `minRows` or `stable`, the time for the whole read, waits included, in milliseconds (default
|
|
968
|
+
* 5000). With `allRows`, the time for the whole read, pages and scrolling included (default 30000).
|
|
969
|
+
* Without any of them, the time to wait for the table element to be attached.
|
|
970
|
+
*/
|
|
971
|
+
timeout?: number;
|
|
972
|
+
/**
|
|
973
|
+
* Return every body cell as the browser reads it (`innerText`, or `textContent`), without any
|
|
974
|
+
* normalization: line breaks, tabs, non-breaking spaces, icon glyphs and visually hidden text stay.
|
|
975
|
+
* Header keys are normalized either way. Defaults to `false`.
|
|
976
|
+
*/
|
|
977
|
+
raw?: boolean;
|
|
978
|
+
};
|
|
979
|
+
/** Filter options accepted by `Locator.filter()`. */
|
|
980
|
+
export type RowFilterOptions = Parameters<Locator["filter"]>[0];
|
|
981
|
+
/**
|
|
982
|
+
* One or more body rows of a {@link TableLocator}, with typed access to their cells.
|
|
983
|
+
*
|
|
984
|
+
* Instances are created by {@link TableLocator.row}, {@link TableLocator.rowAt} and {@link TableLocator.rows}.
|
|
985
|
+
* Their locators read like your code in error messages and traces, such as
|
|
986
|
+
* `locator('#users').row({ "Name": "John" }).cell("Status")` (Playwright 1.53 and later).
|
|
987
|
+
*
|
|
988
|
+
* @example
|
|
989
|
+
* const row = table.row({ Name: "John" });
|
|
990
|
+
* await expect(row.locator).toBeVisible();
|
|
991
|
+
* await expect(row.cell("Status")).toHaveText("Active");
|
|
992
|
+
*/
|
|
993
|
+
export declare class TableRow<H extends string = string> {
|
|
994
|
+
readonly table: TableLocator<H>;
|
|
995
|
+
readonly locator: Locator;
|
|
996
|
+
/**
|
|
997
|
+
* @param table - The table this row belongs to.
|
|
998
|
+
* @param locator - The Playwright `Locator` for the row element(s). Its `toString()` is used to describe
|
|
999
|
+
* the locators built from this row.
|
|
1000
|
+
*/
|
|
1001
|
+
constructor(table: TableLocator<H>, locator: Locator);
|
|
1002
|
+
/**
|
|
1003
|
+
* Returns the cell of this row under `column`.
|
|
1004
|
+
* If this row locator matches several rows, the cell locator matches one cell per row.
|
|
1005
|
+
*
|
|
1006
|
+
* @param column - The header key of the column, or its 0-based index (also works without header rows).
|
|
1007
|
+
* @returns A Playwright Locator for the cell.
|
|
1008
|
+
*
|
|
1009
|
+
* @example
|
|
1010
|
+
* await table.row({ Name: "John" }).cell("Email").click();
|
|
1011
|
+
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* // By column index, for example in a table without header rows
|
|
1014
|
+
* await expect(table.rowAt(0).cell(2)).toHaveText("john@example.com");
|
|
1015
|
+
*/
|
|
1016
|
+
cell(column: ColumnKey<H>): Locator;
|
|
1017
|
+
/**
|
|
1018
|
+
* The first of these rows, with typed cell access. Use it when several rows may match and any is fine.
|
|
1019
|
+
*
|
|
1020
|
+
* @example
|
|
1021
|
+
* await table.row({ Status: "Active" }).first().cell("Edit").click();
|
|
1022
|
+
*/
|
|
1023
|
+
first(): TableRow<H>;
|
|
1024
|
+
/**
|
|
1025
|
+
* The last of these rows, with typed cell access.
|
|
1026
|
+
*
|
|
1027
|
+
* @example
|
|
1028
|
+
* await expect(table.rows.last().cell("Name")).toHaveText("Jane");
|
|
1029
|
+
*/
|
|
1030
|
+
last(): TableRow<H>;
|
|
1031
|
+
/**
|
|
1032
|
+
* The row at `index` among these rows, with typed cell access. Negative indexes count from the end,
|
|
1033
|
+
* like `Locator.nth()`.
|
|
1034
|
+
*
|
|
1035
|
+
* @param index - The 0-based index among these rows.
|
|
1036
|
+
* @returns A {@link TableRow} for the row.
|
|
1037
|
+
* @throws Error if index is not an integer.
|
|
1038
|
+
*
|
|
1039
|
+
* @example
|
|
1040
|
+
* await expect(table.row({ Status: "Active" }).nth(1).cell("Name")).toHaveText("Joe");
|
|
1041
|
+
*/
|
|
1042
|
+
nth(index: number): TableRow<H>;
|
|
1043
|
+
/**
|
|
1044
|
+
* Narrows these rows with `Locator.filter()` and keeps typed cell access.
|
|
1045
|
+
*
|
|
1046
|
+
* @param options - The same options as `Locator.filter()`.
|
|
1047
|
+
* @returns A new {@link TableRow} for the filtered rows.
|
|
1048
|
+
*
|
|
1049
|
+
* @example
|
|
1050
|
+
* await expect(table.rows.filter({ hasText: "Jane" }).cell("Status")).toHaveText("Active");
|
|
1051
|
+
*/
|
|
1052
|
+
filter(options?: RowFilterOptions): TableRow<H>;
|
|
1053
|
+
/**
|
|
1054
|
+
* Fills the controls in the cells of this row, one column after the other. A boolean checks or unchecks
|
|
1055
|
+
* a checkbox or switch; a string selects an option of a `<select>`, or fills an input, a text area or
|
|
1056
|
+
* an editable element.
|
|
1057
|
+
*
|
|
1058
|
+
* A cell without a control is edited in place, as data grids do: it is double-clicked, the editor that
|
|
1059
|
+
* appears in the cell is filled, and `commit` is pressed. Editors that open outside the cell (a popup)
|
|
1060
|
+
* are not found.
|
|
1061
|
+
*
|
|
1062
|
+
* Columns are filled in the order of `values`. When the row is found by the value of a column you fill,
|
|
1063
|
+
* fill that column last: after it changes, the row no longer matches.
|
|
1064
|
+
*
|
|
1065
|
+
* @param values - The value per column.
|
|
1066
|
+
* @param options - Optional settings.
|
|
1067
|
+
* @param options.commit - (Optional) The key that ends in-place editing. Defaults to `"Enter"`; `false` presses nothing.
|
|
1068
|
+
* @param options.timeout - (Optional) The time per action, in milliseconds. Defaults to 5000.
|
|
1069
|
+
* @throws Error if a cell has no control, also not after a double-click.
|
|
1070
|
+
*
|
|
1071
|
+
* @example
|
|
1072
|
+
* await users.row({ Name: "John" }).fill({ Email: "john@example.org", Active: false, Role: "Admin" });
|
|
1073
|
+
*/
|
|
1074
|
+
fill(values: { [K in HeaderKey<H>]?: string | boolean; }, options?: {
|
|
1075
|
+
commit?: string | false;
|
|
1076
|
+
timeout?: number;
|
|
1077
|
+
}): Promise<void>;
|
|
1078
|
+
/**
|
|
1079
|
+
* Selects this row: checks its selection checkbox (see the `select` option of {@link TableLocator}), or
|
|
1080
|
+
* clicks the row if it has none, and waits until the row is selected (see {@link isSelected}). A
|
|
1081
|
+
* selected row is left as it is.
|
|
1082
|
+
*
|
|
1083
|
+
* @param options - Optional timeout, in milliseconds. Defaults to 5000.
|
|
1084
|
+
* @throws Error if the row is not selected within the timeout.
|
|
1085
|
+
*
|
|
1086
|
+
* @example
|
|
1087
|
+
* await users.row({ Name: "John" }).select();
|
|
1088
|
+
* await expect(users.row({ Name: "John" })).toBeSelected();
|
|
1089
|
+
*/
|
|
1090
|
+
select(options?: {
|
|
1091
|
+
timeout?: number;
|
|
1092
|
+
}): Promise<void>;
|
|
1093
|
+
/**
|
|
1094
|
+
* Deselects this row: unchecks its selection checkbox, or clicks the row if it has none, and waits
|
|
1095
|
+
* until the row is not selected. A row that is not selected is left as it is.
|
|
1096
|
+
*
|
|
1097
|
+
* @param options - Optional timeout, in milliseconds. Defaults to 5000.
|
|
1098
|
+
* @throws Error if the row is still selected after the timeout.
|
|
1099
|
+
*/
|
|
1100
|
+
deselect(options?: {
|
|
1101
|
+
timeout?: number;
|
|
1102
|
+
}): Promise<void>;
|
|
1103
|
+
/**
|
|
1104
|
+
* Whether this row is selected. A row with `aria-selected` is selected when it is `"true"`. Otherwise,
|
|
1105
|
+
* the row is selected when its selection checkbox is checked (see the `select` option of
|
|
1106
|
+
* {@link TableLocator}). Reads the row once; for a retrying check, use `expect(row).toBeSelected()`.
|
|
1107
|
+
*
|
|
1108
|
+
* @param options - Optional timeout for finding the row, in milliseconds.
|
|
1109
|
+
* @returns A promise that resolves to whether the row is selected.
|
|
1110
|
+
*/
|
|
1111
|
+
isSelected(options?: {
|
|
1112
|
+
timeout?: number;
|
|
1113
|
+
}): Promise<boolean>;
|
|
1114
|
+
/**
|
|
1115
|
+
* Expands this row of a tree grid, and waits until it is expanded. An expanded row is left as it is.
|
|
1116
|
+
* It clicks `toggle`, or else the element in the row with `aria-expanded` (not a menu button, which has
|
|
1117
|
+
* `aria-haspopup`), or else, when the row itself has `aria-expanded`, the button in its first cell.
|
|
1118
|
+
* Without any of them, it focuses the row and presses `ArrowRight`, as tree grids do.
|
|
1119
|
+
*
|
|
1120
|
+
* @param options - Optional settings.
|
|
1121
|
+
* @param options.toggle - (Optional) The control that expands and collapses the row, from the row locator.
|
|
1122
|
+
* @param options.timeout - (Optional) The time to wait, in milliseconds. Defaults to 5000.
|
|
1123
|
+
* @throws Error if the row has no `aria-expanded` (on the row or an element in it), or doesn't expand.
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* await files.row({ Name: "src" }).expand();
|
|
1127
|
+
* await expect(files.row({ Name: "index.ts" }).locator).toBeVisible();
|
|
1128
|
+
*/
|
|
1129
|
+
expand(options?: {
|
|
1130
|
+
toggle?: (row: Locator) => Locator;
|
|
1131
|
+
timeout?: number;
|
|
1132
|
+
}): Promise<void>;
|
|
1133
|
+
/**
|
|
1134
|
+
* Collapses this row of a tree grid, like {@link expand} does, but with `ArrowLeft` as the fallback.
|
|
1135
|
+
*
|
|
1136
|
+
* @param options - Optional settings, the same as for {@link expand}.
|
|
1137
|
+
* @throws Error if the row has no `aria-expanded` (on the row or an element in it), or doesn't collapse.
|
|
1138
|
+
*/
|
|
1139
|
+
collapse(options?: {
|
|
1140
|
+
toggle?: (row: Locator) => Locator;
|
|
1141
|
+
timeout?: number;
|
|
1142
|
+
}): Promise<void>;
|
|
1143
|
+
/**
|
|
1144
|
+
* Whether this row is expanded: the `aria-expanded` of the row, or else of the first element in it that
|
|
1145
|
+
* has one and is not a menu button (with `aria-haspopup`). `null` if there is none, for a row that
|
|
1146
|
+
* cannot expand.
|
|
1147
|
+
*
|
|
1148
|
+
* @param options - Optional settings.
|
|
1149
|
+
* @param options.timeout - (Optional) The time to wait for the row, in milliseconds.
|
|
1150
|
+
* @returns A promise that resolves to `true`, `false` or `null`.
|
|
1151
|
+
*/
|
|
1152
|
+
isExpanded(options?: {
|
|
1153
|
+
timeout?: number;
|
|
1154
|
+
}): Promise<boolean | null>;
|
|
1155
|
+
/**
|
|
1156
|
+
* The `aria-level` of this row in a tree grid (1 for the top level), or `null` if it has none.
|
|
1157
|
+
*
|
|
1158
|
+
* @param options - Optional settings.
|
|
1159
|
+
* @param options.timeout - (Optional) The time to wait for the row, in milliseconds.
|
|
1160
|
+
* @example
|
|
1161
|
+
* expect(await files.row({ Name: "index.ts" }).level()).toBe(2);
|
|
1162
|
+
*/
|
|
1163
|
+
level(options?: {
|
|
1164
|
+
timeout?: number;
|
|
1165
|
+
}): Promise<number | null>;
|
|
1166
|
+
/**
|
|
1167
|
+
* The control that selects this row: the `select` option of the table, or else the checkbox in the
|
|
1168
|
+
* first column without header text (in a table without header rows, the first column) that has one.
|
|
1169
|
+
* `undefined` if there is none.
|
|
1170
|
+
*/
|
|
1171
|
+
private selectionControl;
|
|
1172
|
+
/**
|
|
1173
|
+
* The control that expands and collapses this row: `toggle` (waited for, as you passed it), or else the
|
|
1174
|
+
* element in the row with `aria-expanded` that is not a menu button, or else, when the row itself has
|
|
1175
|
+
* `aria-expanded`, the button in its first cell. `undefined` if there is none.
|
|
1176
|
+
* @throws Error if `toggle` doesn't show up.
|
|
1177
|
+
*/
|
|
1178
|
+
private expandToggle;
|
|
1179
|
+
private setSelected;
|
|
1180
|
+
private setExpanded;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* A type-safe table API that only returns Playwright `Locator`s.
|
|
1184
|
+
*
|
|
1185
|
+
* Header names are resolved to columns inside the browser by the `pw-table-v1` selector engine on every
|
|
1186
|
+
* evaluation, so every locator auto-waits for late headers and rows, and keeps working when columns are
|
|
1187
|
+
* reordered at runtime. Building locators is synchronous.
|
|
1188
|
+
*
|
|
1189
|
+
* Text is read as the user sees it. Header text is one line: `First<br>name` is the header `"First name"`.
|
|
1190
|
+
* Body cells keep their line breaks as one `\n`. Icon glyphs, invisible characters and the text of the elements
|
|
1191
|
+
* matching the `ignore` option (screen reader only text and icons by default) are left out. Header names
|
|
1192
|
+
* you pass are normalized the same way.
|
|
1193
|
+
*
|
|
1194
|
+
* The `pw-table-v1` selector engine must be registered first: import `test` from this package, or call
|
|
1195
|
+
* {@link registerTableSelectors} before the page is created.
|
|
1196
|
+
*
|
|
1197
|
+
* @example
|
|
1198
|
+
* import { test, expect, TableLocator } from "@cerios/playwright-table";
|
|
1199
|
+
*
|
|
1200
|
+
* test("user is active", async ({ page }) => {
|
|
1201
|
+
* const users = new TableLocator(page.locator("table"), { headers: ["Name", "Status", "Email"] });
|
|
1202
|
+
* await expect(users.row({ Name: "John" }).cell("Status")).toHaveText("Active");
|
|
1203
|
+
* });
|
|
1204
|
+
*/
|
|
1205
|
+
export declare class TableLocator<const H extends string = string> {
|
|
1206
|
+
/** The Playwright `Locator` pointing to the root table element. */
|
|
1207
|
+
readonly root: Locator;
|
|
1208
|
+
private readonly _config;
|
|
1209
|
+
private readonly _headers;
|
|
1210
|
+
private readonly _loading;
|
|
1211
|
+
private readonly _navigator;
|
|
1212
|
+
private readonly _rowKey;
|
|
1213
|
+
private readonly _select;
|
|
1214
|
+
/**
|
|
1215
|
+
* Creates a table with customizable selectors for headers and rows.
|
|
1216
|
+
*
|
|
1217
|
+
* Rows and cells inside a cell (such as a nested table) never belong to this table, and both `<th>`
|
|
1218
|
+
* and `<td>` count as cells in header and body rows. `PlaywrightTable` only takes `<th>` in header rows
|
|
1219
|
+
* and `<td>` in body rows.
|
|
1220
|
+
*
|
|
1221
|
+
* @param root - The Playwright `Locator` pointing to the root table element.
|
|
1222
|
+
* @param options - Optional configuration. See {@link TableLocatorOptions}.
|
|
1223
|
+
* @param options.headers - (Optional) Header names, used for typed header keys and {@link validateHeaders}.
|
|
1224
|
+
* @param options.aria - (Optional) Use ARIA roles for the default selectors. Defaults to `false`.
|
|
1225
|
+
* @param options.header.setMainHeaderRow - (Optional) Index of the main header row. Defaults to the last header row.
|
|
1226
|
+
* @param options.header.rowSelector - (Optional) Header rows: a CSS selector or an {@link ElementSelector} descriptor. Defaults to `"thead>tr"`, plus the leading `<th>`-only rows of a table without `<thead>`.
|
|
1227
|
+
* @param options.header.columnSelector - (Optional) Header cells, see {@link ElementSelector}. Defaults to `"th, td"`.
|
|
1228
|
+
* @param options.row.rowSelector - (Optional) Body rows, see {@link ElementSelector}. Defaults to `"tbody>tr"`, without those header rows.
|
|
1229
|
+
* @param options.row.columnSelector - (Optional) Body cells, see {@link ElementSelector}. Defaults to `"td, th"`.
|
|
1230
|
+
* @param options.cellContentType - (Optional) How cell text is read. Defaults to `InnerText`.
|
|
1231
|
+
* @param options.ignore - (Optional) Elements inside cells whose text is left out, see {@link ElementSelector}. Defaults to {@link DEFAULT_IGNORE}.
|
|
1232
|
+
* @param options.footer.rowSelector - (Optional) Footer rows, see {@link ElementSelector}. Defaults to `"tfoot>tr"`, or none with `aria`.
|
|
1233
|
+
* @param options.paginate - (Optional) How to go to the next page, see {@link PaginateOptions}.
|
|
1234
|
+
* @param options.scroll - (Optional) How to scroll a virtualized grid, see {@link ScrollOptions}.
|
|
1235
|
+
* @param options.loading - (Optional) The loading indicator of the table.
|
|
1236
|
+
* @param options.emptyRow - (Optional) Placeholder rows, such as a "No data" row, that are never body rows, see {@link ElementSelector}.
|
|
1237
|
+
* @param options.readInputValues - (Optional) Read the value of text inputs in body cells as cell text. Defaults to `false`.
|
|
1238
|
+
* @param options.select - (Optional) The control that selects a row, from the row locator. Defaults to the checkbox in a selection column.
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* // Typed headers: header arguments are checked at compile time
|
|
1242
|
+
* const users = new TableLocator(page.locator("table"), { headers: ["Name", "Status"] });
|
|
1243
|
+
*
|
|
1244
|
+
* @example
|
|
1245
|
+
* // Div based table with custom selectors
|
|
1246
|
+
* const table = new TableLocator(page.locator(".divTable"), {
|
|
1247
|
+
* header: { rowSelector: ".divTableHeading > .divTableRow", columnSelector: ".divTableHead" },
|
|
1248
|
+
* row: { rowSelector: ".divTableBody > .divTableRow", columnSelector: ".divTableCell" },
|
|
1249
|
+
* });
|
|
1250
|
+
*
|
|
1251
|
+
* @example
|
|
1252
|
+
* // ARIA grid, such as AG Grid or MUI Data Grid
|
|
1253
|
+
* const grid = new TableLocator(page.getByRole("grid"), { aria: true });
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* // Rows by role and text instead of CSS
|
|
1257
|
+
* const orders = new TableLocator(page.locator("#orders"), {
|
|
1258
|
+
* row: { rowSelector: { role: "row", hasNotText: "Loading" } },
|
|
1259
|
+
* emptyRow: { hasText: "No data" },
|
|
1260
|
+
* });
|
|
1261
|
+
*/
|
|
1262
|
+
constructor(root: Locator, options?: TableLocatorOptions<H>);
|
|
1263
|
+
/**
|
|
1264
|
+
* The footer rows of the table, such as a totals row, with typed cell access. Footer rows are not body
|
|
1265
|
+
* rows: `rows`, `row()`, `toJson()` and the matchers leave them out.
|
|
1266
|
+
*
|
|
1267
|
+
* @example
|
|
1268
|
+
* await expect(orders.footer.cell("Total")).toHaveText("€ 1,295.75");
|
|
1269
|
+
*/
|
|
1270
|
+
get footer(): TableRow<H>;
|
|
1271
|
+
/**
|
|
1272
|
+
* All body rows of the table.
|
|
1273
|
+
*
|
|
1274
|
+
* @example
|
|
1275
|
+
* await expect(table.rows.locator).toHaveCount(3);
|
|
1276
|
+
*
|
|
1277
|
+
* @example
|
|
1278
|
+
* // Compose with Locator.filter and keep typed cell access
|
|
1279
|
+
* await expect(table.rows.filter({ hasText: "Jane" }).cell("Status")).toHaveText("Active");
|
|
1280
|
+
*/
|
|
1281
|
+
get rows(): TableRow<H>;
|
|
1282
|
+
/**
|
|
1283
|
+
* Body rows where every condition matches. Conditions are keyed by header; values are an exact
|
|
1284
|
+
* string, a RegExp, or a {@link CellCondition}. Cell text is compared as the user sees it (see
|
|
1285
|
+
* {@link CellCondition}), and header names are normalized like header text.
|
|
1286
|
+
*
|
|
1287
|
+
* The returned locator follows Playwright's strict mode: acting on it when several rows match throws.
|
|
1288
|
+
*
|
|
1289
|
+
* @param where - At least one condition. Unknown headers are compile-time errors.
|
|
1290
|
+
* @returns A {@link TableRow} for the matching rows.
|
|
1291
|
+
* @throws Error if no condition has a value.
|
|
1292
|
+
*
|
|
1293
|
+
* @example
|
|
1294
|
+
* await expect(table.row({ Name: "John", Status: /active/i }).cell("Email")).toHaveText("john@example.com");
|
|
1295
|
+
*
|
|
1296
|
+
* @example
|
|
1297
|
+
* // Declarative conditions
|
|
1298
|
+
* await table.row({ Name: { startsWith: "Jo" }, Remarks: { empty: true } }).cell("Actions").click();
|
|
1299
|
+
*/
|
|
1300
|
+
row<W extends RowWhere<H>>(where: W & ValidRowWhere<H, W>): TableRow<H>;
|
|
1301
|
+
/** {@link row}, without the compile-time checks of the conditions. */
|
|
1302
|
+
private matchingRows;
|
|
1303
|
+
/**
|
|
1304
|
+
* The body row at `index`.
|
|
1305
|
+
*
|
|
1306
|
+
* @param index - The row number (0-based).
|
|
1307
|
+
* @returns A {@link TableRow} for the row.
|
|
1308
|
+
* @throws Error if index is not a non-negative integer.
|
|
1309
|
+
*
|
|
1310
|
+
* @example
|
|
1311
|
+
* await expect(table.rowAt(0).cell("Name")).toHaveText("John");
|
|
1312
|
+
*/
|
|
1313
|
+
rowAt(index: number): TableRow<H>;
|
|
1314
|
+
/**
|
|
1315
|
+
* The cell under `column` for any row locator, for example one built with `filter()` or `nth()`.
|
|
1316
|
+
*
|
|
1317
|
+
* @param row - A locator for one or more body rows of this table.
|
|
1318
|
+
* @param column - The header key of the column, or its 0-based index (also works without header rows).
|
|
1319
|
+
* @returns A Playwright Locator for the cell.
|
|
1320
|
+
* @throws Error if column is a number that is not a non-negative integer.
|
|
1321
|
+
*
|
|
1322
|
+
* @example
|
|
1323
|
+
* const lastRow = table.rows.locator.last();
|
|
1324
|
+
* await expect(table.cell(lastRow, "Name")).toHaveText("Jane");
|
|
1325
|
+
*/
|
|
1326
|
+
cell(row: Locator, column: ColumnKey<H>): Locator;
|
|
1327
|
+
/**
|
|
1328
|
+
* All body cells under `column`. A cell spanning several rows is included once.
|
|
1329
|
+
*
|
|
1330
|
+
* @param column - The header key of the column, or its 0-based index (also works without header rows).
|
|
1331
|
+
* @returns A Playwright Locator for the cells.
|
|
1332
|
+
* @throws Error if column is a number that is not a non-negative integer.
|
|
1333
|
+
*
|
|
1334
|
+
* @example
|
|
1335
|
+
* await expect(table.column("Status")).toHaveText(["Active", "Inactive", "Active"]);
|
|
1336
|
+
* await expect(table.column(1)).toHaveText(["Active", "Inactive", "Active"]);
|
|
1337
|
+
*/
|
|
1338
|
+
column(column: ColumnKey<H>): Locator;
|
|
1339
|
+
/**
|
|
1340
|
+
* The header cell element for `column` in the main header row.
|
|
1341
|
+
*
|
|
1342
|
+
* @param column - The header key of the column, or its 0-based index.
|
|
1343
|
+
* @returns A Playwright Locator for the header cell.
|
|
1344
|
+
* @throws Error if column is a number that is not a non-negative integer.
|
|
1345
|
+
*
|
|
1346
|
+
* @example
|
|
1347
|
+
* await table.header("Name").click(); // sort by name
|
|
1348
|
+
*/
|
|
1349
|
+
header(column: ColumnKey<H>): Locator;
|
|
1350
|
+
/**
|
|
1351
|
+
* Reads the current header keys of the main header row, exactly as the engine resolves them
|
|
1352
|
+
* (including `__C`/`__D` suffixes and `{{Empty}}`). This is a snapshot, not a locator.
|
|
1353
|
+
*
|
|
1354
|
+
* @param options - Optional timeout for waiting until the table element is attached.
|
|
1355
|
+
* @returns A promise that resolves to the header keys, or an empty array if there are no header rows.
|
|
1356
|
+
*
|
|
1357
|
+
* @example
|
|
1358
|
+
* expect(await table.headerKeys()).toEqual(["Name", "Status", "Email"]);
|
|
1359
|
+
*/
|
|
1360
|
+
headerKeys(options?: {
|
|
1361
|
+
timeout?: number;
|
|
1362
|
+
}): Promise<string[]>;
|
|
1363
|
+
/**
|
|
1364
|
+
* Reads every body row as an object keyed by header key, like `PlaywrightTable.getJson()`. Cells that
|
|
1365
|
+
* span rows or columns are repeated in every position they cover; positions without a cell are `""`.
|
|
1366
|
+
* Cell text is normalized as in row conditions: line breaks stay as one `\n` between trimmed lines.
|
|
1367
|
+
* With `raw: true`, cells are returned exactly as the browser reads them.
|
|
1368
|
+
*
|
|
1369
|
+
* This is a snapshot, not a locator: without `minRows` or `stable` it reads the table once and does not
|
|
1370
|
+
* wait for rows. With `minRows`, it first waits for a header row and at least that many body rows with
|
|
1371
|
+
* content (a row where at least one cell has text), so empty placeholder rows don't count. With
|
|
1372
|
+
* `stable`, it then waits until the table stops changing (see {@link waitForStable}). To wait for
|
|
1373
|
+
* specific content, assert first (for example with `toHaveRow` or `toHaveRows`).
|
|
1374
|
+
*
|
|
1375
|
+
* @param options - Optional settings. See {@link TableJsonOptions}.
|
|
1376
|
+
* @param options.columns - (Optional) Only these columns, in this order. Defaults to every column.
|
|
1377
|
+
* @param options.parse - (Optional) Typed values for some columns: `"number"`, `"date"` or a function.
|
|
1378
|
+
* @param options.decimal - (Optional) The decimal separator of `"number"` columns. Defaults to `"."`.
|
|
1379
|
+
* @param options.minRows - (Optional) Wait for at least this many body rows with content. Defaults to `0` (no waiting).
|
|
1380
|
+
* @param options.stable - (Optional) Wait until the table stops changing before reading it. Defaults to `false`.
|
|
1381
|
+
* @param options.timeout - (Optional) With `minRows` or `stable`, the time to wait in milliseconds (default 5000).
|
|
1382
|
+
* Without them, the time to wait for the table element to be attached.
|
|
1383
|
+
* @param options.raw - (Optional) Return the cell texts without normalization. Defaults to `false`.
|
|
1384
|
+
* @returns A promise that resolves to one object per body row, or an empty array if there are no body rows.
|
|
1385
|
+
* @throws Error if the table has no header row (without `minRows`).
|
|
1386
|
+
* @throws Error if a key of `columns` or `parse` is not a header key of the table, with the available headers.
|
|
1387
|
+
* @throws Error if a cell of a `parse` column cannot be parsed, with the row, the column and the text.
|
|
1388
|
+
* @throws Error if `minRows` is not reached, or the table doesn't become stable, within the timeout.
|
|
1389
|
+
* @throws Error if `minRows` is not a non-negative integer.
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
* expect(await users.toJson()).toEqual([
|
|
1393
|
+
* { Name: "John", Status: "Active" },
|
|
1394
|
+
* { Name: "Jane", Status: "Inactive" },
|
|
1395
|
+
* ]);
|
|
1396
|
+
*
|
|
1397
|
+
* @example
|
|
1398
|
+
* // Wait until at least one row is loaded
|
|
1399
|
+
* const data = await users.toJson({ minRows: 1 });
|
|
1400
|
+
*
|
|
1401
|
+
* @example
|
|
1402
|
+
* // Some columns, with typed values: { Id: string; Total: number | null }[]
|
|
1403
|
+
* const totals = await orders.toJson({ columns: ["Id", "Total"], parse: { Total: "number" } });
|
|
1404
|
+
*/
|
|
1405
|
+
toJson<const C extends HeaderKey<H> = HeaderKey<H>, const P extends ColumnParsers<C> = Record<never, never>>(options?: TableJsonOptions<C, P>): Promise<TableJsonRow<C, P>[]>;
|
|
1406
|
+
/**
|
|
1407
|
+
* Reads the text of every body cell under `column`, one per body row. A cell that spans rows is
|
|
1408
|
+
* repeated in every row it covers; a row without a cell there reads as `""`. Text is normalized as in
|
|
1409
|
+
* row conditions.
|
|
1410
|
+
*
|
|
1411
|
+
* This is a snapshot, not a locator: it reads the table once. To wait for content, assert first, or
|
|
1412
|
+
* use `toHaveColumnValues`.
|
|
1413
|
+
*
|
|
1414
|
+
* @param column - The header key of the column, or its 0-based index (also works without header rows).
|
|
1415
|
+
* @param options - Optional timeout for waiting until the table element is attached.
|
|
1416
|
+
* @returns A promise that resolves to the cell texts, or an empty array if there are no body rows.
|
|
1417
|
+
* @throws Error if the table has no such column, with the available headers.
|
|
1418
|
+
*
|
|
1419
|
+
* @example
|
|
1420
|
+
* expect(await users.columnValues("Status")).toEqual(["Active", "Inactive", "Active"]);
|
|
1421
|
+
*
|
|
1422
|
+
* @example
|
|
1423
|
+
* // The distinct values of a column
|
|
1424
|
+
* const statuses = new Set(await users.columnValues("Status"));
|
|
1425
|
+
*/
|
|
1426
|
+
columnValues(column: ColumnKey<H>, options?: {
|
|
1427
|
+
timeout?: number;
|
|
1428
|
+
}): Promise<string[]>;
|
|
1429
|
+
/**
|
|
1430
|
+
* Waits until the table stops changing: the header keys and the text of every body cell stay the same
|
|
1431
|
+
* for `quiet` milliseconds, and the `loading` indicator (if any) is hidden. Use it after an action that
|
|
1432
|
+
* refreshes the table (searching, filtering, paging, sorting) before you read it with a snapshot method
|
|
1433
|
+
* such as {@link toJson}.
|
|
1434
|
+
*
|
|
1435
|
+
* Locators and the table matchers retry by themselves and don't need it. A table that has not started
|
|
1436
|
+
* to refresh yet looks stable, so pass `loading` when the table shows an indicator while it loads.
|
|
1437
|
+
*
|
|
1438
|
+
* @param options - Optional settings. See {@link StableOptions}.
|
|
1439
|
+
* @param options.quiet - (Optional) How long nothing may change, in milliseconds. Defaults to 500.
|
|
1440
|
+
* @param options.timeout - (Optional) The time to wait in milliseconds. Defaults to 5000.
|
|
1441
|
+
* @param options.loading - (Optional) A loading indicator that must be hidden. Defaults to the `loading` option of the table.
|
|
1442
|
+
* @returns A promise that resolves once the table is stable.
|
|
1443
|
+
* @throws Error if the table is not stable within the timeout: it kept changing, the loading indicator
|
|
1444
|
+
* stayed visible, or the table could not be read.
|
|
1445
|
+
*
|
|
1446
|
+
* @example
|
|
1447
|
+
* await page.getByLabel("Search").fill("Jo");
|
|
1448
|
+
* await users.waitForStable({ loading: page.locator(".spinner") });
|
|
1449
|
+
* const rows = await users.toJson();
|
|
1450
|
+
*/
|
|
1451
|
+
waitForStable(options?: StableOptions): Promise<void>;
|
|
1452
|
+
/**
|
|
1453
|
+
* Finds a body row on any page or scroll position: it searches the rows in view, then (with the
|
|
1454
|
+
* `paginate` and `scroll` options) goes through the pages from the first one (with `paginate.first`)
|
|
1455
|
+
* and scrolls each page from the top, until a row matches. The table is left showing that row, and the
|
|
1456
|
+
* returned row locator resolves to it.
|
|
1457
|
+
*
|
|
1458
|
+
* Without `paginate` and `scroll`, it waits until a row in view matches, like a locator.
|
|
1459
|
+
*
|
|
1460
|
+
* @param where - At least one condition, as for {@link row}.
|
|
1461
|
+
* @param options - Optional timeout for the whole search, in milliseconds. Defaults to 30000.
|
|
1462
|
+
* @returns A promise that resolves to the {@link TableRow} for `row(where)`.
|
|
1463
|
+
* @throws Error if no row matches on any page (or within `paginate.maxPages` and `scroll.maxSteps`), with
|
|
1464
|
+
* the number of pages and scroll steps searched, and whether the search stopped early at the timeout.
|
|
1465
|
+
*
|
|
1466
|
+
* @example
|
|
1467
|
+
* const orders = defineTable(page.getByRole("grid"), {
|
|
1468
|
+
* aria: true,
|
|
1469
|
+
* paginate: { next: page.getByRole("button", { name: "Next page" }) },
|
|
1470
|
+
* });
|
|
1471
|
+
* const row = await orders.findRow({ Id: "1042" });
|
|
1472
|
+
* await row.cell("Status").click();
|
|
1473
|
+
*/
|
|
1474
|
+
findRow<W extends RowWhere<H>>(where: W & ValidRowWhere<H, W>, options?: NavigationOptions): Promise<TableRow<H>>;
|
|
1475
|
+
/**
|
|
1476
|
+
* The number of body rows in the whole table, also the rows that are not rendered or are on other pages.
|
|
1477
|
+
*
|
|
1478
|
+
* - A grid with `aria-rowcount` (on the root, inside it or around it) declares it: `aria-rowcount` minus
|
|
1479
|
+
* the rows it counts that are not body rows, such as header rows.
|
|
1480
|
+
* - Otherwise, with the `paginate` or `scroll` option, every row is read, as `toJson({ allRows: true })` does.
|
|
1481
|
+
* That leaves the table on its last page, scrolled down.
|
|
1482
|
+
* - Otherwise, the number of body rows.
|
|
1483
|
+
*
|
|
1484
|
+
* @param options - Optional timeout, in milliseconds. Defaults to 30000.
|
|
1485
|
+
* @returns A promise that resolves to the number of body rows.
|
|
1486
|
+
* @throws Error if it reads every row and the timeout passes before the end of the table.
|
|
1487
|
+
*
|
|
1488
|
+
* @example
|
|
1489
|
+
* expect(await grid.totalRowCount()).toBe(200);
|
|
1490
|
+
*/
|
|
1491
|
+
totalRowCount(options?: NavigationOptions): Promise<number>;
|
|
1492
|
+
/**
|
|
1493
|
+
* The body rows in view as an aligned text table, with the row index in front, for logs and debugging.
|
|
1494
|
+
* Cells longer than 30 characters are cut off, and line breaks show as `↵`.
|
|
1495
|
+
*
|
|
1496
|
+
* @param options - Optional settings.
|
|
1497
|
+
* @param options.columns - (Optional) Only these columns. Unknown columns are skipped; if none is known,
|
|
1498
|
+
* every column is shown. Defaults to every column.
|
|
1499
|
+
* @param options.maxRows - (Optional) The most rows to show. Defaults to 50.
|
|
1500
|
+
* @param options.timeout - (Optional) The time to wait for the table, in milliseconds.
|
|
1501
|
+
* @returns A promise that resolves to the table as text.
|
|
1502
|
+
*
|
|
1503
|
+
* @example
|
|
1504
|
+
* console.log(await users.format());
|
|
1505
|
+
* // # | Name | Status
|
|
1506
|
+
* // 0 | John | Active
|
|
1507
|
+
* // 1 | Jane | Inactive
|
|
1508
|
+
*/
|
|
1509
|
+
format(options?: {
|
|
1510
|
+
columns?: readonly ColumnKey<H>[];
|
|
1511
|
+
maxRows?: number;
|
|
1512
|
+
timeout?: number;
|
|
1513
|
+
}): Promise<string>;
|
|
1514
|
+
/**
|
|
1515
|
+
* Waits until every expected header exists, and fails fast with the available headers if they don't.
|
|
1516
|
+
*
|
|
1517
|
+
* @param expected - The headers to check. Defaults to the `headers` option of this table.
|
|
1518
|
+
* @param options - Optional timeout in milliseconds. Defaults to 5000.
|
|
1519
|
+
* @returns A promise that resolves once all headers exist.
|
|
1520
|
+
* @throws Error listing the missing and available headers when the timeout is reached.
|
|
1521
|
+
* @throws Error if there are no headers to check.
|
|
1522
|
+
*
|
|
1523
|
+
* @example
|
|
1524
|
+
* const users = new TableLocator(page.locator("table"), { headers: ["Name", "Status"] });
|
|
1525
|
+
* await users.validateHeaders();
|
|
1526
|
+
* // Error: Missing headers: [Status]
|
|
1527
|
+
* // Available headers: [Name, State]
|
|
1528
|
+
*/
|
|
1529
|
+
validateHeaders(expected?: readonly HeaderKey<H>[], options?: {
|
|
1530
|
+
timeout?: number;
|
|
1531
|
+
}): Promise<void>;
|
|
1532
|
+
/** The texts for `toJson()`: read once, or after waiting for `minRows` and `stable`. */
|
|
1533
|
+
private readJsonTexts;
|
|
1534
|
+
/**
|
|
1535
|
+
* The waits of `toJson()`: for `minRows` (with the texts it read), then until the table is `stable`,
|
|
1536
|
+
* each within the time `left`.
|
|
1537
|
+
*/
|
|
1538
|
+
private waitForJson;
|
|
1539
|
+
/** @throws Error if the table has no header row, which `toJson()` needs for its keys. */
|
|
1540
|
+
private checkJsonHeader;
|
|
1541
|
+
/**
|
|
1542
|
+
* Checks the `columns` and `parse` options of `toJson()` against the header keys of the table.
|
|
1543
|
+
* @throws Error with the available headers if a key is not a header key, or a `parse` key is not in `columns`.
|
|
1544
|
+
*/
|
|
1545
|
+
private checkJsonColumns;
|
|
1546
|
+
/** The loading indicator for `loading`, or else the `loading` option of the table. */
|
|
1547
|
+
private resolveLoading;
|
|
1548
|
+
/** A fingerprint of the header keys and body cell texts, and the number of body rows. */
|
|
1549
|
+
private fingerprint;
|
|
1550
|
+
/**
|
|
1551
|
+
* Reads every row on every page and scroll position. Rows are told apart by their key per page, and
|
|
1552
|
+
* ordered by their index when every row has one. Header keys are merged across positions, so a grid
|
|
1553
|
+
* that renders only some columns at a time still gets every column it showed.
|
|
1554
|
+
*
|
|
1555
|
+
* @throws Error if the deadline passes before every row is read (up to `paginate.maxPages` and
|
|
1556
|
+
* `scroll.maxSteps`), so part of the rows is never returned as all of them.
|
|
1557
|
+
*/
|
|
1558
|
+
private readAllTexts;
|
|
1559
|
+
private readTexts;
|
|
1560
|
+
/** Reads the table once it has a header row and at least `minRows` body rows with content. */
|
|
1561
|
+
private readTextsWithRows;
|
|
1562
|
+
private selector;
|
|
484
1563
|
}
|
|
485
|
-
|
|
486
|
-
|
|
1564
|
+
/**
|
|
1565
|
+
* Creates a {@link TableLocator}. Header names passed in `options.headers` are inferred as literal
|
|
1566
|
+
* types, so header arguments are checked and autocompleted.
|
|
1567
|
+
*
|
|
1568
|
+
* @param root - The Playwright `Locator` pointing to the root table element.
|
|
1569
|
+
* @param options - Optional configuration. See {@link TableLocatorOptions}.
|
|
1570
|
+
* @returns A new {@link TableLocator}.
|
|
1571
|
+
*
|
|
1572
|
+
* @example
|
|
1573
|
+
* const users = defineTable(page.locator("#users"), { headers: ["Name", "Status", "Email"] });
|
|
1574
|
+
* await expect(users.row({ Name: "John" }).cell("Email")).toHaveText("john@example.com");
|
|
1575
|
+
* // @ts-expect-error "Nme" is not a header of this table
|
|
1576
|
+
* users.row({ Nme: "John" });
|
|
1577
|
+
*/
|
|
1578
|
+
export declare function defineTable<const H extends string = string>(root: Locator, options?: TableLocatorOptions<H>): TableLocator<H>;
|
|
1579
|
+
/**
|
|
1580
|
+
* {@link TableLocatorOptions} without `headers`, so a preset can be spread into options with your own
|
|
1581
|
+
* typed headers.
|
|
1582
|
+
*/
|
|
1583
|
+
type TablePreset = Omit<TableLocatorOptions<string>, "headers">;
|
|
1584
|
+
/**
|
|
1585
|
+
* Options for AG Grid (tested with AG Grid Community 36): ARIA roles, the header rows without the
|
|
1586
|
+
* floating filter row, the pinned top and bottom rows (such as totals) as footer rows, the body viewport to scroll, the
|
|
1587
|
+
* pagination panel, and the loading overlay. The table root is the element you created the grid in, or
|
|
1588
|
+
* any element inside it that holds the whole grid.
|
|
1589
|
+
*
|
|
1590
|
+
* AG Grid also renders only the columns in view (column virtualization). Rows are read with every
|
|
1591
|
+
* column AG Grid has rendered; to read columns outside the view, turn it off with
|
|
1592
|
+
* `suppressColumnVirtualisation: true`, or make the grid wide enough in your tests.
|
|
1593
|
+
*
|
|
1594
|
+
* @example
|
|
1595
|
+
* const cars = defineTable(page.locator("#myGrid"), { ...presets.agGrid(), headers: ["Make", "Model", "Price"] });
|
|
1596
|
+
* const row = await cars.findRow({ Model: "Model 150" });
|
|
1597
|
+
* await expect(cars).toHaveTotalRowCount(200);
|
|
1598
|
+
*/
|
|
1599
|
+
declare function agGrid(): TablePreset;
|
|
1600
|
+
/**
|
|
1601
|
+
* Options for MUI X Data Grid (based on the markup of version 7 and 8): ARIA roles, the row and cell
|
|
1602
|
+
* classes (without filler and skeleton cells), the virtual scroller, the pagination buttons, and the
|
|
1603
|
+
* loading indicator. The table root is the `.MuiDataGrid-root` element, such as `page.getByRole("grid")`.
|
|
1604
|
+
*
|
|
1605
|
+
* @example
|
|
1606
|
+
* const users = defineTable(page.getByRole("grid"), presets.muiDataGrid());
|
|
1607
|
+
* await expect(users).toHaveTotalRowCount(100);
|
|
1608
|
+
*/
|
|
1609
|
+
declare function muiDataGrid(): TablePreset;
|
|
1610
|
+
/**
|
|
1611
|
+
* Options for an Angular Material table (`mat-table`, as a native `<table>` or with the `<mat-table>`
|
|
1612
|
+
* elements), in both the MDC (`mat-mdc-*`) and the legacy class names. The "no data" row is not a body
|
|
1613
|
+
* row, and footer rows are footer rows. With `paginator`, the `mat-paginator` that pages the table: a
|
|
1614
|
+
* `Locator`, or a function that finds it from the table root.
|
|
1615
|
+
*
|
|
1616
|
+
* @example
|
|
1617
|
+
* const orders = defineTable(page.locator("table[mat-table]"), presets.angularMaterial({ paginator: page.locator("mat-paginator") }));
|
|
1618
|
+
* const row = await orders.findRow({ Id: "42" });
|
|
1619
|
+
*/
|
|
1620
|
+
declare function angularMaterial(options?: {
|
|
1621
|
+
paginator?: LocatorRef;
|
|
1622
|
+
}): TablePreset;
|
|
1623
|
+
/**
|
|
1624
|
+
* Options for a table virtualized with TanStack Virtual (`@tanstack/react-virtual` and friends), which
|
|
1625
|
+
* renders only the rows in view and marks each with `data-index`. TanStack Table itself renders plain
|
|
1626
|
+
* HTML tables, which work without a preset.
|
|
1627
|
+
*
|
|
1628
|
+
* @param options.container - The element that scrolls, usually a `div` around the table.
|
|
1629
|
+
*
|
|
1630
|
+
* @example
|
|
1631
|
+
* const rows = defineTable(page.locator("table"), presets.tanstackVirtual({ container: page.locator(".scroll-container") }));
|
|
1632
|
+
* const all = await rows.toJson({ allRows: true });
|
|
1633
|
+
*/
|
|
1634
|
+
declare function tanstackVirtual(options: {
|
|
1635
|
+
container: Locator;
|
|
1636
|
+
}): TablePreset;
|
|
1637
|
+
/**
|
|
1638
|
+
* Ready-made {@link TableLocatorOptions} for popular data grids. Spread a preset into your own options to
|
|
1639
|
+
* add typed headers or change a setting.
|
|
1640
|
+
*
|
|
1641
|
+
* @example
|
|
1642
|
+
* const grid = defineTable(page.locator("#grid"), { ...presets.agGrid(), headers: ["Id", "Name"] });
|
|
1643
|
+
*/
|
|
1644
|
+
export declare const presets: {
|
|
1645
|
+
agGrid: typeof agGrid;
|
|
1646
|
+
muiDataGrid: typeof muiDataGrid;
|
|
1647
|
+
angularMaterial: typeof angularMaterial;
|
|
1648
|
+
tanstackVirtual: typeof tanstackVirtual;
|
|
1649
|
+
};
|
|
1650
|
+
/**
|
|
1651
|
+
* Registers the `pw-table-v1` selector engine used by {@link TableLocator}.
|
|
1652
|
+
*
|
|
1653
|
+
* Safe to call more than once: later calls reuse the first registration, and an
|
|
1654
|
+
* "already registered" error (for example from another copy of this package) is ignored.
|
|
1655
|
+
*
|
|
1656
|
+
* Registration must happen **before the page is created**. You don't need this function if you
|
|
1657
|
+
* import `test` from this package, which registers the engine with a worker-scoped auto fixture.
|
|
1658
|
+
*
|
|
1659
|
+
* @param target - The Playwright `selectors` object to register on. Defaults to `selectors` from `@playwright/test`.
|
|
1660
|
+
* @returns A promise that resolves once the engine is registered.
|
|
1661
|
+
*
|
|
1662
|
+
* @example
|
|
1663
|
+
* // With your own test fixtures
|
|
1664
|
+
* import { test as base } from "@playwright/test";
|
|
1665
|
+
* import { registerTableSelectors } from "@cerios/playwright-table";
|
|
1666
|
+
*
|
|
1667
|
+
* export const test = base.extend<{}, { tableSelectors: void }>({
|
|
1668
|
+
* tableSelectors: [async ({ playwright }, use) => {
|
|
1669
|
+
* await registerTableSelectors(playwright.selectors);
|
|
1670
|
+
* await use();
|
|
1671
|
+
* }, { scope: "worker", auto: true }],
|
|
1672
|
+
* });
|
|
1673
|
+
*
|
|
1674
|
+
* @example
|
|
1675
|
+
* // Without fixtures
|
|
1676
|
+
* test.beforeAll(async () => {
|
|
1677
|
+
* await registerTableSelectors();
|
|
1678
|
+
* });
|
|
1679
|
+
*/
|
|
1680
|
+
export declare function registerTableSelectors(target?: Selectors): Promise<void>;
|
|
1681
|
+
/**
|
|
1682
|
+
* Playwright `test` with the `pw-table-v1` selector engine registered for every worker, so
|
|
1683
|
+
* {@link TableLocator} works without any setup. Use it instead of `test` from `@playwright/test`.
|
|
1684
|
+
*
|
|
1685
|
+
* The engine is registered by a worker-scoped auto fixture, so it is always registered before
|
|
1686
|
+
* any page is created.
|
|
1687
|
+
*
|
|
1688
|
+
* @example
|
|
1689
|
+
* import { test, expect, defineTable } from "@cerios/playwright-table";
|
|
1690
|
+
*
|
|
1691
|
+
* test("shows the user", async ({ page }) => {
|
|
1692
|
+
* const users = defineTable(page.locator("table"), { headers: ["Name", "Status"] });
|
|
1693
|
+
* await expect(users.row({ Name: "John" }).cell("Status")).toHaveText("Active");
|
|
1694
|
+
* });
|
|
1695
|
+
*/
|
|
1696
|
+
export declare const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
|
|
1697
|
+
_pwTableSelectors: void;
|
|
1698
|
+
}>;
|
|
1699
|
+
/**
|
|
1700
|
+
* The default `ignore` option of {@link TableLocator}: CSS selectors of the elements inside a cell whose
|
|
1701
|
+
* text is left out. They cover screen reader only text (`.sr-only`, `.visually-hidden`), ligature icons
|
|
1702
|
+
* (Material Icons and Material Symbols, whose text such as `arrow_upward` is drawn as an icon) and
|
|
1703
|
+
* decorative content (`[aria-hidden=true]`).
|
|
1704
|
+
*
|
|
1705
|
+
* Spread it to add your own selectors: `ignore: [...DEFAULT_IGNORE, ".sort-icon"]`.
|
|
1706
|
+
*/
|
|
1707
|
+
export declare const DEFAULT_IGNORE: readonly string[];
|
|
1708
|
+
/**
|
|
1709
|
+
* What a matcher returns to `expect.extend`. Declared here instead of importing `MatcherReturnType`,
|
|
1710
|
+
* which older versions of `@playwright/test` within the peer range don't export.
|
|
1711
|
+
*/
|
|
1712
|
+
type MatcherResult = {
|
|
1713
|
+
name: string;
|
|
1714
|
+
pass: boolean;
|
|
1715
|
+
message: () => string;
|
|
1716
|
+
expected?: unknown;
|
|
1717
|
+
actual?: unknown;
|
|
1718
|
+
timeout?: number;
|
|
1719
|
+
};
|
|
1720
|
+
/** Options shared by the table matchers. */
|
|
1721
|
+
type TableMatcherOptions = {
|
|
1722
|
+
/** Time to retry the assertion for, in milliseconds. Defaults to the `expect` timeout. */
|
|
1723
|
+
timeout?: number;
|
|
1724
|
+
};
|
|
1725
|
+
/** Options for the `toBeSortedBy` matcher. */
|
|
1726
|
+
type TableSortOptions = TableMatcherOptions & {
|
|
1727
|
+
/** The expected order. Defaults to `"asc"`. */
|
|
1728
|
+
order?: "asc" | "desc";
|
|
1729
|
+
/**
|
|
1730
|
+
* How cell texts are compared. Defaults to `"text"`.
|
|
1731
|
+
* - `"text"`: case- and accent-insensitive, with digit runs compared as numbers (`"item 9"` before
|
|
1732
|
+
* `"item 10"`).
|
|
1733
|
+
* - `"number"`: the one number in the text, read with the `decimal` separator (`"€ 1,200"` is 1200).
|
|
1734
|
+
* See {@link DecimalSeparator} for the rules.
|
|
1735
|
+
* - `"date"`: parsed with `Date.parse`.
|
|
1736
|
+
*/
|
|
1737
|
+
type?: "text" | "number" | "date";
|
|
1738
|
+
/** The decimal separator for `type: "number"`. Defaults to `"."`. */
|
|
1739
|
+
decimal?: DecimalSeparator;
|
|
1740
|
+
/**
|
|
1741
|
+
* Custom comparison of two cell texts, like `Array.sort`. Runs in Node, so it can use test variables.
|
|
1742
|
+
* Overrides `type`.
|
|
1743
|
+
*/
|
|
1744
|
+
compare?: (a: string, b: string) => number;
|
|
1745
|
+
/** Also require `aria-sort="ascending"` or `"descending"` on the header cell. Defaults to `false`. */
|
|
1746
|
+
ariaSort?: boolean;
|
|
1747
|
+
};
|
|
1748
|
+
/** Options for the `toHaveRows` and `toHaveColumnValues` matchers. */
|
|
1749
|
+
type TableRowsOptions = TableMatcherOptions & {
|
|
1750
|
+
/**
|
|
1751
|
+
* Accept the rows (or values) in any order: each expected entry must match a different body row.
|
|
1752
|
+
* Defaults to `false`: body row `i` must match entry `i`.
|
|
1753
|
+
*/
|
|
1754
|
+
anyOrder?: boolean;
|
|
1755
|
+
};
|
|
1756
|
+
/**
|
|
1757
|
+
* Asserts that the table has at least one body row matching every condition (or none, with `.not`).
|
|
1758
|
+
* Retries until the timeout. On failure, unknown headers are reported with the available headers.
|
|
1759
|
+
*
|
|
1760
|
+
* Conditions are checked against header names at runtime. With the `expect` of this package, they are
|
|
1761
|
+
* also checked at compile time against the `headers` of the table.
|
|
1762
|
+
*/
|
|
1763
|
+
declare function toHaveRow(this: ExpectMatcherState, table: TableLocator, where: RowWhere<string>, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1764
|
+
/**
|
|
1765
|
+
* Asserts that the table has exactly `count` body rows. Retries until the timeout.
|
|
1766
|
+
*/
|
|
1767
|
+
declare function toHaveRowCount(this: ExpectMatcherState, table: TableLocator, count: number, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1768
|
+
/**
|
|
1769
|
+
* Asserts that the main header row contains every given header key, or with `exact: true`, that it
|
|
1770
|
+
* equals the given keys in order. Retries until the timeout.
|
|
1771
|
+
*/
|
|
1772
|
+
declare function toHaveHeaders(this: ExpectMatcherState, table: TableLocator, headers: readonly string[], options?: TableMatcherOptions & {
|
|
1773
|
+
/** Require exactly these header keys, in this order. Defaults to `false`. */
|
|
1774
|
+
exact?: boolean;
|
|
1775
|
+
}): Promise<MatcherResult>;
|
|
1776
|
+
/**
|
|
1777
|
+
* Asserts that a {@link TableRow} resolves to exactly one row whose cells match every condition.
|
|
1778
|
+
* With `.not`, the row must exist and not match. Retries until the timeout.
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* await expect(users.row({ Name: "John" })).toHaveCells({ Status: "Active", Email: /@example\.com$/ });
|
|
1782
|
+
* await expect(users.rowAt(0)).not.toHaveCells({ Status: "Deleted" });
|
|
1783
|
+
*/
|
|
1784
|
+
declare function toHaveCells(this: ExpectMatcherState, row: TableRow, cells: RowWhere<string>, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1785
|
+
/**
|
|
1786
|
+
* Asserts that the table has exactly as many body rows as `rows`, and that body row `i` matches every
|
|
1787
|
+
* condition of `rows[i]`. Headers that a row does not mention are not checked, so `{}` matches any row.
|
|
1788
|
+
* With `anyOrder: true`, the rows may come in any order: each entry must match a different body row.
|
|
1789
|
+
* With `.not`, the table must not match. Retries until the timeout.
|
|
1790
|
+
*
|
|
1791
|
+
* @example
|
|
1792
|
+
* await expect(users).toHaveRows([
|
|
1793
|
+
* { Name: "John", Status: "Active" },
|
|
1794
|
+
* { Name: "Jane", Status: /inactive/i },
|
|
1795
|
+
* ]);
|
|
1796
|
+
*/
|
|
1797
|
+
declare function toHaveRows(this: ExpectMatcherState, table: TableLocator, rows: readonly RowWhere<string>[], options?: TableRowsOptions): Promise<MatcherResult>;
|
|
1798
|
+
/**
|
|
1799
|
+
* Asserts that every entry of `rows` matches a different body row, in any order; other body rows are
|
|
1800
|
+
* allowed. With `.not`, at least one entry has no body row of its own. Retries until the timeout.
|
|
1801
|
+
*
|
|
1802
|
+
* @example
|
|
1803
|
+
* await expect(users).toContainRows([
|
|
1804
|
+
* { Name: "Joe", Status: "Active" },
|
|
1805
|
+
* { Name: "John" },
|
|
1806
|
+
* ]);
|
|
1807
|
+
*/
|
|
1808
|
+
declare function toContainRows(this: ExpectMatcherState, table: TableLocator, rows: readonly RowWhere<string>[], options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1809
|
+
/**
|
|
1810
|
+
* Asserts that the table has body rows and that every one of them matches `where`, for example after
|
|
1811
|
+
* filtering. With `.not`, at least one body row doesn't match. A table without body rows fails both,
|
|
1812
|
+
* so a filter that leaves nothing never passes. Retries until the timeout.
|
|
1813
|
+
*
|
|
1814
|
+
* @example
|
|
1815
|
+
* await page.getByLabel("Status").selectOption("Active");
|
|
1816
|
+
* await expect(users).toHaveEveryRow({ Status: "Active" });
|
|
1817
|
+
*/
|
|
1818
|
+
declare function toHaveEveryRow(this: ExpectMatcherState, table: TableLocator, where: RowWhere<string>, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1819
|
+
/**
|
|
1820
|
+
* Asserts that `column` has exactly these cell values, one per body row: body row `i` matches `values[i]`,
|
|
1821
|
+
* or with `anyOrder: true`, each value matches a different body row. Values are an exact string, a RegExp
|
|
1822
|
+
* or a {@link CellCondition}, compared as in row conditions. With `.not`, the column must not match.
|
|
1823
|
+
* Retries until the timeout.
|
|
1824
|
+
*
|
|
1825
|
+
* @example
|
|
1826
|
+
* await expect(users).toHaveColumnValues("Status", ["Active", "Inactive", "Active"]);
|
|
1827
|
+
* await expect(orders).toHaveColumnValues("Total", [{ gt: 0 }, { gt: 0 }], { anyOrder: true });
|
|
1828
|
+
*/
|
|
1829
|
+
declare function toHaveColumnValues(this: ExpectMatcherState, table: TableLocator, column: string | number, values: readonly CellMatcher[], options?: TableRowsOptions): Promise<MatcherResult>;
|
|
1830
|
+
/**
|
|
1831
|
+
* Asserts that the body rows are sorted by `column`. Values are compared in Node, so `compare` can use
|
|
1832
|
+
* variables from the test. An empty body, an unknown column or a value that cannot be parsed fails the
|
|
1833
|
+
* assertion, also with `.not`. Retries until the timeout.
|
|
1834
|
+
*
|
|
1835
|
+
* @example
|
|
1836
|
+
* await users.header("Age").click();
|
|
1837
|
+
* await expect(users).toBeSortedBy("Age", { type: "number" });
|
|
1838
|
+
* await expect(users).toBeSortedBy("Name", { order: "desc", ariaSort: true });
|
|
1839
|
+
*/
|
|
1840
|
+
declare function toBeSortedBy(this: ExpectMatcherState, table: TableLocator, column: string | number, options?: TableSortOptions): Promise<MatcherResult>;
|
|
1841
|
+
/**
|
|
1842
|
+
* Asserts the number of body rows in the whole table, also the rows that are not rendered or are on
|
|
1843
|
+
* other pages (see {@link TableLocator.totalRowCount}). Retries until the timeout.
|
|
1844
|
+
*
|
|
1845
|
+
* @example
|
|
1846
|
+
* await expect(grid).toHaveTotalRowCount(200);
|
|
1847
|
+
*/
|
|
1848
|
+
declare function toHaveTotalRowCount(this: ExpectMatcherState, table: TableLocator, count: number, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1849
|
+
/**
|
|
1850
|
+
* Asserts that a {@link TableRow} is selected: it has `aria-selected="true"`, or the first checkbox in it
|
|
1851
|
+
* is checked. Retries until the timeout.
|
|
1852
|
+
*
|
|
1853
|
+
* @example
|
|
1854
|
+
* await users.row({ Name: "John" }).select();
|
|
1855
|
+
* await expect(users.row({ Name: "John" })).toBeSelected();
|
|
1856
|
+
*/
|
|
1857
|
+
declare function toBeSelected(this: ExpectMatcherState, row: TableRow, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1858
|
+
/**
|
|
1859
|
+
* Asserts that a {@link TableRow} of a tree grid is expanded (see {@link TableRow.isExpanded}). A row that
|
|
1860
|
+
* cannot expand fails both the assertion and its negation. Retries until the timeout.
|
|
1861
|
+
*
|
|
1862
|
+
* @example
|
|
1863
|
+
* await expect(files.row({ Name: "src" })).not.toBeExpanded();
|
|
1864
|
+
*/
|
|
1865
|
+
declare function toBeExpanded(this: ExpectMatcherState, row: TableRow, options?: TableMatcherOptions): Promise<MatcherResult>;
|
|
1866
|
+
/**
|
|
1867
|
+
* Custom `expect` matchers for {@link TableLocator} and {@link TableRow}. Use them to extend your own `expect`.
|
|
1868
|
+
*
|
|
1869
|
+
* - `toHaveRow(where, options?)`: at least one body row matches every condition.
|
|
1870
|
+
* - `toHaveRowCount(count, options?)`: exactly `count` body rows.
|
|
1871
|
+
* - `toHaveRows(rows, options?)`: exactly these body rows, in order (or `anyOrder`), each matching its conditions.
|
|
1872
|
+
* - `toContainRows(rows, options?)`: each entry matches a different body row, in any order; other rows are allowed.
|
|
1873
|
+
* - `toHaveEveryRow(where, options?)`: there are body rows, and every one matches the conditions.
|
|
1874
|
+
* - `toHaveColumnValues(column, values, options?)`: one value per body row, in order (or `anyOrder`).
|
|
1875
|
+
* - `toHaveHeaders(headers, options?)`: the main header row contains these header keys (`exact` for an ordered match).
|
|
1876
|
+
* - `toBeSortedBy(column, options?)`: the body rows are sorted by a column (see {@link TableSortOptions}).
|
|
1877
|
+
* - `toHaveTotalRowCount(count, options?)`: the number of body rows in the whole table, on every page.
|
|
1878
|
+
* - `toHaveCells(cells, options?)` on a {@link TableRow}: exactly one row, whose cells match every condition.
|
|
1879
|
+
* - `toBeSelected(options?)` on a {@link TableRow}: the row is selected.
|
|
1880
|
+
* - `toBeExpanded(options?)` on a {@link TableRow}: the row of a tree grid is expanded.
|
|
1881
|
+
*
|
|
1882
|
+
* When a table matcher fails inside a test, the body rows are attached to the test report as a Markdown
|
|
1883
|
+
* table, once per test and table.
|
|
1884
|
+
*
|
|
1885
|
+
* @example
|
|
1886
|
+
* // Combine with your own matchers
|
|
1887
|
+
* import { expect as base } from "@playwright/test";
|
|
1888
|
+
* import { tableMatchers } from "@cerios/playwright-table";
|
|
1889
|
+
*
|
|
1890
|
+
* export const expect = base.extend({
|
|
1891
|
+
* ...tableMatchers,
|
|
1892
|
+
* async toHaveEmailFor(table: TableLocator, name: string) { ... },
|
|
1893
|
+
* });
|
|
1894
|
+
*
|
|
1895
|
+
* @example
|
|
1896
|
+
* // Or merge with another extended expect
|
|
1897
|
+
* import { mergeExpects } from "@playwright/test";
|
|
1898
|
+
* import { expect as tableExpect } from "@cerios/playwright-table";
|
|
1899
|
+
*
|
|
1900
|
+
* export const expect = mergeExpects(tableExpect, myExpect);
|
|
1901
|
+
*/
|
|
1902
|
+
export declare const tableMatchers: {
|
|
1903
|
+
toHaveRow: typeof toHaveRow;
|
|
1904
|
+
toHaveRowCount: typeof toHaveRowCount;
|
|
1905
|
+
toHaveRows: typeof toHaveRows;
|
|
1906
|
+
toContainRows: typeof toContainRows;
|
|
1907
|
+
toHaveEveryRow: typeof toHaveEveryRow;
|
|
1908
|
+
toHaveColumnValues: typeof toHaveColumnValues;
|
|
1909
|
+
toHaveHeaders: typeof toHaveHeaders;
|
|
1910
|
+
toHaveCells: typeof toHaveCells;
|
|
1911
|
+
toBeSortedBy: typeof toBeSortedBy;
|
|
1912
|
+
toHaveTotalRowCount: typeof toHaveTotalRowCount;
|
|
1913
|
+
toBeSelected: typeof toBeSelected;
|
|
1914
|
+
toBeExpanded: typeof toBeExpanded;
|
|
1915
|
+
};
|
|
1916
|
+
/**
|
|
1917
|
+
* Playwright `expect` extended with {@link tableMatchers}.
|
|
1918
|
+
*
|
|
1919
|
+
* @example
|
|
1920
|
+
* import { test, expect, defineTable } from "@cerios/playwright-table";
|
|
1921
|
+
*
|
|
1922
|
+
* test("users", async ({ page }) => {
|
|
1923
|
+
* const users = defineTable(page.locator("table"), { headers: ["Name", "Status"] });
|
|
1924
|
+
* await expect(users).toHaveHeaders(["Name", "Status"]);
|
|
1925
|
+
* await expect(users).toHaveRow({ Name: "John", Status: /active/i });
|
|
1926
|
+
* await expect(users).not.toHaveRow({ Name: "Ghost" });
|
|
1927
|
+
* await expect(users).toHaveRowCount(3);
|
|
1928
|
+
* await expect(users).toHaveRows([{ Name: "John" }, { Name: "Jane" }, { Name: "Joe" }]);
|
|
1929
|
+
* await expect(users).toContainRows([{ Name: "Joe" }, { Name: "John" }]);
|
|
1930
|
+
* await expect(users).toHaveColumnValues("Status", ["Active", "Inactive", "Active"]);
|
|
1931
|
+
* await users.header("Name").click();
|
|
1932
|
+
* await expect(users).toBeSortedBy("Name");
|
|
1933
|
+
* await expect(users.row({ Name: "John" })).toHaveCells({ Status: "Active" });
|
|
1934
|
+
* });
|
|
1935
|
+
*/
|
|
1936
|
+
declare const tableExpect: Expect<typeof tableMatchers>;
|
|
1937
|
+
/** The matchers Playwright's `expect` (extended with {@link tableMatchers}) has for a value of type `T`. */
|
|
1938
|
+
type BaseAssertions<T> = ReturnType<typeof tableExpect<T>>;
|
|
1939
|
+
/** `Base` with the matchers of `Typed` replaced by their typed versions, also after `.not`. */
|
|
1940
|
+
type WithTyped<Base, Typed> = Omit<Base, keyof Typed | "not"> & Typed & {
|
|
1941
|
+
not: Omit<Base, keyof Typed | "not"> & Typed;
|
|
1942
|
+
};
|
|
1943
|
+
/**
|
|
1944
|
+
* The table matchers of {@link expect} on a {@link TableLocator} with typed headers: header keys in
|
|
1945
|
+
* conditions and columns are checked at compile time, like in {@link TableLocator.row}.
|
|
1946
|
+
*/
|
|
1947
|
+
interface TableAssertions<H extends string> {
|
|
1948
|
+
toHaveRow<W extends RowWhere<H>>(where: W & ValidRowWhere<H, W>, options?: TableMatcherOptions): Promise<void>;
|
|
1949
|
+
toHaveRowCount(count: number, options?: TableMatcherOptions): Promise<void>;
|
|
1950
|
+
toHaveRows(rows: readonly RowWhere<H>[], options?: TableRowsOptions): Promise<void>;
|
|
1951
|
+
toContainRows(rows: readonly RowWhere<H>[], options?: TableMatcherOptions): Promise<void>;
|
|
1952
|
+
toHaveEveryRow<W extends RowWhere<H>>(where: W & ValidRowWhere<H, W>, options?: TableMatcherOptions): Promise<void>;
|
|
1953
|
+
toHaveColumnValues(column: ColumnKey<H>, values: readonly CellMatcher[], options?: TableRowsOptions): Promise<void>;
|
|
1954
|
+
/** Header keys are not checked: a header that the table doesn't declare may be asserted absent. */
|
|
1955
|
+
toHaveHeaders(headers: readonly string[], options?: TableMatcherOptions & {
|
|
1956
|
+
exact?: boolean;
|
|
1957
|
+
}): Promise<void>;
|
|
1958
|
+
toBeSortedBy(column: ColumnKey<H>, options?: TableSortOptions): Promise<void>;
|
|
1959
|
+
toHaveTotalRowCount(count: number, options?: TableMatcherOptions): Promise<void>;
|
|
1960
|
+
}
|
|
1961
|
+
/** The row matchers of {@link expect} on a {@link TableRow} with typed headers. */
|
|
1962
|
+
interface RowAssertions<H extends string> {
|
|
1963
|
+
toHaveCells<W extends RowWhere<H>>(cells: W & ValidRowWhere<H, W>, options?: TableMatcherOptions): Promise<void>;
|
|
1964
|
+
toBeSelected(options?: TableMatcherOptions): Promise<void>;
|
|
1965
|
+
toBeExpanded(options?: TableMatcherOptions): Promise<void>;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* The type of {@link expect}: Playwright's `expect` extended with {@link tableMatchers}, whose table and
|
|
1969
|
+
* row matchers check header keys at compile time (also with `expect.soft`).
|
|
1970
|
+
*/
|
|
1971
|
+
type TableExpect = {
|
|
1972
|
+
<H extends string>(actual: TableLocator<H>, messageOrOptions?: string | {
|
|
1973
|
+
message?: string;
|
|
1974
|
+
}): WithTyped<BaseAssertions<TableLocator<H>>, TableAssertions<H>>;
|
|
1975
|
+
<H extends string>(actual: TableRow<H>, messageOrOptions?: string | {
|
|
1976
|
+
message?: string;
|
|
1977
|
+
}): WithTyped<BaseAssertions<TableRow<H>>, RowAssertions<H>>;
|
|
1978
|
+
soft: TableExpect;
|
|
1979
|
+
} & Expect<typeof tableMatchers>;
|
|
1980
|
+
export declare const expect: TableExpect;
|
|
1981
|
+
export type { CellCondition, CellMatcher, ColumnKey, ColumnParser, ColumnParsers, DecimalSeparator, ElementDescriptor, ElementSelector, HeaderKey, HeaderRowOptions, LocatorRef, NavigationOptions, NumberCondition, PaginateOptions, ParsedValue, PollingOptions, RowAssertions, RowWhere, ScrollOptions, StableOptions, TableAssertions, TableExpect, TableJsonOptions, TableJsonRow, TableLocatorOptions, TableMatcherOptions, TablePreset, TableRowsOptions, TableSortOptions, ValidRowWhere };
|
|
1982
|
+
//# sourceMappingURL=index.d.mts.map
|