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/dist/index.mjs
ADDED
|
@@ -0,0 +1,1663 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { expect as playwrightExpect } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
// src/matchers/locator/toBeClickable.ts
|
|
5
|
+
async function toBeClickable(locator, options = {}) {
|
|
6
|
+
const assertionName = "toBeClickable";
|
|
7
|
+
const timeout = options.timeout ?? this.timeout;
|
|
8
|
+
let pass = false;
|
|
9
|
+
let errorMessage = "";
|
|
10
|
+
try {
|
|
11
|
+
await locator.click({ trial: true, timeout });
|
|
12
|
+
pass = true;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
pass = false;
|
|
15
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
16
|
+
}
|
|
17
|
+
const message = () => {
|
|
18
|
+
if (this.isNot) {
|
|
19
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
20
|
+
isNot: this.isNot
|
|
21
|
+
}) + `
|
|
22
|
+
|
|
23
|
+
Expected: element to NOT be clickable
|
|
24
|
+
Received: element is clickable`;
|
|
25
|
+
}
|
|
26
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
27
|
+
isNot: this.isNot
|
|
28
|
+
}) + `
|
|
29
|
+
|
|
30
|
+
Expected: element '${locator}' to be clickable
|
|
31
|
+
Received: element is not clickable
|
|
32
|
+
` + (errorMessage ? `
|
|
33
|
+
Error: ${errorMessage}` : "");
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
pass,
|
|
37
|
+
message,
|
|
38
|
+
name: assertionName
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/matchers/locator/toBeCheckable.ts
|
|
43
|
+
async function toBeCheckable(locator, options = {}) {
|
|
44
|
+
const assertionName = "toBeCheckable";
|
|
45
|
+
const timeout = options.timeout ?? this.timeout;
|
|
46
|
+
let pass = false;
|
|
47
|
+
let errorMessage = "";
|
|
48
|
+
try {
|
|
49
|
+
await locator.check({ trial: true, timeout });
|
|
50
|
+
pass = true;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
pass = false;
|
|
53
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
54
|
+
}
|
|
55
|
+
const message = () => {
|
|
56
|
+
if (this.isNot) {
|
|
57
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
58
|
+
isNot: this.isNot
|
|
59
|
+
}) + `
|
|
60
|
+
|
|
61
|
+
Expected: element '${locator}' to NOT be checkable
|
|
62
|
+
Received: element is checkable`;
|
|
63
|
+
}
|
|
64
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
65
|
+
isNot: this.isNot
|
|
66
|
+
}) + `
|
|
67
|
+
|
|
68
|
+
Expected: element '${locator}' to be checkable
|
|
69
|
+
Received: element is not checkable
|
|
70
|
+
` + (errorMessage ? `
|
|
71
|
+
Error: ${errorMessage}` : "");
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
pass,
|
|
75
|
+
message,
|
|
76
|
+
name: assertionName
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/matchers/locator/toBeRequired.ts
|
|
81
|
+
import { expect } from "@playwright/test";
|
|
82
|
+
async function toBeRequired(locator, options = {}) {
|
|
83
|
+
const assertionName = "toBeRequired";
|
|
84
|
+
const timeout = options.timeout ?? this.timeout;
|
|
85
|
+
const intervals = options.intervals;
|
|
86
|
+
let pass = false;
|
|
87
|
+
let errorMessage = "";
|
|
88
|
+
try {
|
|
89
|
+
await expect.poll(
|
|
90
|
+
async () => {
|
|
91
|
+
const hasRequiredAttr = await locator.getAttribute("required");
|
|
92
|
+
if (hasRequiredAttr !== null) return true;
|
|
93
|
+
const ariaRequired = await locator.getAttribute("aria-required");
|
|
94
|
+
if (ariaRequired === "true") return true;
|
|
95
|
+
const isRequired = await locator.evaluate((el) => {
|
|
96
|
+
if ("required" in el) {
|
|
97
|
+
return el.required;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
});
|
|
101
|
+
return isRequired;
|
|
102
|
+
},
|
|
103
|
+
{ timeout, intervals }
|
|
104
|
+
).toBe(true);
|
|
105
|
+
pass = true;
|
|
106
|
+
} catch (error) {
|
|
107
|
+
pass = false;
|
|
108
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
109
|
+
}
|
|
110
|
+
const message = () => {
|
|
111
|
+
if (this.isNot) {
|
|
112
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
113
|
+
isNot: this.isNot
|
|
114
|
+
}) + `
|
|
115
|
+
|
|
116
|
+
Expected: element '${locator}' to NOT be required
|
|
117
|
+
Received: element is required`;
|
|
118
|
+
}
|
|
119
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
120
|
+
isNot: this.isNot
|
|
121
|
+
}) + `
|
|
122
|
+
|
|
123
|
+
Expected: element '${locator}' to be required
|
|
124
|
+
Received: element is not required
|
|
125
|
+
` + (errorMessage ? `
|
|
126
|
+
Details: ${errorMessage}` : "");
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
pass,
|
|
130
|
+
message,
|
|
131
|
+
name: assertionName
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/matchers/locator/toBeInvalid.ts
|
|
136
|
+
import { expect as expect2 } from "@playwright/test";
|
|
137
|
+
var INVALID_CLASSES = [
|
|
138
|
+
"ng-invalid",
|
|
139
|
+
// Angular
|
|
140
|
+
"is-invalid",
|
|
141
|
+
// Bootstrap
|
|
142
|
+
"error",
|
|
143
|
+
// Common pattern
|
|
144
|
+
"has-error",
|
|
145
|
+
// Another common pattern
|
|
146
|
+
"invalid"
|
|
147
|
+
// Generic
|
|
148
|
+
];
|
|
149
|
+
async function toBeInvalid(locator, options = {}) {
|
|
150
|
+
const assertionName = "toBeInvalid";
|
|
151
|
+
const timeout = options.timeout ?? this.timeout;
|
|
152
|
+
const intervals = options.intervals;
|
|
153
|
+
let pass = false;
|
|
154
|
+
let errorMessage = "";
|
|
155
|
+
try {
|
|
156
|
+
await expect2.poll(
|
|
157
|
+
async () => {
|
|
158
|
+
const ariaInvalid = await locator.getAttribute("aria-invalid");
|
|
159
|
+
if (ariaInvalid === "true") return true;
|
|
160
|
+
const isNativeInvalid = await locator.evaluate((el) => {
|
|
161
|
+
if ("validity" in el) {
|
|
162
|
+
return !el.validity.valid;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
});
|
|
166
|
+
if (isNativeInvalid) return true;
|
|
167
|
+
const classAttr = await locator.getAttribute("class");
|
|
168
|
+
if (classAttr) {
|
|
169
|
+
const classes = classAttr.split(/\s+/);
|
|
170
|
+
for (const invalidClass of INVALID_CLASSES) {
|
|
171
|
+
if (classes.includes(invalidClass)) return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
},
|
|
176
|
+
{ timeout, intervals }
|
|
177
|
+
).toBe(true);
|
|
178
|
+
pass = true;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
pass = false;
|
|
181
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
182
|
+
}
|
|
183
|
+
const message = () => {
|
|
184
|
+
if (this.isNot) {
|
|
185
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
186
|
+
isNot: this.isNot
|
|
187
|
+
}) + `
|
|
188
|
+
|
|
189
|
+
Expected: element '${locator}' to NOT be invalid
|
|
190
|
+
Received: element is invalid`;
|
|
191
|
+
}
|
|
192
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
193
|
+
isNot: this.isNot
|
|
194
|
+
}) + `
|
|
195
|
+
|
|
196
|
+
Expected: element '${locator}' to be invalid
|
|
197
|
+
Received: element is valid
|
|
198
|
+
` + (errorMessage ? `
|
|
199
|
+
Details: ${errorMessage}` : "");
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
pass,
|
|
203
|
+
message,
|
|
204
|
+
name: assertionName
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/matchers/locator/toBeValid.ts
|
|
209
|
+
import { expect as expect3 } from "@playwright/test";
|
|
210
|
+
var INVALID_CLASSES2 = [
|
|
211
|
+
"ng-invalid",
|
|
212
|
+
// Angular
|
|
213
|
+
"is-invalid",
|
|
214
|
+
// Bootstrap
|
|
215
|
+
"error",
|
|
216
|
+
// Common pattern
|
|
217
|
+
"has-error",
|
|
218
|
+
// Another common pattern
|
|
219
|
+
"invalid"
|
|
220
|
+
// Generic
|
|
221
|
+
];
|
|
222
|
+
async function toBeValid(locator, options = {}) {
|
|
223
|
+
const assertionName = "toBeValid";
|
|
224
|
+
const timeout = options.timeout ?? this.timeout;
|
|
225
|
+
const intervals = options.intervals;
|
|
226
|
+
let pass = false;
|
|
227
|
+
let errorMessage = "";
|
|
228
|
+
try {
|
|
229
|
+
await expect3.poll(
|
|
230
|
+
async () => {
|
|
231
|
+
const ariaInvalid = await locator.getAttribute("aria-invalid");
|
|
232
|
+
if (ariaInvalid === "true") return false;
|
|
233
|
+
const hasValidity = await locator.evaluate((el) => "validity" in el);
|
|
234
|
+
if (hasValidity) {
|
|
235
|
+
const isNativeValid = await locator.evaluate((el) => {
|
|
236
|
+
return el.validity.valid;
|
|
237
|
+
});
|
|
238
|
+
if (!isNativeValid) return false;
|
|
239
|
+
}
|
|
240
|
+
const classAttr = await locator.getAttribute("class");
|
|
241
|
+
if (classAttr) {
|
|
242
|
+
const classes = classAttr.split(/\s+/);
|
|
243
|
+
for (const invalidClass of INVALID_CLASSES2) {
|
|
244
|
+
if (classes.includes(invalidClass)) return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
},
|
|
249
|
+
{ timeout, intervals }
|
|
250
|
+
).toBe(true);
|
|
251
|
+
pass = true;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
pass = false;
|
|
254
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
255
|
+
}
|
|
256
|
+
const message = () => {
|
|
257
|
+
if (this.isNot) {
|
|
258
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
259
|
+
isNot: this.isNot
|
|
260
|
+
}) + `
|
|
261
|
+
|
|
262
|
+
Expected: element '${locator}' to NOT be valid
|
|
263
|
+
Received: element is valid`;
|
|
264
|
+
}
|
|
265
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
266
|
+
isNot: this.isNot
|
|
267
|
+
}) + `
|
|
268
|
+
|
|
269
|
+
Expected: element '${locator}' to be valid
|
|
270
|
+
Received: element is invalid
|
|
271
|
+
` + (errorMessage ? `
|
|
272
|
+
Details: ${errorMessage}` : "");
|
|
273
|
+
};
|
|
274
|
+
return {
|
|
275
|
+
pass,
|
|
276
|
+
message,
|
|
277
|
+
name: assertionName
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/matchers/locator/toHaveCount.ts
|
|
282
|
+
import { expect as expect4 } from "@playwright/test";
|
|
283
|
+
async function toHaveCountGreaterThan(locator, count, options = {}) {
|
|
284
|
+
const assertionName = "toHaveCountGreaterThan";
|
|
285
|
+
const timeout = options.timeout ?? this.timeout;
|
|
286
|
+
const intervals = options.intervals;
|
|
287
|
+
let pass = false;
|
|
288
|
+
let actualCount = 0;
|
|
289
|
+
try {
|
|
290
|
+
await expect4.poll(
|
|
291
|
+
async () => {
|
|
292
|
+
actualCount = await locator.count();
|
|
293
|
+
return actualCount;
|
|
294
|
+
},
|
|
295
|
+
{ timeout, intervals }
|
|
296
|
+
).toBeGreaterThan(count);
|
|
297
|
+
pass = true;
|
|
298
|
+
} catch {
|
|
299
|
+
pass = false;
|
|
300
|
+
}
|
|
301
|
+
const message = () => {
|
|
302
|
+
if (this.isNot) {
|
|
303
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
304
|
+
isNot: this.isNot
|
|
305
|
+
}) + `
|
|
306
|
+
|
|
307
|
+
Expected: element '${locator}' count to NOT be greater than ${count}
|
|
308
|
+
Received: ${actualCount}`;
|
|
309
|
+
}
|
|
310
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
311
|
+
isNot: this.isNot
|
|
312
|
+
}) + `
|
|
313
|
+
|
|
314
|
+
Expected: element '${locator}' count to be greater than ${count}
|
|
315
|
+
Received: ${actualCount}`;
|
|
316
|
+
};
|
|
317
|
+
return {
|
|
318
|
+
pass,
|
|
319
|
+
message,
|
|
320
|
+
name: assertionName,
|
|
321
|
+
expected: `> ${count}`,
|
|
322
|
+
actual: actualCount
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
async function toHaveCountGreaterThanOrEqual(locator, count, options = {}) {
|
|
326
|
+
const assertionName = "toHaveCountGreaterThanOrEqual";
|
|
327
|
+
const timeout = options.timeout ?? this.timeout;
|
|
328
|
+
const intervals = options.intervals;
|
|
329
|
+
let pass = false;
|
|
330
|
+
let actualCount = 0;
|
|
331
|
+
try {
|
|
332
|
+
await expect4.poll(
|
|
333
|
+
async () => {
|
|
334
|
+
actualCount = await locator.count();
|
|
335
|
+
return actualCount;
|
|
336
|
+
},
|
|
337
|
+
{ timeout, intervals }
|
|
338
|
+
).toBeGreaterThanOrEqual(count);
|
|
339
|
+
pass = true;
|
|
340
|
+
} catch {
|
|
341
|
+
pass = false;
|
|
342
|
+
}
|
|
343
|
+
const message = () => {
|
|
344
|
+
if (this.isNot) {
|
|
345
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
346
|
+
isNot: this.isNot
|
|
347
|
+
}) + `
|
|
348
|
+
|
|
349
|
+
Expected: element '${locator}' count to NOT be greater than or equal to ${count}
|
|
350
|
+
Received: ${actualCount}`;
|
|
351
|
+
}
|
|
352
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
353
|
+
isNot: this.isNot
|
|
354
|
+
}) + `
|
|
355
|
+
|
|
356
|
+
Expected: element '${locator}' count to be greater than or equal to ${count}
|
|
357
|
+
Received: ${actualCount}`;
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
pass,
|
|
361
|
+
message,
|
|
362
|
+
name: assertionName,
|
|
363
|
+
expected: `>= ${count}`,
|
|
364
|
+
actual: actualCount
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
async function toHaveCountLessThan(locator, count, options = {}) {
|
|
368
|
+
const assertionName = "toHaveCountLessThan";
|
|
369
|
+
const timeout = options.timeout ?? this.timeout;
|
|
370
|
+
const intervals = options.intervals;
|
|
371
|
+
let pass = false;
|
|
372
|
+
let actualCount = 0;
|
|
373
|
+
try {
|
|
374
|
+
await expect4.poll(
|
|
375
|
+
async () => {
|
|
376
|
+
actualCount = await locator.count();
|
|
377
|
+
return actualCount;
|
|
378
|
+
},
|
|
379
|
+
{ timeout, intervals }
|
|
380
|
+
).toBeLessThan(count);
|
|
381
|
+
pass = true;
|
|
382
|
+
} catch {
|
|
383
|
+
pass = false;
|
|
384
|
+
}
|
|
385
|
+
const message = () => {
|
|
386
|
+
if (this.isNot) {
|
|
387
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
388
|
+
isNot: this.isNot
|
|
389
|
+
}) + `
|
|
390
|
+
|
|
391
|
+
Expected: element '${locator}' count to NOT be less than ${count}
|
|
392
|
+
Received: ${actualCount}`;
|
|
393
|
+
}
|
|
394
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
395
|
+
isNot: this.isNot
|
|
396
|
+
}) + `
|
|
397
|
+
|
|
398
|
+
Expected: element '${locator}' count to be less than ${count}
|
|
399
|
+
Received: ${actualCount}`;
|
|
400
|
+
};
|
|
401
|
+
return {
|
|
402
|
+
pass,
|
|
403
|
+
message,
|
|
404
|
+
name: assertionName,
|
|
405
|
+
expected: `< ${count}`,
|
|
406
|
+
actual: actualCount
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
async function toHaveCountLessThanOrEqual(locator, count, options = {}) {
|
|
410
|
+
const assertionName = "toHaveCountLessThanOrEqual";
|
|
411
|
+
const timeout = options.timeout ?? this.timeout;
|
|
412
|
+
const intervals = options.intervals;
|
|
413
|
+
let pass = false;
|
|
414
|
+
let actualCount = 0;
|
|
415
|
+
try {
|
|
416
|
+
await expect4.poll(
|
|
417
|
+
async () => {
|
|
418
|
+
actualCount = await locator.count();
|
|
419
|
+
return actualCount;
|
|
420
|
+
},
|
|
421
|
+
{ timeout, intervals }
|
|
422
|
+
).toBeLessThanOrEqual(count);
|
|
423
|
+
pass = true;
|
|
424
|
+
} catch {
|
|
425
|
+
pass = false;
|
|
426
|
+
}
|
|
427
|
+
const message = () => {
|
|
428
|
+
if (this.isNot) {
|
|
429
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
430
|
+
isNot: this.isNot
|
|
431
|
+
}) + `
|
|
432
|
+
|
|
433
|
+
Expected: element '${locator}' count to NOT be less than or equal to ${count}
|
|
434
|
+
Received: ${actualCount}`;
|
|
435
|
+
}
|
|
436
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
437
|
+
isNot: this.isNot
|
|
438
|
+
}) + `
|
|
439
|
+
|
|
440
|
+
Expected: element '${locator}' count to be less than or equal to ${count}
|
|
441
|
+
Received: ${actualCount}`;
|
|
442
|
+
};
|
|
443
|
+
return {
|
|
444
|
+
pass,
|
|
445
|
+
message,
|
|
446
|
+
name: assertionName,
|
|
447
|
+
expected: `<= ${count}`,
|
|
448
|
+
actual: actualCount
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// src/matchers/locator/toHaveWidth.ts
|
|
453
|
+
import { expect as expect5 } from "@playwright/test";
|
|
454
|
+
async function toHaveWidth(locator, expected, options) {
|
|
455
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
456
|
+
const intervals = options?.intervals;
|
|
457
|
+
try {
|
|
458
|
+
await expect5.poll(async () => {
|
|
459
|
+
const box = await locator.boundingBox();
|
|
460
|
+
return box ? box.width : null;
|
|
461
|
+
}, { timeout, intervals }).toBe(expected);
|
|
462
|
+
return {
|
|
463
|
+
message: () => `expected element '${locator}' to have width ${expected}`,
|
|
464
|
+
pass: true
|
|
465
|
+
};
|
|
466
|
+
} catch (e) {
|
|
467
|
+
return {
|
|
468
|
+
message: () => `expected element '${locator}' to have width ${expected}`,
|
|
469
|
+
pass: false
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/matchers/locator/toHaveHeight.ts
|
|
475
|
+
import { expect as expect6 } from "@playwright/test";
|
|
476
|
+
async function toHaveHeight(locator, expected, options) {
|
|
477
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
478
|
+
const intervals = options?.intervals;
|
|
479
|
+
try {
|
|
480
|
+
await expect6.poll(async () => {
|
|
481
|
+
const box = await locator.boundingBox();
|
|
482
|
+
return box ? box.height : null;
|
|
483
|
+
}, { timeout, intervals }).toBe(expected);
|
|
484
|
+
return {
|
|
485
|
+
message: () => `expected element '${locator}' to have height ${expected}`,
|
|
486
|
+
pass: true
|
|
487
|
+
};
|
|
488
|
+
} catch (e) {
|
|
489
|
+
return {
|
|
490
|
+
message: () => `expected element '${locator}' to have height ${expected}`,
|
|
491
|
+
pass: false
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/matchers/locator/toHaveSize.ts
|
|
497
|
+
import { expect as expect7 } from "@playwright/test";
|
|
498
|
+
async function toHaveSize(locator, width, height, options) {
|
|
499
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
500
|
+
const intervals = options?.intervals;
|
|
501
|
+
try {
|
|
502
|
+
await expect7.poll(async () => {
|
|
503
|
+
const box = await locator.boundingBox();
|
|
504
|
+
return box ? { width: box.width, height: box.height } : null;
|
|
505
|
+
}, { timeout, intervals }).toEqual({ width, height });
|
|
506
|
+
return {
|
|
507
|
+
message: () => `expected element '${locator}' to have size { width: ${width}, height: ${height} }`,
|
|
508
|
+
pass: true
|
|
509
|
+
};
|
|
510
|
+
} catch (e) {
|
|
511
|
+
return {
|
|
512
|
+
message: () => `expected element '${locator}' to have size { width: ${width}, height: ${height} }`,
|
|
513
|
+
pass: false
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/matchers/locator/toHaveLoadedImage.ts
|
|
519
|
+
import { expect as expect8 } from "@playwright/test";
|
|
520
|
+
async function toHaveLoadedImage(locator, options) {
|
|
521
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
522
|
+
const intervals = options?.intervals;
|
|
523
|
+
try {
|
|
524
|
+
await expect8.poll(async () => {
|
|
525
|
+
return await locator.evaluate((el) => {
|
|
526
|
+
if (!(el instanceof HTMLImageElement)) {
|
|
527
|
+
throw new Error("Element is not an HTMLImageElement");
|
|
528
|
+
}
|
|
529
|
+
return el.complete && el.naturalWidth > 0;
|
|
530
|
+
});
|
|
531
|
+
}, { timeout, intervals }).toBe(true);
|
|
532
|
+
return {
|
|
533
|
+
message: () => `expected element '${locator}' to have loaded image`,
|
|
534
|
+
pass: true
|
|
535
|
+
};
|
|
536
|
+
} catch (e) {
|
|
537
|
+
if (e.message && e.message.includes("Element is not an HTMLImageElement")) {
|
|
538
|
+
return {
|
|
539
|
+
message: () => `expected element '${locator}' to have loaded image, but it is not an HTMLImageElement`,
|
|
540
|
+
pass: false
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
message: () => `expected element '${locator}' to have loaded image`,
|
|
545
|
+
pass: false
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/matchers/locator/index.ts
|
|
551
|
+
var locatorMatchers = {
|
|
552
|
+
toBeClickable,
|
|
553
|
+
toBeCheckable,
|
|
554
|
+
toBeRequired,
|
|
555
|
+
toBeInvalid,
|
|
556
|
+
toBeValid,
|
|
557
|
+
toHaveCountGreaterThan,
|
|
558
|
+
toHaveCountGreaterThanOrEqual,
|
|
559
|
+
toHaveCountLessThan,
|
|
560
|
+
toHaveCountLessThanOrEqual,
|
|
561
|
+
toHaveWidth,
|
|
562
|
+
toHaveHeight,
|
|
563
|
+
toHaveSize,
|
|
564
|
+
toHaveLoadedImage
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// src/matchers/page/toHaveNoErrors.ts
|
|
568
|
+
function toHaveNoErrors(testObject, options = {}) {
|
|
569
|
+
const assertionName = "toHaveNoErrors";
|
|
570
|
+
const { ignore } = options;
|
|
571
|
+
const testInfo = testObject.info();
|
|
572
|
+
let errors = testInfo.errors || [];
|
|
573
|
+
if (ignore) {
|
|
574
|
+
errors = errors.filter((error) => {
|
|
575
|
+
const message2 = error.message || "";
|
|
576
|
+
return !ignore.test(message2);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const pass = errors.length === 0;
|
|
580
|
+
const errorMessages = errors.map((e, i) => ` ${i + 1}. ${e.message || "Unknown error"}`).join("\n");
|
|
581
|
+
const message = () => {
|
|
582
|
+
if (this.isNot) {
|
|
583
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
584
|
+
isNot: this.isNot
|
|
585
|
+
}) + `
|
|
586
|
+
|
|
587
|
+
Expected: to have errors
|
|
588
|
+
Received: no errors`;
|
|
589
|
+
}
|
|
590
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
591
|
+
isNot: this.isNot
|
|
592
|
+
}) + `
|
|
593
|
+
|
|
594
|
+
Expected: no errors
|
|
595
|
+
Received: ${errors.length} error(s)
|
|
596
|
+
|
|
597
|
+
` + errorMessages;
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
pass,
|
|
601
|
+
message,
|
|
602
|
+
name: assertionName,
|
|
603
|
+
expected: 0,
|
|
604
|
+
actual: errors.length
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/matchers/page/toHaveCookie.ts
|
|
609
|
+
import { expect as expect9 } from "@playwright/test";
|
|
610
|
+
async function toHaveCookie(page, name, options = {}) {
|
|
611
|
+
const assertionName = "toHaveCookie";
|
|
612
|
+
const { value, domain, timeout = this.timeout, intervals } = options;
|
|
613
|
+
let pass = false;
|
|
614
|
+
let actualValue;
|
|
615
|
+
let foundCookie = false;
|
|
616
|
+
try {
|
|
617
|
+
await expect9.poll(
|
|
618
|
+
async () => {
|
|
619
|
+
const cookies = await page.context().cookies();
|
|
620
|
+
const cookie = cookies.find((c) => {
|
|
621
|
+
if (c.name !== name) return false;
|
|
622
|
+
if (domain && c.domain !== domain) return false;
|
|
623
|
+
return true;
|
|
624
|
+
});
|
|
625
|
+
if (!cookie) {
|
|
626
|
+
foundCookie = false;
|
|
627
|
+
actualValue = void 0;
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
foundCookie = true;
|
|
631
|
+
actualValue = cookie.value;
|
|
632
|
+
if (value !== void 0) {
|
|
633
|
+
if (typeof value === "string") {
|
|
634
|
+
return cookie.value === value;
|
|
635
|
+
} else {
|
|
636
|
+
return value.test(cookie.value);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return true;
|
|
640
|
+
},
|
|
641
|
+
{ timeout, intervals }
|
|
642
|
+
).toBe(true);
|
|
643
|
+
pass = true;
|
|
644
|
+
} catch {
|
|
645
|
+
pass = false;
|
|
646
|
+
}
|
|
647
|
+
const expectedDesc = value ? `cookie "${name}" with value ${value instanceof RegExp ? value : `"${value}"`}` : `cookie "${name}"`;
|
|
648
|
+
const message = () => {
|
|
649
|
+
if (this.isNot) {
|
|
650
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
651
|
+
isNot: this.isNot
|
|
652
|
+
}) + `
|
|
653
|
+
|
|
654
|
+
Expected: NOT to have ${expectedDesc}
|
|
655
|
+
Received: cookie found with value "${actualValue}"`;
|
|
656
|
+
}
|
|
657
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
658
|
+
isNot: this.isNot
|
|
659
|
+
}) + `
|
|
660
|
+
|
|
661
|
+
Expected: ${expectedDesc}
|
|
662
|
+
` + (foundCookie ? `Received: cookie found with value "${actualValue}"` : `Received: cookie not found`);
|
|
663
|
+
};
|
|
664
|
+
return {
|
|
665
|
+
pass,
|
|
666
|
+
message,
|
|
667
|
+
name: assertionName,
|
|
668
|
+
expected: value ?? name,
|
|
669
|
+
actual: actualValue
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/matchers/page/toHaveLocalStorage.ts
|
|
674
|
+
import { expect as expect10 } from "@playwright/test";
|
|
675
|
+
|
|
676
|
+
// src/utils/matcherUtils.ts
|
|
677
|
+
function formatValue(value) {
|
|
678
|
+
if (value === null) return "null";
|
|
679
|
+
if (value === void 0) return "undefined";
|
|
680
|
+
if (typeof value === "string") return `"${value}"`;
|
|
681
|
+
if (typeof value === "object") {
|
|
682
|
+
try {
|
|
683
|
+
return JSON.stringify(value, null, 2);
|
|
684
|
+
} catch {
|
|
685
|
+
return String(value);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return String(value);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/matchers/page/toHaveLocalStorage.ts
|
|
692
|
+
async function toHaveLocalStorage(page, key, options = {}) {
|
|
693
|
+
const assertionName = "toHaveLocalStorage";
|
|
694
|
+
const { value, timeout = this.timeout, intervals } = options;
|
|
695
|
+
let pass = false;
|
|
696
|
+
let actualValue;
|
|
697
|
+
let foundKey = false;
|
|
698
|
+
try {
|
|
699
|
+
await expect10.poll(
|
|
700
|
+
async () => {
|
|
701
|
+
const storageState = await page.context().storageState();
|
|
702
|
+
const pageUrl = page.url();
|
|
703
|
+
let origin;
|
|
704
|
+
try {
|
|
705
|
+
origin = new URL(pageUrl).origin;
|
|
706
|
+
} catch {
|
|
707
|
+
origin = pageUrl;
|
|
708
|
+
}
|
|
709
|
+
const originStorage = storageState.origins.find(
|
|
710
|
+
(o) => o.origin === origin
|
|
711
|
+
);
|
|
712
|
+
if (!originStorage) {
|
|
713
|
+
foundKey = false;
|
|
714
|
+
actualValue = void 0;
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
const item = originStorage.localStorage.find((ls) => ls.name === key);
|
|
718
|
+
if (!item) {
|
|
719
|
+
foundKey = false;
|
|
720
|
+
actualValue = void 0;
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
foundKey = true;
|
|
724
|
+
try {
|
|
725
|
+
actualValue = JSON.parse(item.value);
|
|
726
|
+
} catch {
|
|
727
|
+
actualValue = item.value;
|
|
728
|
+
}
|
|
729
|
+
if (value !== void 0) {
|
|
730
|
+
try {
|
|
731
|
+
expect10(actualValue).toEqual(value);
|
|
732
|
+
return true;
|
|
733
|
+
} catch {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return true;
|
|
738
|
+
},
|
|
739
|
+
{ timeout, intervals }
|
|
740
|
+
).toBe(true);
|
|
741
|
+
pass = true;
|
|
742
|
+
} catch {
|
|
743
|
+
pass = false;
|
|
744
|
+
}
|
|
745
|
+
const expectedDesc = value !== void 0 ? `localStorage key "${key}" with value ${formatValue(value)}` : `localStorage key "${key}"`;
|
|
746
|
+
const message = () => {
|
|
747
|
+
if (this.isNot) {
|
|
748
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
749
|
+
isNot: this.isNot
|
|
750
|
+
}) + `
|
|
751
|
+
|
|
752
|
+
Expected: NOT to have ${expectedDesc}
|
|
753
|
+
Received: key found with value ${formatValue(actualValue)}`;
|
|
754
|
+
}
|
|
755
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
756
|
+
isNot: this.isNot
|
|
757
|
+
}) + `
|
|
758
|
+
|
|
759
|
+
Expected: ${expectedDesc}
|
|
760
|
+
` + (foundKey ? `Received: key found with value ${formatValue(actualValue)}` : `Received: key not found`);
|
|
761
|
+
};
|
|
762
|
+
return {
|
|
763
|
+
pass,
|
|
764
|
+
message,
|
|
765
|
+
name: assertionName,
|
|
766
|
+
expected: value ?? key,
|
|
767
|
+
actual: actualValue
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/matchers/page/toHaveSessionStorage.ts
|
|
772
|
+
import { expect as expect11 } from "@playwright/test";
|
|
773
|
+
async function toHaveSessionStorage(page, key, options = {}) {
|
|
774
|
+
const assertionName = "toHaveSessionStorage";
|
|
775
|
+
const { value, timeout = this.timeout, intervals } = options;
|
|
776
|
+
let pass = false;
|
|
777
|
+
let actualValue;
|
|
778
|
+
let foundKey = false;
|
|
779
|
+
try {
|
|
780
|
+
await expect11.poll(
|
|
781
|
+
async () => {
|
|
782
|
+
const rawValue = await page.evaluate((k) => {
|
|
783
|
+
return window.sessionStorage.getItem(k);
|
|
784
|
+
}, key);
|
|
785
|
+
if (rawValue === null) {
|
|
786
|
+
foundKey = false;
|
|
787
|
+
actualValue = void 0;
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
foundKey = true;
|
|
791
|
+
try {
|
|
792
|
+
actualValue = JSON.parse(rawValue);
|
|
793
|
+
} catch {
|
|
794
|
+
actualValue = rawValue;
|
|
795
|
+
}
|
|
796
|
+
if (value !== void 0) {
|
|
797
|
+
try {
|
|
798
|
+
expect11(actualValue).toEqual(value);
|
|
799
|
+
return true;
|
|
800
|
+
} catch {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return true;
|
|
805
|
+
},
|
|
806
|
+
{ timeout, intervals }
|
|
807
|
+
).toBe(true);
|
|
808
|
+
pass = true;
|
|
809
|
+
} catch {
|
|
810
|
+
pass = false;
|
|
811
|
+
}
|
|
812
|
+
const expectedDesc = value !== void 0 ? `sessionStorage key "${key}" with value ${formatValue(value)}` : `sessionStorage key "${key}"`;
|
|
813
|
+
const message = () => {
|
|
814
|
+
if (this.isNot) {
|
|
815
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
816
|
+
isNot: this.isNot
|
|
817
|
+
}) + `
|
|
818
|
+
|
|
819
|
+
Expected: NOT to have ${expectedDesc}
|
|
820
|
+
Received: key found with value ${formatValue(actualValue)}`;
|
|
821
|
+
}
|
|
822
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
823
|
+
isNot: this.isNot
|
|
824
|
+
}) + `
|
|
825
|
+
|
|
826
|
+
Expected: ${expectedDesc}
|
|
827
|
+
` + (foundKey ? `Received: key found with value ${formatValue(actualValue)}` : `Received: key not found`);
|
|
828
|
+
};
|
|
829
|
+
return {
|
|
830
|
+
pass,
|
|
831
|
+
message,
|
|
832
|
+
name: assertionName,
|
|
833
|
+
expected: value ?? key,
|
|
834
|
+
actual: actualValue
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/matchers/page/toHaveClipboardText.ts
|
|
839
|
+
import { expect as expect12 } from "@playwright/test";
|
|
840
|
+
async function toHaveClipboardText(page, expected, options) {
|
|
841
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
842
|
+
const intervals = options?.intervals;
|
|
843
|
+
try {
|
|
844
|
+
const assertion = expect12.poll(async () => {
|
|
845
|
+
return await page.evaluate(() => navigator.clipboard.readText());
|
|
846
|
+
}, { timeout, intervals });
|
|
847
|
+
if (expected instanceof RegExp) {
|
|
848
|
+
await assertion.toMatch(expected);
|
|
849
|
+
} else {
|
|
850
|
+
await assertion.toBe(expected);
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
message: () => `expected clipboard to have text ${expected}`,
|
|
854
|
+
pass: true
|
|
855
|
+
};
|
|
856
|
+
} catch (e) {
|
|
857
|
+
return {
|
|
858
|
+
message: () => `expected clipboard to have text ${expected} but got error or different text: ${e.message}`,
|
|
859
|
+
pass: false
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/matchers/page/toHaveRequest.ts
|
|
865
|
+
import { expect as expect13 } from "@playwright/test";
|
|
866
|
+
async function toHaveRequest(page, options = {}) {
|
|
867
|
+
const assertionName = "toHaveRequest";
|
|
868
|
+
const { method, status, url, timeout = this.timeout, intervals } = options;
|
|
869
|
+
let matchedRequest;
|
|
870
|
+
let requests = [];
|
|
871
|
+
let pass = false;
|
|
872
|
+
try {
|
|
873
|
+
await expect13.poll(
|
|
874
|
+
async () => {
|
|
875
|
+
requests = await page.requests();
|
|
876
|
+
for (const request of requests) {
|
|
877
|
+
let matches = true;
|
|
878
|
+
if (url !== void 0) {
|
|
879
|
+
const requestUrl = request.url();
|
|
880
|
+
if (typeof url === "string") {
|
|
881
|
+
if (!requestUrl.includes(url)) matches = false;
|
|
882
|
+
} else {
|
|
883
|
+
if (!url.test(requestUrl)) matches = false;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (matches && method !== void 0) {
|
|
887
|
+
if (request.method().toUpperCase() !== method.toUpperCase()) matches = false;
|
|
888
|
+
}
|
|
889
|
+
if (matches && status !== void 0) {
|
|
890
|
+
const response = await request.response();
|
|
891
|
+
if (!response || response.status() !== status) matches = false;
|
|
892
|
+
}
|
|
893
|
+
if (matches) {
|
|
894
|
+
matchedRequest = request;
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return false;
|
|
899
|
+
},
|
|
900
|
+
{ timeout, intervals }
|
|
901
|
+
).toBe(true);
|
|
902
|
+
pass = true;
|
|
903
|
+
} catch {
|
|
904
|
+
pass = false;
|
|
905
|
+
}
|
|
906
|
+
const criteriaDesc = [
|
|
907
|
+
url ? `URL matching ${url instanceof RegExp ? url : `"${url}"`}` : null,
|
|
908
|
+
method ? `method "${method}"` : null,
|
|
909
|
+
status ? `status ${status}` : null
|
|
910
|
+
].filter(Boolean).join(", ");
|
|
911
|
+
const message = () => {
|
|
912
|
+
if (this.isNot) {
|
|
913
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
914
|
+
isNot: this.isNot
|
|
915
|
+
}) + `
|
|
916
|
+
|
|
917
|
+
Expected: NOT to have request with ${criteriaDesc || "any criteria"}
|
|
918
|
+
Received: found matching request: ${matchedRequest?.method()} ${matchedRequest?.url()}`;
|
|
919
|
+
}
|
|
920
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
921
|
+
isNot: this.isNot
|
|
922
|
+
}) + `
|
|
923
|
+
|
|
924
|
+
Expected: request with ${criteriaDesc || "any criteria"}
|
|
925
|
+
Received: no matching request found among ${requests.length} captured requests
|
|
926
|
+
|
|
927
|
+
Note: page.requests() only returns up to 100 most recent requests`;
|
|
928
|
+
};
|
|
929
|
+
return {
|
|
930
|
+
pass,
|
|
931
|
+
message,
|
|
932
|
+
name: assertionName
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/matchers/page/toHaveConsoleMessage.ts
|
|
937
|
+
import { expect as expect14 } from "@playwright/test";
|
|
938
|
+
async function toHaveConsoleMessage(page, options = {}) {
|
|
939
|
+
const assertionName = "toHaveConsoleMessage";
|
|
940
|
+
const { type, text, timeout = this.timeout, intervals } = options;
|
|
941
|
+
let matchedMessage;
|
|
942
|
+
let messages = [];
|
|
943
|
+
let pass = false;
|
|
944
|
+
try {
|
|
945
|
+
await expect14.poll(
|
|
946
|
+
async () => {
|
|
947
|
+
messages = await page.consoleMessages();
|
|
948
|
+
matchedMessage = messages.find((msg) => {
|
|
949
|
+
if (type !== void 0) {
|
|
950
|
+
if (msg.type() !== type) return false;
|
|
951
|
+
}
|
|
952
|
+
if (text !== void 0) {
|
|
953
|
+
const msgText = msg.text();
|
|
954
|
+
if (typeof text === "string") {
|
|
955
|
+
if (!msgText.includes(text)) return false;
|
|
956
|
+
} else {
|
|
957
|
+
if (!text.test(msgText)) return false;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return true;
|
|
961
|
+
});
|
|
962
|
+
return matchedMessage !== void 0;
|
|
963
|
+
},
|
|
964
|
+
{ timeout, intervals }
|
|
965
|
+
).toBe(true);
|
|
966
|
+
pass = true;
|
|
967
|
+
} catch {
|
|
968
|
+
pass = false;
|
|
969
|
+
}
|
|
970
|
+
const criteriaDesc = [
|
|
971
|
+
type ? `type "${type}"` : null,
|
|
972
|
+
text ? `text matching ${text instanceof RegExp ? text : `"${text}"`}` : null
|
|
973
|
+
].filter(Boolean).join(", ");
|
|
974
|
+
const message = () => {
|
|
975
|
+
if (this.isNot) {
|
|
976
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
977
|
+
isNot: this.isNot
|
|
978
|
+
}) + `
|
|
979
|
+
|
|
980
|
+
Expected: NOT to have console message with ${criteriaDesc || "any criteria"}
|
|
981
|
+
Received: found matching message: "${matchedMessage?.text()}"`;
|
|
982
|
+
}
|
|
983
|
+
const capturedMessages = messages.slice(0, 5).map((m) => ` [${m.type()}] ${m.text().substring(0, 50)}${m.text().length > 50 ? "..." : ""}`).join("\n");
|
|
984
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
985
|
+
isNot: this.isNot
|
|
986
|
+
}) + `
|
|
987
|
+
|
|
988
|
+
Expected: console message with ${criteriaDesc || "any criteria"}
|
|
989
|
+
Received: no matching message found among ${messages.length} captured messages
|
|
990
|
+
` + (capturedMessages ? `
|
|
991
|
+
Recent messages:
|
|
992
|
+
${capturedMessages}` : "") + `
|
|
993
|
+
|
|
994
|
+
Note: page.consoleMessages() only returns up to 200 most recent messages`;
|
|
995
|
+
};
|
|
996
|
+
return {
|
|
997
|
+
pass,
|
|
998
|
+
message,
|
|
999
|
+
name: assertionName
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/matchers/page/toHavePageError.ts
|
|
1004
|
+
import { expect as expect15 } from "@playwright/test";
|
|
1005
|
+
async function toHavePageError(page, options = {}) {
|
|
1006
|
+
const assertionName = "toHavePageError";
|
|
1007
|
+
const { message: errorMessage, name: errorName, timeout = this.timeout, intervals } = options;
|
|
1008
|
+
let matchedError;
|
|
1009
|
+
let errors = [];
|
|
1010
|
+
let pass = false;
|
|
1011
|
+
try {
|
|
1012
|
+
await expect15.poll(
|
|
1013
|
+
async () => {
|
|
1014
|
+
errors = await page.pageErrors();
|
|
1015
|
+
matchedError = errors.find((error) => {
|
|
1016
|
+
if (errorMessage !== void 0) {
|
|
1017
|
+
const msg = error.message;
|
|
1018
|
+
if (typeof errorMessage === "string") {
|
|
1019
|
+
if (!msg.includes(errorMessage)) return false;
|
|
1020
|
+
} else {
|
|
1021
|
+
if (!errorMessage.test(msg)) return false;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (errorName !== void 0) {
|
|
1025
|
+
if (error.name !== errorName) return false;
|
|
1026
|
+
}
|
|
1027
|
+
return true;
|
|
1028
|
+
});
|
|
1029
|
+
return matchedError !== void 0;
|
|
1030
|
+
},
|
|
1031
|
+
{ timeout, intervals }
|
|
1032
|
+
).toBe(true);
|
|
1033
|
+
pass = true;
|
|
1034
|
+
} catch {
|
|
1035
|
+
pass = false;
|
|
1036
|
+
}
|
|
1037
|
+
const criteriaDesc = [
|
|
1038
|
+
errorMessage ? `message matching ${errorMessage instanceof RegExp ? errorMessage : `"${errorMessage}"`}` : null,
|
|
1039
|
+
errorName ? `name "${errorName}"` : null
|
|
1040
|
+
].filter(Boolean).join(", ");
|
|
1041
|
+
const message = () => {
|
|
1042
|
+
if (this.isNot) {
|
|
1043
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1044
|
+
isNot: this.isNot
|
|
1045
|
+
}) + `
|
|
1046
|
+
|
|
1047
|
+
Expected: NOT to have page error with ${criteriaDesc || "any criteria"}
|
|
1048
|
+
Received: found matching error: ${matchedError?.name}: ${matchedError?.message}`;
|
|
1049
|
+
}
|
|
1050
|
+
const capturedErrors = errors.slice(0, 5).map((e) => ` ${e.name}: ${e.message.substring(0, 60)}${e.message.length > 60 ? "..." : ""}`).join("\n");
|
|
1051
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1052
|
+
isNot: this.isNot
|
|
1053
|
+
}) + `
|
|
1054
|
+
|
|
1055
|
+
Expected: page error with ${criteriaDesc || "any criteria"}
|
|
1056
|
+
Received: no matching error found among ${errors.length} captured errors
|
|
1057
|
+
` + (capturedErrors ? `
|
|
1058
|
+
Recent errors:
|
|
1059
|
+
${capturedErrors}` : "") + `
|
|
1060
|
+
|
|
1061
|
+
Note: page.pageErrors() only returns up to 200 most recent errors`;
|
|
1062
|
+
};
|
|
1063
|
+
return {
|
|
1064
|
+
pass,
|
|
1065
|
+
message,
|
|
1066
|
+
name: assertionName
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// src/matchers/page/index.ts
|
|
1071
|
+
var pageMatchers = {
|
|
1072
|
+
toHaveNoErrors,
|
|
1073
|
+
toHaveCookie,
|
|
1074
|
+
toHaveLocalStorage,
|
|
1075
|
+
toHaveSessionStorage,
|
|
1076
|
+
toHaveClipboardText,
|
|
1077
|
+
toHaveRequest,
|
|
1078
|
+
toHaveConsoleMessage,
|
|
1079
|
+
toHavePageError
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// src/matchers/api/toMatchJSON.ts
|
|
1083
|
+
import { expect as expect16 } from "@playwright/test";
|
|
1084
|
+
async function toMatchJSON(response, expected) {
|
|
1085
|
+
const assertionName = "toMatchJSON";
|
|
1086
|
+
let pass = false;
|
|
1087
|
+
let actualBody;
|
|
1088
|
+
let parseError = null;
|
|
1089
|
+
try {
|
|
1090
|
+
actualBody = await response.json();
|
|
1091
|
+
} catch (error) {
|
|
1092
|
+
parseError = error instanceof Error ? error.message : String(error);
|
|
1093
|
+
}
|
|
1094
|
+
if (parseError) {
|
|
1095
|
+
pass = false;
|
|
1096
|
+
} else {
|
|
1097
|
+
try {
|
|
1098
|
+
expect16(actualBody).toEqual(expected);
|
|
1099
|
+
pass = true;
|
|
1100
|
+
} catch {
|
|
1101
|
+
pass = false;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
const message = () => {
|
|
1105
|
+
if (parseError) {
|
|
1106
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1107
|
+
isNot: this.isNot
|
|
1108
|
+
}) + `
|
|
1109
|
+
|
|
1110
|
+
Failed to parse response body as JSON:
|
|
1111
|
+
${parseError}`;
|
|
1112
|
+
}
|
|
1113
|
+
const diff = this.utils.diff(expected, actualBody);
|
|
1114
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1115
|
+
isNot: this.isNot
|
|
1116
|
+
}) + "\n\n" + (this.isNot ? `Expected: not ${formatValue(expected)}
|
|
1117
|
+
` : `Expected: ${formatValue(expected)}
|
|
1118
|
+
`) + `Received: ${formatValue(actualBody)}` + (diff ? `
|
|
1119
|
+
|
|
1120
|
+
Difference:
|
|
1121
|
+
${diff}` : "");
|
|
1122
|
+
};
|
|
1123
|
+
return {
|
|
1124
|
+
pass,
|
|
1125
|
+
message,
|
|
1126
|
+
name: assertionName,
|
|
1127
|
+
expected,
|
|
1128
|
+
actual: actualBody
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/matchers/api/toMatchSchema.ts
|
|
1133
|
+
async function toMatchSchema(response, schema) {
|
|
1134
|
+
const assertionName = "toMatchSchema";
|
|
1135
|
+
let pass = false;
|
|
1136
|
+
let actualBody;
|
|
1137
|
+
let parseError = null;
|
|
1138
|
+
let validationErrors = [];
|
|
1139
|
+
try {
|
|
1140
|
+
actualBody = await response.json();
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
parseError = error instanceof Error ? error.message : String(error);
|
|
1143
|
+
}
|
|
1144
|
+
if (parseError) {
|
|
1145
|
+
pass = false;
|
|
1146
|
+
} else {
|
|
1147
|
+
const result = schema.safeParse(actualBody);
|
|
1148
|
+
pass = result.success;
|
|
1149
|
+
if (!result.success) {
|
|
1150
|
+
validationErrors = formatZodErrors(result.error);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
const message = () => {
|
|
1154
|
+
if (parseError) {
|
|
1155
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1156
|
+
isNot: this.isNot
|
|
1157
|
+
}) + `
|
|
1158
|
+
|
|
1159
|
+
Failed to parse response body as JSON:
|
|
1160
|
+
${parseError}`;
|
|
1161
|
+
}
|
|
1162
|
+
if (this.isNot) {
|
|
1163
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1164
|
+
isNot: this.isNot
|
|
1165
|
+
}) + `
|
|
1166
|
+
|
|
1167
|
+
Expected: response to NOT match schema
|
|
1168
|
+
Received: response matches schema
|
|
1169
|
+
|
|
1170
|
+
Body: ${formatValue(actualBody)}`;
|
|
1171
|
+
}
|
|
1172
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1173
|
+
isNot: this.isNot
|
|
1174
|
+
}) + `
|
|
1175
|
+
|
|
1176
|
+
Expected: response to match schema
|
|
1177
|
+
Received: validation failed
|
|
1178
|
+
|
|
1179
|
+
Validation errors:
|
|
1180
|
+
${validationErrors.map((e) => ` \u2022 ${e}`).join("\n")}
|
|
1181
|
+
|
|
1182
|
+
Body: ${formatValue(actualBody)}`;
|
|
1183
|
+
};
|
|
1184
|
+
return {
|
|
1185
|
+
pass,
|
|
1186
|
+
message,
|
|
1187
|
+
name: assertionName,
|
|
1188
|
+
expected: "valid schema",
|
|
1189
|
+
actual: actualBody
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function formatZodErrors(error) {
|
|
1193
|
+
return error.errors.map((e) => {
|
|
1194
|
+
const path = e.path.length > 0 ? `${e.path.join(".")}: ` : "";
|
|
1195
|
+
return `${path}${e.message}`;
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/matchers/api/toHaveStatus.ts
|
|
1200
|
+
async function toHaveStatus(response, expected) {
|
|
1201
|
+
const assertionName = "toHaveStatus";
|
|
1202
|
+
const actualStatus = response.status();
|
|
1203
|
+
let pass = false;
|
|
1204
|
+
if (typeof expected === "number") {
|
|
1205
|
+
pass = actualStatus === expected;
|
|
1206
|
+
} else {
|
|
1207
|
+
pass = actualStatus >= expected.min && actualStatus <= expected.max;
|
|
1208
|
+
}
|
|
1209
|
+
const expectedDesc = typeof expected === "number" ? `${expected}` : `${expected.min}-${expected.max}`;
|
|
1210
|
+
const message = () => {
|
|
1211
|
+
if (this.isNot) {
|
|
1212
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1213
|
+
isNot: this.isNot
|
|
1214
|
+
}) + `
|
|
1215
|
+
|
|
1216
|
+
Expected: status NOT to be ${expectedDesc}
|
|
1217
|
+
Received: ${actualStatus}`;
|
|
1218
|
+
}
|
|
1219
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1220
|
+
isNot: this.isNot
|
|
1221
|
+
}) + `
|
|
1222
|
+
|
|
1223
|
+
Expected: status ${expectedDesc}
|
|
1224
|
+
Received: ${actualStatus}`;
|
|
1225
|
+
};
|
|
1226
|
+
return {
|
|
1227
|
+
pass,
|
|
1228
|
+
message,
|
|
1229
|
+
name: assertionName,
|
|
1230
|
+
expected: expectedDesc,
|
|
1231
|
+
actual: actualStatus
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/matchers/api/toHaveHeader.ts
|
|
1236
|
+
async function toHaveHeader(response, name, options = {}) {
|
|
1237
|
+
const assertionName = "toHaveHeader";
|
|
1238
|
+
const { value } = options;
|
|
1239
|
+
const headers = response.headers();
|
|
1240
|
+
const lowerName = name.toLowerCase();
|
|
1241
|
+
const actualValue = Object.entries(headers).find(
|
|
1242
|
+
([key]) => key.toLowerCase() === lowerName
|
|
1243
|
+
)?.[1];
|
|
1244
|
+
let pass = actualValue !== void 0;
|
|
1245
|
+
if (pass && value !== void 0) {
|
|
1246
|
+
if (typeof value === "string") {
|
|
1247
|
+
pass = actualValue === value;
|
|
1248
|
+
} else {
|
|
1249
|
+
pass = value.test(actualValue);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const expectedDesc = value ? `header "${name}" with value ${value instanceof RegExp ? value : `"${value}"`}` : `header "${name}"`;
|
|
1253
|
+
const message = () => {
|
|
1254
|
+
if (this.isNot) {
|
|
1255
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1256
|
+
isNot: this.isNot
|
|
1257
|
+
}) + `
|
|
1258
|
+
|
|
1259
|
+
Expected: NOT to have ${expectedDesc}
|
|
1260
|
+
Received: header found with value "${actualValue}"`;
|
|
1261
|
+
}
|
|
1262
|
+
return this.utils.matcherHint(assertionName, void 0, void 0, {
|
|
1263
|
+
isNot: this.isNot
|
|
1264
|
+
}) + `
|
|
1265
|
+
|
|
1266
|
+
Expected: ${expectedDesc}
|
|
1267
|
+
` + (actualValue !== void 0 ? `Received: header found with value "${actualValue}"` : `Received: header not found`);
|
|
1268
|
+
};
|
|
1269
|
+
return {
|
|
1270
|
+
pass,
|
|
1271
|
+
message,
|
|
1272
|
+
name: assertionName,
|
|
1273
|
+
expected: value ?? name,
|
|
1274
|
+
actual: actualValue
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/matchers/api/toRespondWithin.ts
|
|
1279
|
+
async function toRespondWithin(response, timeout) {
|
|
1280
|
+
if (!(response instanceof Promise) && typeof response !== "function") {
|
|
1281
|
+
return {
|
|
1282
|
+
message: () => `toRespondWithin: expected received value to be a Promise or async function, but got ${typeof response}. Did you await the request? Remove 'wait' to allow measurement.`,
|
|
1283
|
+
pass: false
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
const start = Date.now();
|
|
1287
|
+
try {
|
|
1288
|
+
if (typeof response === "function") {
|
|
1289
|
+
await response();
|
|
1290
|
+
} else {
|
|
1291
|
+
await response;
|
|
1292
|
+
}
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
}
|
|
1295
|
+
const duration = Date.now() - start;
|
|
1296
|
+
const pass = duration <= timeout;
|
|
1297
|
+
if (pass) {
|
|
1298
|
+
return {
|
|
1299
|
+
message: () => `expected request not to answer within ${timeout}ms, took ${duration}ms`,
|
|
1300
|
+
pass: true
|
|
1301
|
+
};
|
|
1302
|
+
} else {
|
|
1303
|
+
return {
|
|
1304
|
+
message: () => `expected request to answer within ${timeout}ms, but took ${duration}ms`,
|
|
1305
|
+
pass: false
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/matchers/api/index.ts
|
|
1311
|
+
var apiMatchers = {
|
|
1312
|
+
toMatchJSON,
|
|
1313
|
+
toMatchSchema,
|
|
1314
|
+
toHaveStatus,
|
|
1315
|
+
toHaveHeader,
|
|
1316
|
+
toRespondWithin
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// src/matchers/asymmetric/toBeWithinRange.ts
|
|
1320
|
+
function toBeWithinRange(received, min, max) {
|
|
1321
|
+
const pass = typeof received === "number" && received >= min && received <= max;
|
|
1322
|
+
return {
|
|
1323
|
+
message: () => pass ? `expected ${received} not to be within range ${min} - ${max}` : `expected ${received} to be within range ${min} - ${max}`,
|
|
1324
|
+
pass
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/matchers/asymmetric/toBeUUID.ts
|
|
1329
|
+
var UUID_PATTERNS = {
|
|
1330
|
+
v1: /^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
1331
|
+
v4: /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
1332
|
+
v5: /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
|
|
1333
|
+
any: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
1334
|
+
};
|
|
1335
|
+
function toBeUUID(received, version) {
|
|
1336
|
+
const pattern = version ? UUID_PATTERNS[version] : UUID_PATTERNS.any;
|
|
1337
|
+
const versionLabel = version || "any";
|
|
1338
|
+
const pass = typeof received === "string" && pattern.test(received);
|
|
1339
|
+
return {
|
|
1340
|
+
message: () => pass ? `expected ${received} not to be a valid UUID (${versionLabel})` : `expected ${received} to be a valid UUID (${versionLabel})`,
|
|
1341
|
+
pass
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/matchers/asymmetric/toBeISODate.ts
|
|
1346
|
+
var ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?(Z|[+-]\d{2}:?\d{2})?$/;
|
|
1347
|
+
function toBeISODate(received) {
|
|
1348
|
+
let pass = false;
|
|
1349
|
+
if (typeof received === "string" && ISO_DATE_PATTERN.test(received)) {
|
|
1350
|
+
const date = new Date(received);
|
|
1351
|
+
if (!isNaN(date.getTime())) {
|
|
1352
|
+
pass = true;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
message: () => pass ? `expected ${received} not to be a valid ISO date` : `expected ${received} to be a valid ISO date`,
|
|
1357
|
+
pass
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// src/matchers/asymmetric/toBeDateString.ts
|
|
1362
|
+
var FORMAT_PATTERNS = {
|
|
1363
|
+
"YYYY-MM-DD": /^\d{4}-\d{2}-\d{2}$/,
|
|
1364
|
+
"MM/DD/YYYY": /^\d{2}\/\d{2}\/\d{4}$/,
|
|
1365
|
+
"DD/MM/YYYY": /^\d{2}\/\d{2}\/\d{4}$/,
|
|
1366
|
+
"YYYY/MM/DD": /^\d{4}\/\d{2}\/\d{2}$/,
|
|
1367
|
+
"MM-DD-YYYY": /^\d{2}-\d{2}-\d{4}$/,
|
|
1368
|
+
"DD-MM-YYYY": /^\d{2}-\d{2}-\d{4}$/,
|
|
1369
|
+
"YYYYMMDD": /^\d{8}$/
|
|
1370
|
+
};
|
|
1371
|
+
function toBeDateString(received, format) {
|
|
1372
|
+
const pattern = FORMAT_PATTERNS[format];
|
|
1373
|
+
if (!pattern) {
|
|
1374
|
+
throw new Error(
|
|
1375
|
+
`Unsupported date format: ${format}. Supported formats: ${Object.keys(FORMAT_PATTERNS).join(", ")}`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
const pass = typeof received === "string" && pattern.test(received);
|
|
1379
|
+
return {
|
|
1380
|
+
message: () => pass ? `expected ${received} not to be a date string (${format})` : `expected ${received} to be a date string (${format})`,
|
|
1381
|
+
pass
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/matchers/asymmetric/toBeEmail.ts
|
|
1386
|
+
var EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
1387
|
+
function toBeEmail(received) {
|
|
1388
|
+
const pass = typeof received === "string" && EMAIL_PATTERN.test(received);
|
|
1389
|
+
return {
|
|
1390
|
+
message: () => pass ? `expected ${received} not to be a valid email` : `expected ${received} to be a valid email`,
|
|
1391
|
+
pass
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/matchers/asymmetric/toBeURL.ts
|
|
1396
|
+
function toBeURL(received, options = {}) {
|
|
1397
|
+
const { protocol } = options;
|
|
1398
|
+
let pass = false;
|
|
1399
|
+
if (typeof received === "string") {
|
|
1400
|
+
try {
|
|
1401
|
+
const url = new URL(received);
|
|
1402
|
+
pass = true;
|
|
1403
|
+
if (protocol) {
|
|
1404
|
+
const urlProtocol = url.protocol.replace(":", "");
|
|
1405
|
+
const allowedProtocols = Array.isArray(protocol) ? protocol : [protocol];
|
|
1406
|
+
if (!allowedProtocols.includes(urlProtocol)) {
|
|
1407
|
+
pass = false;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
} catch {
|
|
1411
|
+
pass = false;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
const protocolMsg = protocol ? ` (protocol: ${Array.isArray(protocol) ? protocol.join("|") : protocol})` : "";
|
|
1415
|
+
return {
|
|
1416
|
+
message: () => pass ? `expected ${received} not to be a valid URL${protocolMsg}` : `expected ${received} to be a valid URL${protocolMsg}`,
|
|
1417
|
+
pass
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/matchers/asymmetric/toBeJSON.ts
|
|
1422
|
+
function toBeJSON(received) {
|
|
1423
|
+
let pass = false;
|
|
1424
|
+
if (typeof received === "string") {
|
|
1425
|
+
try {
|
|
1426
|
+
JSON.parse(received);
|
|
1427
|
+
pass = true;
|
|
1428
|
+
} catch {
|
|
1429
|
+
pass = false;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
message: () => pass ? `expected ${received} not to be valid JSON` : `expected ${received} to be valid JSON`,
|
|
1434
|
+
pass
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// src/matchers/asymmetric/toStartWith.ts
|
|
1439
|
+
function toStartWith(received, expected) {
|
|
1440
|
+
const pass = typeof received === "string" && received.startsWith(expected);
|
|
1441
|
+
return {
|
|
1442
|
+
message: () => pass ? `expected ${received} not to start with ${expected}` : `expected ${received} to start with ${expected}`,
|
|
1443
|
+
pass
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/matchers/asymmetric/toEndWith.ts
|
|
1448
|
+
function toEndWith(received, expected) {
|
|
1449
|
+
const pass = typeof received === "string" && received.endsWith(expected);
|
|
1450
|
+
return {
|
|
1451
|
+
message: () => pass ? `expected ${received} not to end with ${expected}` : `expected ${received} to end with ${expected}`,
|
|
1452
|
+
pass
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// src/matchers/asymmetric/toBeUpperCase.ts
|
|
1457
|
+
function toBeUpperCase(received) {
|
|
1458
|
+
const pass = typeof received === "string" && received === received.toUpperCase() && received !== received.toLowerCase();
|
|
1459
|
+
return {
|
|
1460
|
+
message: () => pass ? `expected ${received} not to be upper case` : `expected ${received} to be upper case`,
|
|
1461
|
+
pass
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/matchers/asymmetric/toBeLowerCase.ts
|
|
1466
|
+
function toBeLowerCase(received) {
|
|
1467
|
+
const pass = typeof received === "string" && received === received.toLowerCase() && received !== received.toUpperCase();
|
|
1468
|
+
return {
|
|
1469
|
+
message: () => pass ? `expected ${received} not to be lower case` : `expected ${received} to be lower case`,
|
|
1470
|
+
pass
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// src/matchers/asymmetric/toBeKebabCase.ts
|
|
1475
|
+
function toBeKebabCase(received) {
|
|
1476
|
+
const pass = typeof received === "string" && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(received);
|
|
1477
|
+
return {
|
|
1478
|
+
message: () => pass ? `expected ${received} not to be kebab-case` : `expected ${received} to be kebab-case`,
|
|
1479
|
+
pass
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/matchers/asymmetric/toBeCamelCase.ts
|
|
1484
|
+
function toBeCamelCase(received) {
|
|
1485
|
+
const pass = typeof received === "string" && /^[a-z][a-zA-Z0-9]*$/.test(received) && !/[_\-]/.test(received);
|
|
1486
|
+
return {
|
|
1487
|
+
message: () => pass ? `expected ${received} not to be camelCase` : `expected ${received} to be camelCase`,
|
|
1488
|
+
pass
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// src/matchers/asymmetric/toBeSnakeCase.ts
|
|
1493
|
+
function toBeSnakeCase(received) {
|
|
1494
|
+
const pass = typeof received === "string" && /^[a-z0-9]+(_[a-z0-9]+)*$/.test(received);
|
|
1495
|
+
return {
|
|
1496
|
+
message: () => pass ? `expected ${received} not to be snake_case` : `expected ${received} to be snake_case`,
|
|
1497
|
+
pass
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// src/matchers/asymmetric/toBePascalCase.ts
|
|
1502
|
+
function toBePascalCase(received) {
|
|
1503
|
+
const pass = typeof received === "string" && /^[A-Z][a-zA-Z0-9]*$/.test(received) && !/[_\-]/.test(received);
|
|
1504
|
+
return {
|
|
1505
|
+
message: () => pass ? `expected ${received} not to be PascalCase` : `expected ${received} to be PascalCase`,
|
|
1506
|
+
pass
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/matchers/asymmetric/index.ts
|
|
1511
|
+
var asymmetricMatchers = {
|
|
1512
|
+
toBeWithinRange,
|
|
1513
|
+
toBeUUID,
|
|
1514
|
+
toBeISODate,
|
|
1515
|
+
toBeDateString,
|
|
1516
|
+
toBeEmail,
|
|
1517
|
+
toBeURL,
|
|
1518
|
+
toBeJSON,
|
|
1519
|
+
toStartWith,
|
|
1520
|
+
toEndWith,
|
|
1521
|
+
toBeUpperCase,
|
|
1522
|
+
toBeLowerCase,
|
|
1523
|
+
toBeKebabCase,
|
|
1524
|
+
toBeCamelCase,
|
|
1525
|
+
toBeSnakeCase,
|
|
1526
|
+
toBePascalCase
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
// src/matchers/general/toBeSorted.ts
|
|
1530
|
+
import { expect as expect17 } from "@playwright/test";
|
|
1531
|
+
function isLocator(value) {
|
|
1532
|
+
return value !== null && typeof value === "object" && "allInnerTexts" in value;
|
|
1533
|
+
}
|
|
1534
|
+
function checkSorted(values, descending, key) {
|
|
1535
|
+
return values.every((item, index) => {
|
|
1536
|
+
if (index === 0) return true;
|
|
1537
|
+
const prev = values[index - 1];
|
|
1538
|
+
let a = key ? typeof key === "function" ? key(prev) : prev[key] : prev;
|
|
1539
|
+
let b = key ? typeof key === "function" ? key(item) : item[key] : item;
|
|
1540
|
+
if (a instanceof Date) a = a.getTime();
|
|
1541
|
+
if (b instanceof Date) b = b.getTime();
|
|
1542
|
+
if (descending) {
|
|
1543
|
+
return a >= b;
|
|
1544
|
+
}
|
|
1545
|
+
return a <= b;
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
async function toBeSorted(received, options) {
|
|
1549
|
+
const {
|
|
1550
|
+
descending = false,
|
|
1551
|
+
key,
|
|
1552
|
+
useTextContent = false,
|
|
1553
|
+
compareAsNumbers = false,
|
|
1554
|
+
timeout = this.timeout,
|
|
1555
|
+
intervals
|
|
1556
|
+
} = options || {};
|
|
1557
|
+
if (Array.isArray(received)) {
|
|
1558
|
+
const pass = checkSorted(received, descending, key);
|
|
1559
|
+
return {
|
|
1560
|
+
message: () => pass ? `expected array not to be sorted ${descending ? "descending" : "ascending"}` : `expected array to be sorted ${descending ? "descending" : "ascending"}, but received: [${received.join(", ")}]`,
|
|
1561
|
+
pass
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
if (isLocator(received)) {
|
|
1565
|
+
const locator = received;
|
|
1566
|
+
let values = [];
|
|
1567
|
+
let pass = false;
|
|
1568
|
+
try {
|
|
1569
|
+
await expect17.poll(
|
|
1570
|
+
async () => {
|
|
1571
|
+
values = useTextContent ? await locator.allTextContents() : await locator.allInnerTexts();
|
|
1572
|
+
const compareValues = compareAsNumbers ? values.map((v) => parseFloat(v.replace(/[^0-9.-]/g, ""))) : values;
|
|
1573
|
+
return checkSorted(compareValues, descending);
|
|
1574
|
+
},
|
|
1575
|
+
{ timeout, intervals }
|
|
1576
|
+
).toBe(true);
|
|
1577
|
+
pass = true;
|
|
1578
|
+
} catch {
|
|
1579
|
+
pass = false;
|
|
1580
|
+
}
|
|
1581
|
+
return {
|
|
1582
|
+
message: () => pass ? `expected locator elements not to be sorted ${descending ? "descending" : "ascending"}` : `expected locator elements to be sorted ${descending ? "descending" : "ascending"}, but received: [${values.join(", ")}]`,
|
|
1583
|
+
pass
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
return {
|
|
1587
|
+
message: () => `expected an array or Locator, but received: ${typeof received}`,
|
|
1588
|
+
pass: false
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/matchers/general/index.ts
|
|
1593
|
+
var generalMatchers = {
|
|
1594
|
+
toBeSorted
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// src/matchers/index.ts
|
|
1598
|
+
var allMatchers = {
|
|
1599
|
+
...locatorMatchers,
|
|
1600
|
+
...pageMatchers,
|
|
1601
|
+
...apiMatchers,
|
|
1602
|
+
...generalMatchers,
|
|
1603
|
+
...asymmetricMatchers
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
// src/index.ts
|
|
1607
|
+
var exPwExpect = playwrightExpect.extend(allMatchers);
|
|
1608
|
+
var expect18 = exPwExpect;
|
|
1609
|
+
var exPw = {
|
|
1610
|
+
...allMatchers
|
|
1611
|
+
};
|
|
1612
|
+
var index_default = exPw;
|
|
1613
|
+
export {
|
|
1614
|
+
apiMatchers,
|
|
1615
|
+
asymmetricMatchers,
|
|
1616
|
+
index_default as default,
|
|
1617
|
+
expect18 as expect,
|
|
1618
|
+
generalMatchers,
|
|
1619
|
+
locatorMatchers,
|
|
1620
|
+
pageMatchers,
|
|
1621
|
+
toBeCamelCase,
|
|
1622
|
+
toBeCheckable,
|
|
1623
|
+
toBeClickable,
|
|
1624
|
+
toBeDateString,
|
|
1625
|
+
toBeEmail,
|
|
1626
|
+
toBeISODate,
|
|
1627
|
+
toBeInvalid,
|
|
1628
|
+
toBeJSON,
|
|
1629
|
+
toBeKebabCase,
|
|
1630
|
+
toBeLowerCase,
|
|
1631
|
+
toBePascalCase,
|
|
1632
|
+
toBeRequired,
|
|
1633
|
+
toBeSnakeCase,
|
|
1634
|
+
toBeSorted,
|
|
1635
|
+
toBeURL,
|
|
1636
|
+
toBeUUID,
|
|
1637
|
+
toBeUpperCase,
|
|
1638
|
+
toBeValid,
|
|
1639
|
+
toBeWithinRange,
|
|
1640
|
+
toEndWith,
|
|
1641
|
+
toHaveClipboardText,
|
|
1642
|
+
toHaveConsoleMessage,
|
|
1643
|
+
toHaveCookie,
|
|
1644
|
+
toHaveCountGreaterThan,
|
|
1645
|
+
toHaveCountGreaterThanOrEqual,
|
|
1646
|
+
toHaveCountLessThan,
|
|
1647
|
+
toHaveCountLessThanOrEqual,
|
|
1648
|
+
toHaveHeader,
|
|
1649
|
+
toHaveHeight,
|
|
1650
|
+
toHaveLoadedImage,
|
|
1651
|
+
toHaveLocalStorage,
|
|
1652
|
+
toHaveNoErrors,
|
|
1653
|
+
toHavePageError,
|
|
1654
|
+
toHaveRequest,
|
|
1655
|
+
toHaveSessionStorage,
|
|
1656
|
+
toHaveSize,
|
|
1657
|
+
toHaveStatus,
|
|
1658
|
+
toHaveWidth,
|
|
1659
|
+
toMatchJSON,
|
|
1660
|
+
toMatchSchema,
|
|
1661
|
+
toRespondWithin,
|
|
1662
|
+
toStartWith
|
|
1663
|
+
};
|