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 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
+ [![npm version](https://badge.fury.io/js/ex-pw.svg)](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