@cerios/playwright-table 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -7
- package/README.md +686 -258
- package/dist/index.d.mts +431 -51
- package/dist/index.d.ts +431 -51
- package/dist/index.js +814 -204
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +814 -204
- package/dist/index.mjs.map +1 -1
- package/package.json +32 -27
package/README.md
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
# 🎭 Playwright Tables | By Cerios
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@cerios/playwright-table)
|
|
4
|
+
[](https://www.npmjs.com/package/@cerios/playwright-table)
|
|
5
|
+
[](https://github.com/CeriosTesting/playwright-tables/blob/main/LICENSE)
|
|
6
|
+
[](https://playwright.dev)
|
|
7
|
+
|
|
3
8
|
## Introduction
|
|
4
9
|
|
|
5
|
-
Testing HTML tables can be challenging due to their
|
|
10
|
+
Testing HTML tables can be challenging due to their complex structures with colspan, rowspan, dynamic content, and nested elements. This package simplifies the process by providing a comprehensive API to parse, validate, and interact with HTML tables in your Playwright tests.
|
|
11
|
+
|
|
12
|
+
**Key Features:**
|
|
13
|
+
|
|
14
|
+
- ✅ Full support for **colspan** and **rowspan** attributes
|
|
15
|
+
- ✅ Handles **multiple header rows** with flexible main header selection
|
|
16
|
+
- ✅ **Dynamic content loading** with polling and stability checks
|
|
17
|
+
- ✅ **Parallel data fetching** for optimal performance
|
|
18
|
+
- ✅ Robust **error handling** with detailed context
|
|
19
|
+
- ✅ Support for both **InnerText** and **TextContent** extraction
|
|
20
|
+
- ✅ Works with standard `<table>` elements and custom div-based tables
|
|
21
|
+
- ✅ Comprehensive **waiting utilities** for dynamic tables
|
|
6
22
|
|
|
7
23
|
---
|
|
8
24
|
|
|
9
25
|
## Installation
|
|
10
26
|
|
|
11
|
-
First,
|
|
27
|
+
First, ensure you have Playwright installed:
|
|
12
28
|
[Playwright Documentation](https://github.com/microsoft/playwright/blob/main/README.md)
|
|
13
29
|
|
|
14
30
|
Then, install this package:
|
|
@@ -17,431 +33,843 @@ Then, install this package:
|
|
|
17
33
|
npm i @cerios/playwright-table
|
|
18
34
|
```
|
|
19
35
|
|
|
20
|
-
Or as
|
|
36
|
+
Or as a dev dependency:
|
|
21
37
|
|
|
22
38
|
```bash
|
|
23
39
|
npm i -D @cerios/playwright-table
|
|
24
40
|
```
|
|
25
41
|
|
|
26
|
-
|
|
42
|
+
> ⚠️ **Upgrading from v1.x?** See the [Migration Guide](./MIGRATIONv2.md) for breaking changes and upgrade instructions.
|
|
27
43
|
|
|
28
|
-
|
|
44
|
+
---
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
## Quick Start
|
|
31
47
|
|
|
32
|
-
|
|
48
|
+
### Basic Usage
|
|
33
49
|
|
|
34
50
|
```ts
|
|
51
|
+
import { PlaywrightTable } from "@cerios/playwright-table";
|
|
52
|
+
|
|
53
|
+
// Create a table instance
|
|
35
54
|
const table = new PlaywrightTable(page.locator("table"));
|
|
55
|
+
|
|
56
|
+
// Get table data as JSON
|
|
57
|
+
const data = await table.getJson();
|
|
58
|
+
console.log(data);
|
|
59
|
+
// Output: [{"Name": "John", "Age": "30"}, {"Name": "Jane", "Age": "25"}]
|
|
60
|
+
|
|
61
|
+
// Get specific cell locator
|
|
62
|
+
const cell = table.getBodyCellLocator(0, 1); // Row 0, Column 1
|
|
63
|
+
await expect(cell).toHaveText("30");
|
|
64
|
+
|
|
65
|
+
// Wait for dynamic content
|
|
66
|
+
await table.waitForExactRowCount(5, { timeout: 10000 });
|
|
36
67
|
```
|
|
37
68
|
|
|
38
|
-
|
|
69
|
+
### Default Selectors
|
|
70
|
+
|
|
71
|
+
By default, the following CSS selectors are used:
|
|
39
72
|
|
|
40
|
-
- Header rows
|
|
41
|
-
- Header cells
|
|
42
|
-
- Body rows
|
|
43
|
-
- Body cells
|
|
73
|
+
- **Header rows:** `thead > tr`
|
|
74
|
+
- **Header cells:** `th`
|
|
75
|
+
- **Body rows:** `tbody > tr`
|
|
76
|
+
- **Body cells:** `td`
|
|
77
|
+
|
|
78
|
+
You can customize these selectors in the constructor (see [Advanced Options](#advanced-options)).
|
|
44
79
|
|
|
45
80
|
---
|
|
46
81
|
|
|
47
|
-
## Methods
|
|
82
|
+
## Core Methods
|
|
83
|
+
|
|
84
|
+
### 1. `getHeaderRows(options?)`
|
|
85
|
+
|
|
86
|
+
Retrieves all header rows with support for colspan and rowspan. Colspan cells get a suffix (`__C1`, `__C2`, etc.).
|
|
48
87
|
|
|
49
|
-
|
|
88
|
+
**Parameters:**
|
|
50
89
|
|
|
51
|
-
|
|
90
|
+
- `options?: HeaderRowOptions` - Configure header parsing behavior
|
|
52
91
|
|
|
53
|
-
|
|
92
|
+
**Returns:** `Promise<HeaderRow[]>` - Array of header rows
|
|
54
93
|
|
|
55
|
-
|
|
94
|
+
**Example:**
|
|
95
|
+
|
|
96
|
+
HTML with complex headers
|
|
56
97
|
|
|
57
98
|
<table>
|
|
58
99
|
<thead>
|
|
59
100
|
<tr>
|
|
60
|
-
<th rowspan="2">
|
|
61
|
-
<th>
|
|
62
|
-
<th>Last Name</th>
|
|
101
|
+
<th rowspan="2">Name</th>
|
|
102
|
+
<th colspan="2">Details</th>
|
|
63
103
|
</tr>
|
|
64
104
|
<tr>
|
|
65
|
-
|
|
105
|
+
<th>Age</th>
|
|
106
|
+
<th>City</th>
|
|
66
107
|
</tr>
|
|
67
108
|
</thead>
|
|
68
109
|
</table>
|
|
69
110
|
|
|
70
|
-
Code:
|
|
71
|
-
|
|
72
111
|
```ts
|
|
73
|
-
|
|
112
|
+
// HTML with complex headers
|
|
113
|
+
<table>
|
|
114
|
+
<thead>
|
|
115
|
+
<tr>
|
|
116
|
+
<th rowspan="2">Name</th>
|
|
117
|
+
<th colspan="2">Details</th>
|
|
118
|
+
</tr>
|
|
119
|
+
<tr>
|
|
120
|
+
<th>Age</th>
|
|
121
|
+
<th>City</th>
|
|
122
|
+
</tr>
|
|
123
|
+
</thead>
|
|
124
|
+
</table>
|
|
125
|
+
|
|
126
|
+
const headers = await table.getHeaderRows({
|
|
127
|
+
colspan: { enabled: true, suffix: true }
|
|
128
|
+
});
|
|
74
129
|
console.log(headers);
|
|
130
|
+
// Output: [
|
|
131
|
+
// ["Name", "Details", "Details__C1"],
|
|
132
|
+
// ["Name", "Age", "City"]
|
|
133
|
+
// ]
|
|
75
134
|
```
|
|
76
135
|
|
|
77
|
-
|
|
136
|
+
**Options:**
|
|
78
137
|
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
138
|
+
```ts
|
|
139
|
+
{
|
|
140
|
+
cellContentType?: CellContentType.InnerText | CellContentType.TextContent, // Default: InnerText
|
|
141
|
+
emptyCellReplacement?: boolean, // Replace empty cells with "{{Empty}}" (default: false)
|
|
142
|
+
duplicateSuffix?: boolean, // Add suffix to duplicate headers (default: false)
|
|
143
|
+
colspan?: {
|
|
144
|
+
enabled?: boolean, // Enable colspan parsing (default: false)
|
|
145
|
+
suffix?: boolean // Add suffix to colspan cells (default: false)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
84
148
|
```
|
|
85
149
|
|
|
86
150
|
---
|
|
87
151
|
|
|
88
|
-
### 2. `getMainHeaderRow`
|
|
152
|
+
### 2. `getMainHeaderRow(options?)`
|
|
89
153
|
|
|
90
|
-
Retrieves the main header row
|
|
154
|
+
Retrieves the main header row. By default, uses the **last header row**. Customizable via constructor.
|
|
91
155
|
|
|
92
|
-
|
|
156
|
+
**Parameters:**
|
|
93
157
|
|
|
94
|
-
|
|
158
|
+
- `options?: HeaderRowOptions` - Configure header parsing
|
|
95
159
|
|
|
96
|
-
<
|
|
97
|
-
<thead>
|
|
98
|
-
<tr>
|
|
99
|
-
<th colspan="2">Full Name</th>
|
|
100
|
-
</tr>
|
|
101
|
-
<tr>
|
|
102
|
-
<th>First Name</th>
|
|
103
|
-
<th>Last Name</th>
|
|
104
|
-
</tr>
|
|
105
|
-
</thead>
|
|
106
|
-
</table>
|
|
160
|
+
**Returns:** `Promise<HeaderRow>` - The main header row
|
|
107
161
|
|
|
108
|
-
|
|
162
|
+
**Throws:**
|
|
163
|
+
|
|
164
|
+
- Error if no header rows are available
|
|
165
|
+
- Error if `setMainHeaderRow` index is out of bounds
|
|
166
|
+
|
|
167
|
+
**Example:**
|
|
109
168
|
|
|
110
169
|
```ts
|
|
170
|
+
// Use first row as main header
|
|
171
|
+
const table = new PlaywrightTable(page.locator("table"), {
|
|
172
|
+
header: { setMainHeaderRow: 0 },
|
|
173
|
+
});
|
|
174
|
+
|
|
111
175
|
const mainHeader = await table.getMainHeaderRow();
|
|
112
176
|
console.log(mainHeader);
|
|
113
|
-
// Output: ["
|
|
177
|
+
// Output: ["Name", "Age", "City"]
|
|
114
178
|
```
|
|
115
179
|
|
|
116
180
|
---
|
|
117
181
|
|
|
118
|
-
### 3. `getBodyRows`
|
|
182
|
+
### 3. `getBodyRows(options?)`
|
|
119
183
|
|
|
120
|
-
Retrieves all body rows
|
|
184
|
+
Retrieves all body rows with support for rowspan and colspan.
|
|
121
185
|
|
|
122
|
-
|
|
186
|
+
**Parameters:**
|
|
123
187
|
|
|
124
|
-
|
|
188
|
+
- `options?: { cellContentType?: CellContentType }` - Content extraction type
|
|
125
189
|
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<td>Ronald</td>
|
|
130
|
-
<td>Veth</td>
|
|
131
|
-
</tr>
|
|
132
|
-
<tr>
|
|
133
|
-
<td>Logan</td>
|
|
134
|
-
<td>Deacon</td>
|
|
135
|
-
</tr>
|
|
136
|
-
</tbody>
|
|
137
|
-
</table>
|
|
190
|
+
**Returns:** `Promise<BodyRow[]>` - Array of body rows
|
|
191
|
+
|
|
192
|
+
**Throws:** Error if no body rows found
|
|
138
193
|
|
|
139
|
-
|
|
194
|
+
**Example:**
|
|
140
195
|
|
|
141
196
|
```ts
|
|
142
|
-
const rows = await table.getBodyRows(
|
|
197
|
+
const rows = await table.getBodyRows({
|
|
198
|
+
cellContentType: CellContentType.TextContent, // Include hidden elements
|
|
199
|
+
});
|
|
143
200
|
console.log(rows);
|
|
144
|
-
// Output: [["
|
|
201
|
+
// Output: [["John", "30", "NYC"], ["Jane", "25", "LA"]]
|
|
145
202
|
```
|
|
146
203
|
|
|
204
|
+
**Cell Content Types:**
|
|
205
|
+
|
|
206
|
+
- `CellContentType.InnerText` - Rendered text (default, excludes hidden elements)
|
|
207
|
+
- `CellContentType.TextContent` - Raw text content (includes hidden elements)
|
|
208
|
+
|
|
147
209
|
---
|
|
148
210
|
|
|
149
|
-
### 4. `getBodyCellLocator`
|
|
211
|
+
### 4. `getBodyCellLocator(rowNumber, headerPosition)`
|
|
150
212
|
|
|
151
|
-
|
|
213
|
+
Gets a Playwright Locator for a specific cell by row and column indices (0-based).
|
|
152
214
|
|
|
153
|
-
|
|
215
|
+
**Parameters:**
|
|
154
216
|
|
|
155
|
-
|
|
217
|
+
- `rowNumber: number` - Row index (0-based)
|
|
218
|
+
- `headerPosition: number` - Column index (0-based)
|
|
156
219
|
|
|
157
|
-
|
|
158
|
-
<tbody>
|
|
159
|
-
<tr>
|
|
160
|
-
<td>Ronald</td>
|
|
161
|
-
<td>Veth</td>
|
|
162
|
-
</tr>
|
|
163
|
-
<tr>
|
|
164
|
-
<td>Principal</td>
|
|
165
|
-
<td>Cerios</td>
|
|
166
|
-
</tr>
|
|
167
|
-
</tbody>
|
|
168
|
-
</table>
|
|
220
|
+
**Returns:** `Locator` - Playwright locator for the cell
|
|
169
221
|
|
|
170
|
-
|
|
222
|
+
**Example:**
|
|
171
223
|
|
|
172
224
|
```ts
|
|
173
|
-
|
|
174
|
-
|
|
225
|
+
// Get cell at row 1, column 2
|
|
226
|
+
const cellLocator = table.getBodyCellLocator(1, 2);
|
|
227
|
+
await expect(cellLocator).toHaveText("Expected Value");
|
|
228
|
+
await cellLocator.click();
|
|
175
229
|
```
|
|
176
230
|
|
|
177
231
|
---
|
|
178
232
|
|
|
179
|
-
### 5. `getBodyCellLocatorByRowConditions`
|
|
233
|
+
### 5. `getBodyCellLocatorByRowConditions(conditions, targetHeader, options?)`
|
|
180
234
|
|
|
181
|
-
|
|
235
|
+
Finds a cell by matching row conditions and returns the locator for a specific column. Supports both object syntax (multiple conditions) and tuple syntax (single condition).
|
|
182
236
|
|
|
183
|
-
|
|
237
|
+
**Parameters:**
|
|
184
238
|
|
|
185
|
-
|
|
239
|
+
- `conditions: Record<string, string> | [string, string]` - Header-value pairs to match (object) or single [header, value] tuple
|
|
240
|
+
- `targetHeader: string` - Column name to retrieve
|
|
241
|
+
- `options?: TableOptions` - Table loading options
|
|
186
242
|
|
|
187
|
-
<
|
|
188
|
-
<thead>
|
|
189
|
-
<tr>
|
|
190
|
-
<th>First Name</th>
|
|
191
|
-
<th>Last Name</th>
|
|
192
|
-
</tr>
|
|
193
|
-
</thead>
|
|
194
|
-
<tbody>
|
|
195
|
-
<tr>
|
|
196
|
-
<td>Ronald</td>
|
|
197
|
-
<td>Veth</td>
|
|
198
|
-
</tr>
|
|
199
|
-
<tr>
|
|
200
|
-
<td>Principal</td>
|
|
201
|
-
<td>Cerios</td>
|
|
202
|
-
</tr>
|
|
203
|
-
</tbody>
|
|
204
|
-
</table>
|
|
243
|
+
**Returns:** `Promise<Locator>` - Cell locator
|
|
205
244
|
|
|
206
|
-
|
|
245
|
+
**Throws:**
|
|
246
|
+
|
|
247
|
+
- Error if no matching row found
|
|
248
|
+
- Error if specified headers don't exist
|
|
249
|
+
|
|
250
|
+
**Example:**
|
|
207
251
|
|
|
208
252
|
```ts
|
|
209
|
-
|
|
210
|
-
await
|
|
253
|
+
// Find cell with multiple conditions (object syntax)
|
|
254
|
+
const emailCell = await table.getBodyCellLocatorByRowConditions({ "First name": "John", Status: "Active" }, "Email");
|
|
255
|
+
await expect(emailCell).toContainText("@example.com");
|
|
256
|
+
|
|
257
|
+
// Find cell with single condition (tuple syntax)
|
|
258
|
+
const actionCell = await table.getBodyCellLocatorByRowConditions(["Username", "john.doe"], "Actions");
|
|
259
|
+
await actionCell.locator("button.delete").click();
|
|
260
|
+
|
|
261
|
+
// Find cell with single condition (object syntax also works)
|
|
262
|
+
const statusCell = await table.getBodyCellLocatorByRowConditions({ Username: "john.doe" }, "Status");
|
|
211
263
|
```
|
|
212
264
|
|
|
213
265
|
---
|
|
214
266
|
|
|
215
|
-
### 6. `getAllBodyCellLocatorsByHeaderName`
|
|
267
|
+
### 6. `getAllBodyCellLocatorsByHeaderName(headerName, options?)`
|
|
216
268
|
|
|
217
|
-
|
|
269
|
+
Gets all cell locators in a column by header name.
|
|
218
270
|
|
|
219
|
-
|
|
271
|
+
**Parameters:**
|
|
220
272
|
|
|
221
|
-
|
|
273
|
+
- `headerName: string` - Column header name
|
|
274
|
+
- `options?: TableOptions` - Table loading options
|
|
222
275
|
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<th>First Name</th>
|
|
227
|
-
<th>Last Name</th>
|
|
228
|
-
</tr>
|
|
229
|
-
</thead>
|
|
230
|
-
<tbody>
|
|
231
|
-
<tr>
|
|
232
|
-
<td>Ronald</td>
|
|
233
|
-
<td>Veth</td>
|
|
234
|
-
</tr>
|
|
235
|
-
<tr>
|
|
236
|
-
<td>Principal</td>
|
|
237
|
-
<td>Cerios</td>
|
|
238
|
-
</tr>
|
|
239
|
-
</tbody>
|
|
240
|
-
</table>
|
|
276
|
+
**Returns:** `Promise<Locator[]>` - Array of cell locators
|
|
277
|
+
|
|
278
|
+
**Throws:** Error if header not found
|
|
241
279
|
|
|
242
|
-
|
|
280
|
+
**Example:**
|
|
243
281
|
|
|
244
282
|
```ts
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
283
|
+
// Get all cells in "Status" column
|
|
284
|
+
const statusCells = await table.getAllBodyCellLocatorsByHeaderName("Status");
|
|
285
|
+
for (const cell of statusCells) {
|
|
286
|
+
await expect(cell).toHaveText(/Active|Inactive/);
|
|
249
287
|
}
|
|
288
|
+
|
|
289
|
+
// Extract all values from "Name" column
|
|
290
|
+
const names = await Promise.all(statusCells.map(cell => cell.textContent()));
|
|
291
|
+
console.log(names); // ["John", "Jane", "Bob"]
|
|
250
292
|
```
|
|
251
293
|
|
|
252
294
|
---
|
|
253
295
|
|
|
254
|
-
### 7. `getAllBodyCellLocatorsByHeaderIndex`
|
|
296
|
+
### 7. `getAllBodyCellLocatorsByHeaderIndex(headerIndex, options?)`
|
|
255
297
|
|
|
256
|
-
|
|
298
|
+
Gets all cell locators in a column by column index (0-based).
|
|
257
299
|
|
|
258
|
-
|
|
300
|
+
**Parameters:**
|
|
259
301
|
|
|
260
|
-
|
|
302
|
+
- `headerIndex: number` - Column index (0-based)
|
|
303
|
+
- `options?: TableOptions` - Table loading options
|
|
261
304
|
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
<th>First Name</th>
|
|
266
|
-
<th>Last Name</th>
|
|
267
|
-
</tr>
|
|
268
|
-
</thead>
|
|
269
|
-
<tbody>
|
|
270
|
-
<tr>
|
|
271
|
-
<td>Ronald</td>
|
|
272
|
-
<td>Veth</td>
|
|
273
|
-
</tr>
|
|
274
|
-
<tr>
|
|
275
|
-
<td>Principal</td>
|
|
276
|
-
<td>Cerios</td>
|
|
277
|
-
</tr>
|
|
278
|
-
</tbody>
|
|
279
|
-
</table>
|
|
305
|
+
**Returns:** `Promise<Locator[]>` - Array of cell locators
|
|
306
|
+
|
|
307
|
+
**Throws:** Error if index out of bounds
|
|
280
308
|
|
|
281
|
-
|
|
309
|
+
**Example:**
|
|
282
310
|
|
|
283
311
|
```ts
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
312
|
+
// Get all cells in first column (index 0)
|
|
313
|
+
const firstColumnCells = await table.getAllBodyCellLocatorsByHeaderIndex(0);
|
|
314
|
+
const values = await Promise.all(firstColumnCells.map(c => c.textContent()));
|
|
315
|
+
console.log(values);
|
|
316
|
+
|
|
317
|
+
// Verify all cells are visible
|
|
318
|
+
for (const cell of firstColumnCells) {
|
|
319
|
+
await expect(cell).toBeVisible();
|
|
288
320
|
}
|
|
289
321
|
```
|
|
290
322
|
|
|
291
323
|
---
|
|
292
324
|
|
|
293
|
-
### 8. `getJson`
|
|
325
|
+
### 8. `getJson(options?)`
|
|
326
|
+
|
|
327
|
+
Converts the entire table to a JSON array of objects. **Headers become object keys**, values are cell contents.
|
|
328
|
+
|
|
329
|
+
**Parameters:**
|
|
294
330
|
|
|
295
|
-
|
|
331
|
+
- `options?: TableOptions` - Configure parsing behavior
|
|
296
332
|
|
|
297
|
-
|
|
333
|
+
**Returns:** `Promise<any[]>` - Array of row objects
|
|
298
334
|
|
|
299
|
-
|
|
335
|
+
**Throws:** Error if no headers or body rows found
|
|
336
|
+
|
|
337
|
+
**Simple Example:**
|
|
300
338
|
|
|
301
339
|
<table>
|
|
302
|
-
<thead>
|
|
303
|
-
<tr>
|
|
304
|
-
<th>id</th>
|
|
305
|
-
<th>name</th>
|
|
306
|
-
<th>yearOfBirth</th>
|
|
307
|
-
</tr>
|
|
308
|
-
</thead>
|
|
340
|
+
<thead><tr><th>Name</th><th>Age</th></tr></thead>
|
|
309
341
|
<tbody>
|
|
310
|
-
<tr>
|
|
311
|
-
|
|
312
|
-
<td>Ronald</td>
|
|
313
|
-
<td>1987</td>
|
|
314
|
-
</tr>
|
|
315
|
-
<tr>
|
|
316
|
-
<td>2</td>
|
|
317
|
-
<td>Cerios</td>
|
|
318
|
-
<td>2022</td>
|
|
319
|
-
</tr>
|
|
342
|
+
<tr><td>John</td><td>30</td></tr>
|
|
343
|
+
<tr><td>Jane</td><td>25</td></tr>
|
|
320
344
|
</tbody>
|
|
321
345
|
</table>
|
|
322
346
|
|
|
323
|
-
Code:
|
|
324
|
-
|
|
325
347
|
```ts
|
|
348
|
+
// HTML:
|
|
349
|
+
<table>
|
|
350
|
+
<thead><tr><th>Name</th><th>Age</th></tr></thead>
|
|
351
|
+
<tbody>
|
|
352
|
+
<tr><td>John</td><td>30</td></tr>
|
|
353
|
+
<tr><td>Jane</td><td>25</td></tr>
|
|
354
|
+
</tbody>
|
|
355
|
+
</table>
|
|
356
|
+
|
|
326
357
|
const json = await table.getJson();
|
|
327
358
|
console.log(json);
|
|
359
|
+
// Output:
|
|
360
|
+
// [
|
|
361
|
+
// { "Name": "John", "Age": "30" },
|
|
362
|
+
// { "Name": "Jane", "Age": "25" }
|
|
363
|
+
// ]
|
|
328
364
|
```
|
|
329
365
|
|
|
330
|
-
|
|
366
|
+
**Complex Example with Options:**
|
|
331
367
|
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
368
|
+
```ts
|
|
369
|
+
// HTML with colspan, rowspan, empty cells, and duplicates
|
|
370
|
+
const json = await table.getJson({
|
|
371
|
+
headerRowOptions: {
|
|
372
|
+
colspan: { enabled: true, suffix: true },
|
|
373
|
+
duplicateSuffix: true,
|
|
374
|
+
emptyCellReplacement: true,
|
|
338
375
|
},
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
376
|
+
});
|
|
377
|
+
// Output:
|
|
378
|
+
// [
|
|
379
|
+
// {
|
|
380
|
+
// "Awesome Rowspan": "Value1",
|
|
381
|
+
// "Name": "John",
|
|
382
|
+
// "Name__C1": "Doe",
|
|
383
|
+
// "{{Empty}}": "EmptyValue",
|
|
384
|
+
// "Duplicate": "First",
|
|
385
|
+
// "Duplicate__D1": "Second"
|
|
386
|
+
// }
|
|
387
|
+
// ]
|
|
345
388
|
```
|
|
346
389
|
|
|
347
|
-
|
|
390
|
+
---
|
|
348
391
|
|
|
349
|
-
|
|
392
|
+
## Waiting Methods
|
|
350
393
|
|
|
351
|
-
|
|
352
|
-
<thead>
|
|
353
|
-
<tr>
|
|
354
|
-
<th rowspan="2">Awesome Rowspan</th>
|
|
355
|
-
<th>Very Duplicate</th>
|
|
356
|
-
<th colspan="3">Mega colspan</th>
|
|
357
|
-
<th></th>
|
|
358
|
-
<th></th>
|
|
359
|
-
<th>Very Duplicate</th>
|
|
360
|
-
</tr>
|
|
361
|
-
</thead>
|
|
362
|
-
<tbody>
|
|
363
|
-
<tr>
|
|
364
|
-
<td></td>
|
|
365
|
-
<td>Ronald</td>
|
|
366
|
-
<td>Veth</td>
|
|
367
|
-
<td>Test</td>
|
|
368
|
-
<td>Automator</td>
|
|
369
|
-
<td>At</td>
|
|
370
|
-
<td>Cerios</td>
|
|
371
|
-
<td>!</td>
|
|
372
|
-
</tr>
|
|
373
|
-
</tbody>
|
|
374
|
-
</table>
|
|
394
|
+
### 9. `waitForEmpty(options?)`
|
|
375
395
|
|
|
376
|
-
|
|
396
|
+
Waits for the table body to be completely empty (no body rows).
|
|
397
|
+
|
|
398
|
+
> 💡 **Tip:** Consider using `expect.poll` for more flexibility: `await expect.poll(async () => (await table.getBodyRows()).length).toBe(0);`
|
|
399
|
+
|
|
400
|
+
**Parameters:**
|
|
401
|
+
|
|
402
|
+
- `options?: PollingOptions`
|
|
403
|
+
|
|
404
|
+
**Returns:** `Promise<void>`
|
|
405
|
+
|
|
406
|
+
**Throws:** Error if table is not empty within timeout
|
|
407
|
+
|
|
408
|
+
**Example:**
|
|
377
409
|
|
|
378
410
|
```ts
|
|
379
|
-
|
|
380
|
-
|
|
411
|
+
// Click delete all button
|
|
412
|
+
await page.locator("button.delete-all").click();
|
|
413
|
+
|
|
414
|
+
// Wait for table to clear
|
|
415
|
+
await table.waitForEmpty({ timeout: 5000 });
|
|
416
|
+
|
|
417
|
+
// Verify empty state
|
|
418
|
+
const rows = await table.getBodyRows();
|
|
419
|
+
expect(rows).toHaveLength(0);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
### 10. `waitForNonEmpty(options?)`
|
|
425
|
+
|
|
426
|
+
Waits for the table body to have at least one body row with actual content. A row is considered valid if it has at least one cell containing text.
|
|
427
|
+
|
|
428
|
+
> 💡 **Tip:** Consider using `expect.poll` for more flexibility: `await expect.poll(async () => (await table.getBodyRows()).length).toBeGreaterThan(0);`
|
|
429
|
+
|
|
430
|
+
**Parameters:**
|
|
431
|
+
|
|
432
|
+
- `options?: PollingOptions`
|
|
433
|
+
|
|
434
|
+
**Returns:** `Promise<void>`
|
|
435
|
+
|
|
436
|
+
**Throws:**
|
|
437
|
+
|
|
438
|
+
- Error if table remains empty (no rows) within timeout
|
|
439
|
+
- Error if table has rows but all cells are empty within timeout
|
|
440
|
+
|
|
441
|
+
**Example:**
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
// Trigger data load
|
|
445
|
+
await page.locator("button.load-data").click();
|
|
446
|
+
|
|
447
|
+
// Wait for table to populate with actual data
|
|
448
|
+
await table.waitForNonEmpty({ timeout: 5000 });
|
|
449
|
+
|
|
450
|
+
// Now safe to process data - guaranteed to have content
|
|
451
|
+
const data = await table.getJson();
|
|
452
|
+
expect(data.length).toBeGreaterThan(0);
|
|
453
|
+
|
|
454
|
+
// Useful for dynamically loaded tables where structure appears before data
|
|
455
|
+
await searchInput.fill("search term");
|
|
456
|
+
await table.waitForNonEmpty(); // Waits for rows AND content, not just empty row elements
|
|
381
457
|
```
|
|
382
458
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### 11. `waitForExactRowCount(count, options?)`
|
|
462
|
+
|
|
463
|
+
Waits for the table to have exactly the specified number of body rows.
|
|
464
|
+
|
|
465
|
+
> 💡 **Tip:** Consider using `expect.poll` for more flexibility: `await expect.poll(async () => (await table.getBodyRows()).length).toBe(10);`
|
|
466
|
+
|
|
467
|
+
**Parameters:**
|
|
468
|
+
|
|
469
|
+
- `count: number` - The exact number of rows expected
|
|
470
|
+
- `options?: PollingOptions` - Polling options (timeout, interval)
|
|
471
|
+
|
|
472
|
+
**Returns:** `Promise<void>`
|
|
473
|
+
|
|
474
|
+
**Throws:**
|
|
475
|
+
|
|
476
|
+
- Error if row count doesn't match within timeout
|
|
477
|
+
- Error if count is negative or not an integer
|
|
478
|
+
|
|
479
|
+
**Example:**
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
// Wait for table to have exactly 10 rows
|
|
483
|
+
await table.waitForExactRowCount(10, { timeout: 5000 });
|
|
484
|
+
|
|
485
|
+
// Verify table has exactly 0 rows (alternative to waitForEmpty)
|
|
486
|
+
await table.waitForExactRowCount(0);
|
|
487
|
+
|
|
488
|
+
// Wait after pagination change
|
|
489
|
+
await page.locator("button.page-2").click();
|
|
490
|
+
await table.waitForExactRowCount(25);
|
|
491
|
+
|
|
492
|
+
// Verify filtered results
|
|
493
|
+
await page.locator("input.search").fill("admin");
|
|
494
|
+
await table.waitForExactRowCount(3); // Expect exactly 3 admin users
|
|
398
495
|
```
|
|
399
496
|
|
|
497
|
+
**Use Cases:**
|
|
498
|
+
|
|
499
|
+
- Verifying exact result counts after filtering
|
|
500
|
+
- Validating pagination behavior
|
|
501
|
+
- Testing data deletion/addition
|
|
502
|
+
- Ensuring precise table state for assertions
|
|
503
|
+
|
|
400
504
|
---
|
|
401
505
|
|
|
402
|
-
###
|
|
506
|
+
### 12. `getRowCount()`
|
|
507
|
+
|
|
508
|
+
Gets the count of header and body rows in the table. Lightweight method that doesn't fetch cell data, only counts rows.
|
|
509
|
+
|
|
510
|
+
**Parameters:** None
|
|
403
511
|
|
|
404
|
-
|
|
512
|
+
**Returns:** `Promise<{ header: number; body: number }>`
|
|
405
513
|
|
|
406
|
-
|
|
514
|
+
**Example:**
|
|
407
515
|
|
|
408
516
|
```ts
|
|
409
|
-
|
|
517
|
+
// Get current row counts
|
|
518
|
+
const counts = await table.getRowCount();
|
|
519
|
+
console.log(`Headers: ${counts.header}, Body: ${counts.body}`);
|
|
520
|
+
|
|
521
|
+
// Assert specific row count
|
|
522
|
+
const { body } = await table.getRowCount();
|
|
523
|
+
expect(body).toBe(10);
|
|
524
|
+
|
|
525
|
+
// Check if table is empty efficiently
|
|
526
|
+
const rowCounts = await table.getRowCount();
|
|
527
|
+
if (rowCounts.body === 0) {
|
|
528
|
+
console.log("Table is empty");
|
|
529
|
+
}
|
|
410
530
|
```
|
|
411
531
|
|
|
532
|
+
**Use Cases:**
|
|
533
|
+
|
|
534
|
+
- Quick validation of table size without fetching data
|
|
535
|
+
- Performance-sensitive checks in loops
|
|
536
|
+
- Conditional logic based on row counts
|
|
537
|
+
- Monitoring table size during pagination
|
|
538
|
+
|
|
412
539
|
---
|
|
413
540
|
|
|
414
|
-
###
|
|
541
|
+
### 13. `getDistinctColumnValues(headerName, options?)`
|
|
542
|
+
|
|
543
|
+
Gets all distinct (unique) values from a specific column. Returns a sorted array of unique string values, excluding empty values.
|
|
544
|
+
|
|
545
|
+
**Parameters:**
|
|
546
|
+
|
|
547
|
+
- `headerName: string` - The header name of the column to extract distinct values from
|
|
548
|
+
- `options?: TableOptions` - Optional table loading options
|
|
549
|
+
|
|
550
|
+
**Returns:** `Promise<string[]>` - Sorted array of distinct values
|
|
415
551
|
|
|
416
|
-
|
|
552
|
+
**Throws:** Error if the specified header is not found in the table
|
|
417
553
|
|
|
418
|
-
|
|
554
|
+
**Example:**
|
|
419
555
|
|
|
420
556
|
```ts
|
|
421
|
-
|
|
557
|
+
// Get all unique status values
|
|
558
|
+
const statuses = await table.getDistinctColumnValues("Status");
|
|
559
|
+
// Result: ["Active", "Inactive", "Pending"]
|
|
560
|
+
|
|
561
|
+
// Get distinct countries for filtering
|
|
562
|
+
const countries = await table.getDistinctColumnValues("Country");
|
|
563
|
+
console.log(`Available countries: ${countries.join(", ")}`);
|
|
564
|
+
|
|
565
|
+
// Verify expected values exist
|
|
566
|
+
const roles = await table.getDistinctColumnValues("Role");
|
|
567
|
+
expect(roles).toContain("Admin");
|
|
568
|
+
expect(roles).toHaveLength(3);
|
|
569
|
+
|
|
570
|
+
// Build dynamic filters from table data
|
|
571
|
+
const categories = await table.getDistinctColumnValues("Category");
|
|
572
|
+
for (const category of categories) {
|
|
573
|
+
await page.locator(`button[data-filter="${category}"]`).click();
|
|
574
|
+
// Verify filtering works
|
|
575
|
+
}
|
|
422
576
|
```
|
|
423
577
|
|
|
578
|
+
**Use Cases:**
|
|
579
|
+
|
|
580
|
+
- Extracting filter options from table data
|
|
581
|
+
- Validating data variety and uniqueness
|
|
582
|
+
- Building dynamic test data sets
|
|
583
|
+
- Verifying dropdown/select options match table content
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
### 14. `findRowIndex(conditions, options?)`
|
|
588
|
+
|
|
589
|
+
Finds the index of the first body row matching the specified conditions. Returns -1 if no matching row is found.
|
|
590
|
+
|
|
591
|
+
**Parameters:**
|
|
592
|
+
|
|
593
|
+
- `conditions: Record<string, string>` - Record of header names and expected cell values to match
|
|
594
|
+
- `options?: TableOptions` - Optional table loading options
|
|
595
|
+
|
|
596
|
+
**Returns:** `Promise<number>` - The 0-based index of the first matching row, or -1 if not found
|
|
597
|
+
|
|
598
|
+
**Throws:** Error if specified headers don't exist in the table
|
|
599
|
+
|
|
600
|
+
**Example:**
|
|
601
|
+
|
|
602
|
+
```ts
|
|
603
|
+
// Find index of row where Status is "Active"
|
|
604
|
+
const index = await table.findRowIndex({ Status: "Active" });
|
|
605
|
+
if (index >= 0) {
|
|
606
|
+
console.log(`Found at row ${index}`);
|
|
607
|
+
const cell = table.getBodyCellLocator(index, 0);
|
|
608
|
+
await cell.click();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Find row and interact with it
|
|
612
|
+
const rowIndex = await table.findRowIndex({ Username: "john.doe" });
|
|
613
|
+
if (rowIndex >= 0) {
|
|
614
|
+
const deleteButton = table.getBodyCellLocator(rowIndex, 3).locator("button.delete");
|
|
615
|
+
await deleteButton.click();
|
|
616
|
+
} else {
|
|
617
|
+
console.log("User not found");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Check if specific data exists before taking action
|
|
621
|
+
const orderIndex = await table.findRowIndex({ "Order ID": "12345" });
|
|
622
|
+
if (orderIndex === -1) {
|
|
623
|
+
throw new Error("Order not found in table");
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
**Use Cases:**
|
|
628
|
+
|
|
629
|
+
- Finding row position for interaction without fetching all data
|
|
630
|
+
- Conditional logic based on row existence
|
|
631
|
+
- Getting row indices for further locator operations
|
|
632
|
+
- Checking data presence without waiting
|
|
633
|
+
|
|
424
634
|
---
|
|
425
635
|
|
|
426
636
|
## Advanced Options
|
|
427
637
|
|
|
428
|
-
|
|
638
|
+
### Custom Selectors
|
|
639
|
+
|
|
640
|
+
Customize selectors for non-standard table structures or div-based tables:
|
|
429
641
|
|
|
430
642
|
```ts
|
|
431
|
-
const table = new PlaywrightTable(page.locator("table"), {
|
|
643
|
+
const table = new PlaywrightTable(page.locator(".custom-table"), {
|
|
644
|
+
header: {
|
|
645
|
+
setMainHeaderRow: 0, // Use first header row as main (default: last row)
|
|
646
|
+
rowSelector: ".header-row", // Custom header row selector
|
|
647
|
+
columnSelector: ".header-cell", // Custom header cell selector
|
|
648
|
+
},
|
|
649
|
+
row: {
|
|
650
|
+
rowSelector: ".data-row", // Custom body row selector
|
|
651
|
+
columnSelector: ".data-cell", // Custom body cell selector
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Div-based Tables
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
// Works with div-based table structures
|
|
660
|
+
const divTable = new PlaywrightTable(page.locator(".divTable"), {
|
|
432
661
|
header: {
|
|
433
|
-
rowSelector: ".
|
|
434
|
-
columnSelector: ".
|
|
662
|
+
rowSelector: ".divTableHeading > .divTableRow",
|
|
663
|
+
columnSelector: ".divTableHead",
|
|
435
664
|
},
|
|
436
665
|
row: {
|
|
437
|
-
rowSelector: ".
|
|
438
|
-
columnSelector: ".
|
|
666
|
+
rowSelector: ".divTableBody > .divTableRow",
|
|
667
|
+
columnSelector: ".divTableCell",
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
### Header Row Options
|
|
673
|
+
|
|
674
|
+
Control how headers are processed:
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
const headers = await table.getHeaderRows({
|
|
678
|
+
cellContentType: CellContentType.TextContent, // Include hidden text
|
|
679
|
+
emptyCellReplacement: true, // Replace "" with "{{Empty}}"
|
|
680
|
+
duplicateSuffix: true, // "Name", "Name__D1", "Name__D2"
|
|
681
|
+
colspan: {
|
|
682
|
+
enabled: true, // Process colspan attributes
|
|
683
|
+
suffix: true, // "Col", "Col__C1", "Col__C2"
|
|
439
684
|
},
|
|
440
685
|
});
|
|
441
686
|
```
|
|
442
687
|
|
|
443
688
|
---
|
|
444
689
|
|
|
690
|
+
## Error Handling
|
|
691
|
+
|
|
692
|
+
The library provides **detailed error messages** with context to help you debug issues:
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
try {
|
|
696
|
+
await table.getBodyCellLocatorByRowConditions({ "Invalid Header": "value" }, "Target");
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error(error.message);
|
|
699
|
+
// Error output:
|
|
700
|
+
// Header "Invalid Header" not found.
|
|
701
|
+
// Available headers: [First name, Last name, Date of birth]
|
|
702
|
+
// Header row locator: Locator@table >> thead >> tr
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
**Error Context Includes:**
|
|
707
|
+
|
|
708
|
+
- Locator descriptions with full selector chains
|
|
709
|
+
- Available headers when header not found
|
|
710
|
+
- Row/column indices when out of bounds
|
|
711
|
+
- Cell content types in use
|
|
712
|
+
- Validation failures with expected vs actual values
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## Best Practices
|
|
717
|
+
|
|
718
|
+
### 1. Wait for Dynamic Content
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
// ❌ Bad - might fail if table still loading
|
|
722
|
+
const data = await table.getJson();
|
|
723
|
+
|
|
724
|
+
// ✅ Good - wait for content first
|
|
725
|
+
await table.waitForNonEmpty();
|
|
726
|
+
const data = await table.getJson();
|
|
727
|
+
|
|
728
|
+
// ✅ Good - wait for specific row count
|
|
729
|
+
await table.waitForExactRowCount(5);
|
|
730
|
+
const data = await table.getJson();
|
|
731
|
+
|
|
732
|
+
// ✅ Good - use expect.poll for flexible waiting
|
|
733
|
+
await expect.poll(async () => (await table.getBodyRows()).length).toBe(5);
|
|
734
|
+
const data = await table.getJson();
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### 2. Use `expect.poll` for Flexible Waiting
|
|
738
|
+
|
|
739
|
+
While this library provides dedicated wait methods, Playwright's `expect.poll` offers more flexibility when combined with retrieval methods:
|
|
740
|
+
|
|
741
|
+
```ts
|
|
742
|
+
import { expect } from "@playwright/test";
|
|
743
|
+
|
|
744
|
+
// Wait for exact row count
|
|
745
|
+
await expect.poll(async () => (await table.getBodyRows()).length).toBe(10);
|
|
746
|
+
|
|
747
|
+
// Wait for table to be empty
|
|
748
|
+
await expect.poll(async () => (await table.getJson()).length).toBe(0);
|
|
749
|
+
|
|
750
|
+
// Wait for at least N rows
|
|
751
|
+
await expect.poll(async () => (await table.getBodyRows()).length).toBeGreaterThanOrEqual(5);
|
|
752
|
+
|
|
753
|
+
// Wait for specific data to appear
|
|
754
|
+
await expect.poll(() => table.getJson()).toContainEqual({ Name: "John", Status: "Active" });
|
|
755
|
+
|
|
756
|
+
// Wait for column to have specific value
|
|
757
|
+
await expect.poll(() => table.getDistinctColumnValues("Status")).toContain("Completed");
|
|
758
|
+
|
|
759
|
+
// Custom timeout and intervals
|
|
760
|
+
await expect
|
|
761
|
+
.poll(async () => (await table.getBodyRows()).length, {
|
|
762
|
+
message: "Waiting for table to have 10 rows",
|
|
763
|
+
timeout: 10000,
|
|
764
|
+
intervals: [100, 250, 500, 1000],
|
|
765
|
+
})
|
|
766
|
+
.toBe(10);
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Benefits of `expect.poll`:**
|
|
770
|
+
|
|
771
|
+
- ✅ Native Playwright API with familiar assertion syntax
|
|
772
|
+
- ✅ Rich assertion library (`toBe`, `toContain`, `toEqual`, `toMatch`, etc.)
|
|
773
|
+
- ✅ Better error messages with actual vs expected values
|
|
774
|
+
- ✅ Works with any retrieval method (`getBodyRows`, `getJson`, `getDistinctColumnValues`, etc.)
|
|
775
|
+
|
|
776
|
+
### 3. Use Appropriate Content Type
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
// For visible text only (respects CSS visibility)
|
|
780
|
+
const rows = await table.getBodyRows({
|
|
781
|
+
cellContentType: CellContentType.InnerText,
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// For all text including hidden elements
|
|
785
|
+
const rows = await table.getBodyRows({
|
|
786
|
+
cellContentType: CellContentType.TextContent,
|
|
787
|
+
});
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### 4. Handle Complex Tables
|
|
791
|
+
|
|
792
|
+
```ts
|
|
793
|
+
// Enable all options for complex tables with colspan/rowspan
|
|
794
|
+
const json = await table.getJson({
|
|
795
|
+
headerRowOptions: {
|
|
796
|
+
colspan: { enabled: true, suffix: true },
|
|
797
|
+
duplicateSuffix: true,
|
|
798
|
+
emptyCellReplacement: true,
|
|
799
|
+
},
|
|
800
|
+
});
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
### 5. Leverage Cell Locators for Interactions
|
|
804
|
+
|
|
805
|
+
```ts
|
|
806
|
+
// Get locator first, then interact
|
|
807
|
+
const cell = await table.getBodyCellLocatorByRowConditions({ Name: "John Doe" }, "Actions");
|
|
808
|
+
await cell.locator("button.edit").click();
|
|
809
|
+
await cell.locator("button.delete").click();
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## TypeScript Support
|
|
815
|
+
|
|
816
|
+
Full TypeScript support with exported types:
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
import {
|
|
820
|
+
PlaywrightTable,
|
|
821
|
+
HeaderRow,
|
|
822
|
+
BodyRow,
|
|
823
|
+
Cell,
|
|
824
|
+
CellContentType,
|
|
825
|
+
HeaderRowOptions,
|
|
826
|
+
TableOptions,
|
|
827
|
+
PollingOptions,
|
|
828
|
+
RowKind,
|
|
829
|
+
} from "@cerios/playwright-table";
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## Performance
|
|
835
|
+
|
|
836
|
+
The library is optimized for performance:
|
|
837
|
+
|
|
838
|
+
- ✅ **Parallel data fetching** - All cells in a row are fetched simultaneously
|
|
839
|
+
- ✅ **Efficient DOM queries** - Minimizes Playwright API calls
|
|
840
|
+
- ✅ **Smart polling** - Configurable intervals and timeouts
|
|
841
|
+
- ✅ **Lazy evaluation** - Only fetches data when needed
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Browser Support
|
|
846
|
+
|
|
847
|
+
Works with all Playwright-supported browsers:
|
|
848
|
+
|
|
849
|
+
- ✅ Chromium
|
|
850
|
+
- ✅ Firefox
|
|
851
|
+
- ✅ WebKit
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
## Contributing
|
|
856
|
+
|
|
857
|
+
Contributions are welcome! Please check out the [GitHub repository](https://github.com/CeriosTesting/playwright-tables).
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
445
861
|
## License
|
|
446
862
|
|
|
447
863
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## Support
|
|
868
|
+
|
|
869
|
+
- 📖 [Documentation](https://github.com/CeriosTesting/playwright-tables)
|
|
870
|
+
- 🐛 [Report Issues](https://github.com/CeriosTesting/playwright-tables/issues)
|
|
871
|
+
- 💬 [Discussions](https://github.com/CeriosTesting/playwright-tables/discussions)
|
|
872
|
+
|
|
873
|
+
---
|
|
874
|
+
|
|
875
|
+
**Made with ❤️ by Cerios**
|