ex-pw 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/index.d.mts +1116 -0
- package/dist/index.d.ts +1116 -0
- package/dist/index.js +1735 -0
- package/dist/index.mjs +1663 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yevhen Laichenkov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
# ex-pw
|
|
2
|
+
|
|
3
|
+
Extended Playwright expect matchers with auto-waiting and improved developer
|
|
4
|
+
experience.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/ex-pw)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -D ex-pw
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
### Option 1: Extend in Playwright Config (Recommended)
|
|
17
|
+
|
|
18
|
+
Update your `playwright.config.ts`:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { expect } from "@playwright/test";
|
|
22
|
+
import exPw from "ex-pw";
|
|
23
|
+
|
|
24
|
+
// Extend expect globally
|
|
25
|
+
expect.extend(exPw);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Option 2: Import Individual Matchers
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { expect } from "@playwright/test";
|
|
32
|
+
import { toBeClickable, toBeRequired, toMatchSchema } from "ex-pw";
|
|
33
|
+
|
|
34
|
+
expect.extend({ toBeClickable, toBeRequired, toMatchSchema });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Locator Matchers
|
|
40
|
+
|
|
41
|
+
### toBeClickable
|
|
42
|
+
|
|
43
|
+
Asserts that an element is in a clickable state (visible, enabled, and not
|
|
44
|
+
obscured).
|
|
45
|
+
|
|
46
|
+
| Option | Type | Description |
|
|
47
|
+
| --------- | -------- | -------------------------- |
|
|
48
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
await expect(page.getByRole("button")).toBeClickable();
|
|
52
|
+
await expect(page.getByRole("button")).toBeClickable({ timeout: 5000 });
|
|
53
|
+
await expect(page.getByRole("button")).not.toBeClickable(); // disabled button
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### toBeCheckable
|
|
57
|
+
|
|
58
|
+
Asserts that a checkbox or radio is in a checkable state.
|
|
59
|
+
|
|
60
|
+
| Option | Type | Description |
|
|
61
|
+
| --------- | -------- | -------------------------- |
|
|
62
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
await expect(page.getByRole("checkbox")).toBeCheckable();
|
|
66
|
+
await expect(page.getByRole("radio")).toBeCheckable();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### toBeRequired
|
|
70
|
+
|
|
71
|
+
Asserts that a form element is required.
|
|
72
|
+
|
|
73
|
+
| Option | Type | Description |
|
|
74
|
+
| ----------- | ---------- | -------------------------- |
|
|
75
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
76
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
await expect(page.getByLabel("Email")).toBeRequired();
|
|
80
|
+
await expect(page.getByLabel("Optional")).not.toBeRequired();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### toBeInvalid
|
|
84
|
+
|
|
85
|
+
Asserts that a form element is in an invalid validation state.
|
|
86
|
+
|
|
87
|
+
| Option | Type | Description |
|
|
88
|
+
| ----------- | ---------- | -------------------------- |
|
|
89
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
90
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await expect(page.getByLabel("Email")).toBeInvalid();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### toBeValid
|
|
97
|
+
|
|
98
|
+
Asserts that a form element is in a valid validation state.
|
|
99
|
+
|
|
100
|
+
| Option | Type | Description |
|
|
101
|
+
| ----------- | ---------- | -------------------------- |
|
|
102
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
103
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
await expect(page.getByLabel("Email")).toBeValid();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### toHaveCountGreaterThan
|
|
110
|
+
|
|
111
|
+
Asserts that a locator matches more than the specified number of elements.
|
|
112
|
+
|
|
113
|
+
| Option | Type | Description |
|
|
114
|
+
| ----------- | ---------- | ------------------------------ |
|
|
115
|
+
| `count` | `number` | The count threshold (required) |
|
|
116
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
117
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
await expect(page.locator(".item")).toHaveCountGreaterThan(3);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### toHaveCountGreaterThanOrEqual
|
|
124
|
+
|
|
125
|
+
Asserts that a locator matches at least the specified number of elements.
|
|
126
|
+
|
|
127
|
+
| Option | Type | Description |
|
|
128
|
+
| ----------- | ---------- | ------------------------------ |
|
|
129
|
+
| `count` | `number` | The count threshold (required) |
|
|
130
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
131
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
await expect(page.locator(".item")).toHaveCountGreaterThanOrEqual(5);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### toHaveCountLessThan
|
|
138
|
+
|
|
139
|
+
Asserts that a locator matches fewer than the specified number of elements.
|
|
140
|
+
|
|
141
|
+
| Option | Type | Description |
|
|
142
|
+
| ----------- | ---------- | ------------------------------ |
|
|
143
|
+
| `count` | `number` | The count threshold (required) |
|
|
144
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
145
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
await expect(page.locator(".item")).toHaveCountLessThan(10);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### toHaveCountLessThanOrEqual
|
|
152
|
+
|
|
153
|
+
Asserts that a locator matches at most the specified number of elements.
|
|
154
|
+
|
|
155
|
+
| Option | Type | Description |
|
|
156
|
+
| ----------- | ---------- | ------------------------------ |
|
|
157
|
+
| `count` | `number` | The count threshold (required) |
|
|
158
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
159
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
await expect(page.locator(".item")).toHaveCountLessThanOrEqual(5);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### toHaveWidth
|
|
166
|
+
|
|
167
|
+
Asserts that an element has the expected width.
|
|
168
|
+
|
|
169
|
+
| Option | Type | Description |
|
|
170
|
+
| ----------- | ---------- | -------------------------- |
|
|
171
|
+
| `expected` | `number` | Expected width in pixels |
|
|
172
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
173
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
await expect(page.locator("#box")).toHaveWidth(100);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### toHaveHeight
|
|
180
|
+
|
|
181
|
+
Asserts that an element has the expected height.
|
|
182
|
+
|
|
183
|
+
| Option | Type | Description |
|
|
184
|
+
| ----------- | ---------- | -------------------------- |
|
|
185
|
+
| `expected` | `number` | Expected height in pixels |
|
|
186
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
187
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
await expect(page.locator("#box")).toHaveHeight(50);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### toHaveSize
|
|
194
|
+
|
|
195
|
+
Asserts that an element has the expected width and height.
|
|
196
|
+
|
|
197
|
+
| Option | Type | Description |
|
|
198
|
+
| ----------- | ---------- | -------------------------- |
|
|
199
|
+
| `width` | `number` | Expected width in pixels |
|
|
200
|
+
| `height` | `number` | Expected height in pixels |
|
|
201
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
202
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
await expect(page.locator("#box")).toHaveSize(100, 50);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### toHaveLoadedImage
|
|
209
|
+
|
|
210
|
+
Asserts that an `<img>` element has successfully loaded its image.
|
|
211
|
+
|
|
212
|
+
| Option | Type | Description |
|
|
213
|
+
| ----------- | ---------- | -------------------------- |
|
|
214
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
215
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
await expect(page.locator("img#logo")).toHaveLoadedImage();
|
|
219
|
+
await expect(page.locator("img#broken")).not.toHaveLoadedImage();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Page Matchers
|
|
225
|
+
|
|
226
|
+
### toHaveCookie
|
|
227
|
+
|
|
228
|
+
Asserts that a cookie exists with optional value/domain matching.
|
|
229
|
+
|
|
230
|
+
| Option | Type | Description |
|
|
231
|
+
| --------- | ------------------ | -------------------------- |
|
|
232
|
+
| `name` | `string` | Cookie name (required) |
|
|
233
|
+
| `value` | `string \| RegExp` | Expected cookie value |
|
|
234
|
+
| `domain` | `string` | Expected cookie domain |
|
|
235
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
await expect(page).toHaveCookie("session");
|
|
239
|
+
await expect(page).toHaveCookie("session", { value: "abc123" });
|
|
240
|
+
await expect(page).toHaveCookie("session", { value: /^abc/ });
|
|
241
|
+
await expect(page).toHaveCookie("auth", { domain: "example.com" });
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### toHaveLocalStorage
|
|
245
|
+
|
|
246
|
+
Asserts that localStorage contains a key with optional value matching.
|
|
247
|
+
|
|
248
|
+
| Option | Type | Description |
|
|
249
|
+
| ----------- | ---------- | ------------------------------- |
|
|
250
|
+
| `key` | `string` | Storage key (required) |
|
|
251
|
+
| `value` | `any` | Expected value (parsed as JSON) |
|
|
252
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
253
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
await expect(page).toHaveLocalStorage("authToken");
|
|
257
|
+
await expect(page).toHaveLocalStorage("settings", {
|
|
258
|
+
value: { theme: "dark" },
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### toHaveSessionStorage
|
|
263
|
+
|
|
264
|
+
Asserts that sessionStorage contains a key with optional value matching.
|
|
265
|
+
|
|
266
|
+
| Option | Type | Description |
|
|
267
|
+
| ----------- | ---------- | ------------------------------- |
|
|
268
|
+
| `key` | `string` | Storage key (required) |
|
|
269
|
+
| `value` | `any` | Expected value (parsed as JSON) |
|
|
270
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
271
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
await expect(page).toHaveSessionStorage("tempData");
|
|
275
|
+
await expect(page).toHaveSessionStorage("cart", {
|
|
276
|
+
value: expect.arrayContaining([{ id: 1 }]),
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### toHaveClipboardText
|
|
281
|
+
|
|
282
|
+
Asserts that the clipboard contains the expected text.
|
|
283
|
+
|
|
284
|
+
| Option | Type | Description |
|
|
285
|
+
| ----------- | ------------------ | -------------------------- |
|
|
286
|
+
| `expected` | `string \| RegExp` | Expected clipboard text |
|
|
287
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
288
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
289
|
+
|
|
290
|
+
> **Note:** Requires `permissions: ["clipboard-read"]` in your Playwright
|
|
291
|
+
> config.
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// playwright.config.ts
|
|
295
|
+
export default defineConfig({
|
|
296
|
+
use: {
|
|
297
|
+
permissions: ["clipboard-read"],
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// In tests
|
|
302
|
+
await expect(page).toHaveClipboardText("copied text");
|
|
303
|
+
await expect(page).toHaveClipboardText(/pattern/);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### toHaveRequest
|
|
307
|
+
|
|
308
|
+
Asserts that the page has made a network request matching the criteria.
|
|
309
|
+
|
|
310
|
+
| Option | Type | Description |
|
|
311
|
+
| ----------- | ------------------ | -------------------------- |
|
|
312
|
+
| `url` | `string \| RegExp` | URL pattern to match |
|
|
313
|
+
| `method` | `string` | HTTP method to match |
|
|
314
|
+
| `status` | `number` | Expected response status |
|
|
315
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
316
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
317
|
+
|
|
318
|
+
> **Limitations:** Only returns up to 100 most recent requests. Requests may be
|
|
319
|
+
> garbage collected if not accessed promptly.
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
await expect(page).toHaveRequest({ url: /api\/users/ });
|
|
323
|
+
await expect(page).toHaveRequest({ method: "POST", url: /api\/login/ });
|
|
324
|
+
await expect(page).toHaveRequest({ url: "example.com/api", status: 200 });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### toHaveConsoleMessage
|
|
328
|
+
|
|
329
|
+
Asserts that the page has a console message matching the criteria.
|
|
330
|
+
|
|
331
|
+
| Option | Type | Description |
|
|
332
|
+
| ----------- | ------------------ | -------------------------- |
|
|
333
|
+
| `type` | `string` | Console message type |
|
|
334
|
+
| `text` | `string \| RegExp` | Message text pattern |
|
|
335
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
336
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
337
|
+
|
|
338
|
+
> **Limitations:** Only returns up to 200 most recent console messages.
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
await expect(page).toHaveConsoleMessage({ text: "Hello" });
|
|
342
|
+
await expect(page).toHaveConsoleMessage({ type: "error" });
|
|
343
|
+
await expect(page).toHaveConsoleMessage({
|
|
344
|
+
type: "warning",
|
|
345
|
+
text: /deprecated/,
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### toHavePageError
|
|
350
|
+
|
|
351
|
+
Asserts that the page has encountered a JavaScript error.
|
|
352
|
+
|
|
353
|
+
| Option | Type | Description |
|
|
354
|
+
| ----------- | ------------------ | -------------------------- |
|
|
355
|
+
| `message` | `string \| RegExp` | Error message pattern |
|
|
356
|
+
| `name` | `string` | Error name to match |
|
|
357
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
358
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
359
|
+
|
|
360
|
+
> **Limitations:** Only returns up to 200 most recent errors. Only captures
|
|
361
|
+
> uncaught exceptions.
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
await expect(page).toHavePageError();
|
|
365
|
+
await expect(page).toHavePageError({ message: "undefined is not a function" });
|
|
366
|
+
await expect(page).toHavePageError({ message: /TypeError/ });
|
|
367
|
+
await expect(page).not.toHavePageError(); // No errors expected
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### toHaveNoErrors
|
|
371
|
+
|
|
372
|
+
For use with soft assertions - checks for test errors.
|
|
373
|
+
|
|
374
|
+
| Option | Type | Description |
|
|
375
|
+
| -------- | -------- | ---------------------------- |
|
|
376
|
+
| `ignore` | `RegExp` | Pattern for errors to ignore |
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
await expect.soft(page.getByTestId("status")).toHaveText("Success");
|
|
380
|
+
await expect.soft(page.getByTestId("eta")).toHaveText("1 day");
|
|
381
|
+
expect(test).toHaveNoErrors();
|
|
382
|
+
|
|
383
|
+
// Ignore specific errors
|
|
384
|
+
expect(test).toHaveNoErrors({ ignore: /Warning:/ });
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## General Matchers
|
|
390
|
+
|
|
391
|
+
### toBeSorted
|
|
392
|
+
|
|
393
|
+
Asserts that an array or locator elements are sorted.
|
|
394
|
+
|
|
395
|
+
| Option | Type | Description |
|
|
396
|
+
| ------------------ | -------------------- | ------------------------------------ |
|
|
397
|
+
| `descending` | `boolean` | Sort in descending order |
|
|
398
|
+
| `key` | `string \| Function` | Extract value from objects |
|
|
399
|
+
| `compareAsNumbers` | `boolean` | Parse text as numbers for comparison |
|
|
400
|
+
| `useTextContent` | `boolean` | Use `allTextContents()` instead |
|
|
401
|
+
| `timeout` | `number` | Maximum time to wait in ms |
|
|
402
|
+
| `intervals` | `number[]` | Retry intervals in ms |
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// With arrays
|
|
406
|
+
await expect([1, 2, 3]).toBeSorted();
|
|
407
|
+
await expect(["c", "b", "a"]).toBeSorted({ descending: true });
|
|
408
|
+
await expect([{ val: 1 }, { val: 2 }]).toBeSorted({ key: "val" });
|
|
409
|
+
|
|
410
|
+
// With Locators - extracts text from elements
|
|
411
|
+
await expect(page.locator(".item")).toBeSorted();
|
|
412
|
+
await expect(page.locator(".price")).toBeSorted({ compareAsNumbers: true });
|
|
413
|
+
await expect(page.locator(".name")).toBeSorted({ descending: true });
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## API Matchers
|
|
419
|
+
|
|
420
|
+
### toMatchJSON
|
|
421
|
+
|
|
422
|
+
Asserts that the response body matches expected JSON. Supports asymmetric
|
|
423
|
+
matchers for flexible field validation.
|
|
424
|
+
|
|
425
|
+
| Option | Type | Description |
|
|
426
|
+
| ---------- | ----- | ------------------------ |
|
|
427
|
+
| `expected` | `any` | Expected JSON (required) |
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
const response = await request.get("/api/user");
|
|
431
|
+
|
|
432
|
+
// Exact match
|
|
433
|
+
await expect(response).toMatchJSON({ id: 1, name: "John" });
|
|
434
|
+
|
|
435
|
+
// Partial match with Playwright's built-in matchers
|
|
436
|
+
await expect(response).toMatchJSON(expect.objectContaining({ id: 1 }));
|
|
437
|
+
|
|
438
|
+
// With ex-pw asymmetric matchers
|
|
439
|
+
await expect(response).toMatchJSON({
|
|
440
|
+
id: expect.toBeUUID(),
|
|
441
|
+
email: expect.toBeEmail(),
|
|
442
|
+
createdAt: expect.toBeISODate(),
|
|
443
|
+
name: expect.toStartWith("John"),
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### toMatchSchema
|
|
448
|
+
|
|
449
|
+
Validates response body against a Zod schema.
|
|
450
|
+
|
|
451
|
+
| Option | Type | Description |
|
|
452
|
+
| -------- | ----------- | --------------------- |
|
|
453
|
+
| `schema` | `ZodSchema` | Zod schema (required) |
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { z } from "zod";
|
|
457
|
+
|
|
458
|
+
const UserSchema = z.object({
|
|
459
|
+
id: z.number(),
|
|
460
|
+
name: z.string(),
|
|
461
|
+
email: z.string().email(),
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const response = await request.get("/api/user");
|
|
465
|
+
await expect(response).toMatchSchema(UserSchema);
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### toHaveStatus
|
|
469
|
+
|
|
470
|
+
Checks HTTP status code or range.
|
|
471
|
+
|
|
472
|
+
| Option | Type | Description |
|
|
473
|
+
| ---------- | ------------------------ | ------------------------------- |
|
|
474
|
+
| `expected` | `number \| { min, max }` | Status code or range (required) |
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
await expect(response).toHaveStatus(200);
|
|
478
|
+
await expect(response).toHaveStatus({ min: 200, max: 299 }); // Any 2xx
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### toHaveHeader
|
|
482
|
+
|
|
483
|
+
Checks for HTTP header existence and optional value.
|
|
484
|
+
|
|
485
|
+
| Option | Type | Description |
|
|
486
|
+
| ------- | ------------------ | ---------------------- |
|
|
487
|
+
| `name` | `string` | Header name (required) |
|
|
488
|
+
| `value` | `string \| RegExp` | Expected header value |
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
await expect(response).toHaveHeader("content-type");
|
|
492
|
+
await expect(response).toHaveHeader("content-type", {
|
|
493
|
+
value: "application/json",
|
|
494
|
+
});
|
|
495
|
+
await expect(response).toHaveHeader("content-type", { value: /json/ });
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### toRespondWithin
|
|
499
|
+
|
|
500
|
+
Asserts that a request completes within the specified time.
|
|
501
|
+
|
|
502
|
+
| Option | Type | Description |
|
|
503
|
+
| --------- | -------- | ----------------------- |
|
|
504
|
+
| `timeout` | `number` | Max response time in ms |
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
// Don't await the request - pass the promise directly
|
|
508
|
+
const request = page.request.get("/api/fast");
|
|
509
|
+
await expect(request).toRespondWithin(1000);
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Asymmetric Matchers
|
|
515
|
+
|
|
516
|
+
Asymmetric matchers can be used both in `expect().toEqual()` assertions and as
|
|
517
|
+
standalone matchers.
|
|
518
|
+
|
|
519
|
+
> **Note:** To use asymmetric matchers like `expect.toBeEmail()`, you need to
|
|
520
|
+
> import `expect` directly from ex-pw:
|
|
521
|
+
>
|
|
522
|
+
> ```typescript
|
|
523
|
+
> import { expect } from "ex-pw";
|
|
524
|
+
> ```
|
|
525
|
+
|
|
526
|
+
### String Matchers
|
|
527
|
+
|
|
528
|
+
#### toStartWith
|
|
529
|
+
|
|
530
|
+
Asserts that a string starts with the expected prefix.
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
expect("JohnDoe").toStartWith("John");
|
|
534
|
+
expect(data).toEqual({ name: expect.toStartWith("John") });
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
#### toEndWith
|
|
538
|
+
|
|
539
|
+
Asserts that a string ends with the expected suffix.
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
expect("john@example.com").toEndWith("@example.com");
|
|
543
|
+
expect(data).toEqual({ email: expect.toEndWith("@example.com") });
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### toBeUpperCase
|
|
547
|
+
|
|
548
|
+
Asserts that a string is entirely uppercase.
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
expect("HELLO").toBeUpperCase();
|
|
552
|
+
expect(data).toEqual({ code: expect.toBeUpperCase() });
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### toBeLowerCase
|
|
556
|
+
|
|
557
|
+
Asserts that a string is entirely lowercase.
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
expect("hello").toBeLowerCase();
|
|
561
|
+
expect(data).toEqual({ slug: expect.toBeLowerCase() });
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Case Format Matchers
|
|
565
|
+
|
|
566
|
+
#### toBePascalCase
|
|
567
|
+
|
|
568
|
+
Asserts that a string is in PascalCase format (e.g., `MyClassName`).
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
expect("MyClassName").toBePascalCase();
|
|
572
|
+
expect(data).toEqual({ className: expect.toBePascalCase() });
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
#### toBeCamelCase
|
|
576
|
+
|
|
577
|
+
Asserts that a string is in camelCase format (e.g., `myMethodName`).
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
expect("myMethodName").toBeCamelCase();
|
|
581
|
+
expect(data).toEqual({ methodName: expect.toBeCamelCase() });
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
#### toBeKebabCase
|
|
585
|
+
|
|
586
|
+
Asserts that a string is in kebab-case format (e.g., `my-css-class`).
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
expect("my-css-class").toBeKebabCase();
|
|
590
|
+
expect(data).toEqual({ cssClass: expect.toBeKebabCase() });
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### toBeSnakeCase
|
|
594
|
+
|
|
595
|
+
Asserts that a string is in snake_case format (e.g., `my_variable_name`).
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
expect("my_variable_name").toBeSnakeCase();
|
|
599
|
+
expect(data).toEqual({ envVar: expect.toBeSnakeCase() });
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Validation Matchers
|
|
603
|
+
|
|
604
|
+
#### toBeEmail
|
|
605
|
+
|
|
606
|
+
Asserts that a string is a valid email address.
|
|
607
|
+
|
|
608
|
+
```typescript
|
|
609
|
+
expect("user@example.com").toBeEmail();
|
|
610
|
+
expect(data).toEqual({ email: expect.toBeEmail() });
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### toBeURL
|
|
614
|
+
|
|
615
|
+
Asserts that a string is a valid URL.
|
|
616
|
+
|
|
617
|
+
| Option | Type | Description |
|
|
618
|
+
| ---------- | -------- | ------------------------------- |
|
|
619
|
+
| `protocol` | `string` | Required protocol (e.g., https) |
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
expect("https://example.com").toBeURL();
|
|
623
|
+
expect("https://example.com").toBeURL({ protocol: "https" });
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### toBeUUID
|
|
627
|
+
|
|
628
|
+
Asserts that a string is a valid UUID.
|
|
629
|
+
|
|
630
|
+
| Option | Type | Description |
|
|
631
|
+
| --------- | -------- | ------------------------- |
|
|
632
|
+
| `version` | `string` | UUID version (e.g., "v4") |
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
expect("550e8400-e29b-41d4-a716-446655440000").toBeUUID();
|
|
636
|
+
expect(id).toBeUUID("v4");
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### toBeJSON
|
|
640
|
+
|
|
641
|
+
Asserts that a string is valid JSON.
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
expect('{"key": "value"}').toBeJSON();
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Date Matchers
|
|
648
|
+
|
|
649
|
+
#### toBeISODate
|
|
650
|
+
|
|
651
|
+
Asserts that a string is a valid ISO 8601 date.
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
expect("2024-01-15T10:30:00.000Z").toBeISODate();
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
#### toBeDateString
|
|
658
|
+
|
|
659
|
+
Asserts that a string matches a custom date format.
|
|
660
|
+
|
|
661
|
+
| Option | Type | Description |
|
|
662
|
+
| -------- | -------- | ------------------------------ |
|
|
663
|
+
| `format` | `string` | Date format pattern (required) |
|
|
664
|
+
|
|
665
|
+
```typescript
|
|
666
|
+
expect("2024-01-15").toBeDateString("YYYY-MM-DD");
|
|
667
|
+
expect("01/15/2024").toBeDateString("MM/DD/YYYY");
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Number Matchers
|
|
671
|
+
|
|
672
|
+
#### toBeWithinRange
|
|
673
|
+
|
|
674
|
+
Asserts that a number is within the specified range (inclusive).
|
|
675
|
+
|
|
676
|
+
| Option | Type | Description |
|
|
677
|
+
| ------ | -------- | ------------------------ |
|
|
678
|
+
| `min` | `number` | Minimum value (required) |
|
|
679
|
+
| `max` | `number` | Maximum value (required) |
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
expect(5).toBeWithinRange(1, 10);
|
|
683
|
+
expect(response).toEqual({ count: expect.toBeWithinRange(0, 100) });
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
---
|
|
687
|
+
|
|
688
|
+
## License
|
|
689
|
+
|
|
690
|
+
MIT
|