@letsrunit/playwright 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +18 -3
- package/dist/index.js +559 -381
- package/dist/index.js.map +1 -1
- package/package.json +3 -6
- package/src/browser.ts +2 -1
- package/src/field/aria-select.ts +3 -3
- package/src/field/calendar.ts +9 -8
- package/src/field/date-group.ts +12 -12
- package/src/field/date-text-input.ts +14 -10
- package/src/field/index.ts +3 -1
- package/src/field/native-date.ts +1 -1
- package/src/field/radio-group.ts +2 -2
- package/src/field/toggle.ts +2 -2
- package/src/fuzzy-locator.ts +24 -29
- package/src/index.ts +1 -0
- package/src/scrub-html.ts +42 -5
- package/src/snapshot.ts +16 -2
- package/src/suppress-interferences.ts +11 -3
- package/src/wait.ts +11 -2
|
@@ -79,7 +79,7 @@ async function tryClick(page: Page, selectors: string[], _label: string) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
async function closeNativeJsAlerts(page: Page) {
|
|
82
|
-
page.on('dialog', (d) => d.accept().catch(() => {}));
|
|
82
|
+
page.on('dialog', /* v8 ignore next */ (d) => d.accept().catch(() => {}));
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// 1) Known cookie CMPs (quick wins)
|
|
@@ -155,6 +155,7 @@ async function sweepNewsletter(page: Page, regex: TrRegExps): Promise<boolean> {
|
|
|
155
155
|
} catch {}
|
|
156
156
|
}
|
|
157
157
|
// click outside the modal (overlay)
|
|
158
|
+
/* v8 ignore start */
|
|
158
159
|
const overlay = page.locator('div[role="presentation"], .modal-backdrop, .overlay, .ReactModal__Overlay');
|
|
159
160
|
if (await overlay.count().catch(() => 0)) {
|
|
160
161
|
try {
|
|
@@ -162,6 +163,7 @@ async function sweepNewsletter(page: Page, regex: TrRegExps): Promise<boolean> {
|
|
|
162
163
|
return true;
|
|
163
164
|
} catch {}
|
|
164
165
|
}
|
|
166
|
+
/* v8 ignore stop */
|
|
165
167
|
}
|
|
166
168
|
return false;
|
|
167
169
|
}
|
|
@@ -173,6 +175,7 @@ async function sweepOverlays(page: Page, regex: TrRegExps): Promise<boolean> {
|
|
|
173
175
|
|
|
174
176
|
return await page
|
|
175
177
|
.evaluate(
|
|
178
|
+
/* v8 ignore start */
|
|
176
179
|
([source, flags]) => {
|
|
177
180
|
const acceptRx = new RegExp(source, flags);
|
|
178
181
|
const isBig = (el: Element) => {
|
|
@@ -192,7 +195,7 @@ async function sweepOverlays(page: Page, regex: TrRegExps): Promise<boolean> {
|
|
|
192
195
|
for (const el of candidates) {
|
|
193
196
|
// try a close button inside
|
|
194
197
|
const btn = el.querySelector<HTMLElement>(
|
|
195
|
-
'[aria-label
|
|
198
|
+
'[aria-label*=”close” i], button[aria-label*=”close” i], button:has(svg), .close, [data-close], .btn-close',
|
|
196
199
|
);
|
|
197
200
|
|
|
198
201
|
if (btn) {
|
|
@@ -221,9 +224,10 @@ async function sweepOverlays(page: Page, regex: TrRegExps): Promise<boolean> {
|
|
|
221
224
|
}
|
|
222
225
|
return false;
|
|
223
226
|
},
|
|
227
|
+
/* v8 ignore stop */
|
|
224
228
|
[acceptRxSource, acceptRxFlags],
|
|
225
229
|
)
|
|
226
|
-
.catch(() => false);
|
|
230
|
+
.catch(/* v8 ignore next */ () => false);
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
export async function suppressInterferences(page: Page, opts: Options = {}) {
|
|
@@ -264,7 +268,9 @@ export async function suppressInterferences(page: Page, opts: Options = {}) {
|
|
|
264
268
|
|
|
265
269
|
// Optional: bail if we are hitting a loop of re-spawning popups
|
|
266
270
|
if (actions >= maxActions) {
|
|
271
|
+
/* v8 ignore start */
|
|
267
272
|
if (opts.verbose) console.log(`[suppressInterferences] Max actions ${maxActions} reached, stopping.`);
|
|
273
|
+
/* v8 ignore stop */
|
|
268
274
|
break;
|
|
269
275
|
}
|
|
270
276
|
|
|
@@ -278,7 +284,9 @@ export async function suppressInterferences(page: Page, opts: Options = {}) {
|
|
|
278
284
|
const ranFor = Date.now() - startedAt;
|
|
279
285
|
|
|
280
286
|
if (quietFor >= quietPeriodMs && ranFor >= minSweepMs) {
|
|
287
|
+
/* v8 ignore start */
|
|
281
288
|
if (opts.verbose) console.log(`[suppressInterferences] Quiet for ${quietFor}ms, stopping early.`);
|
|
289
|
+
/* v8 ignore stop */
|
|
282
290
|
break;
|
|
283
291
|
}
|
|
284
292
|
|
package/src/wait.ts
CHANGED
|
@@ -15,6 +15,7 @@ export async function waitForMeta(page: Page, timeout = 2500) {
|
|
|
15
15
|
|
|
16
16
|
await page
|
|
17
17
|
.waitForFunction(
|
|
18
|
+
/* v8 ignore start */
|
|
18
19
|
() => {
|
|
19
20
|
const head = document.head;
|
|
20
21
|
if (!head) return false;
|
|
@@ -26,6 +27,7 @@ export async function waitForMeta(page: Page, timeout = 2500) {
|
|
|
26
27
|
head.querySelector('script[type="application/ld+json"]'),
|
|
27
28
|
);
|
|
28
29
|
},
|
|
30
|
+
/* v8 ignore stop */
|
|
29
31
|
{ timeout },
|
|
30
32
|
)
|
|
31
33
|
.catch(() => {});
|
|
@@ -37,6 +39,7 @@ export async function waitForDomIdle(
|
|
|
37
39
|
{ quiet = 500, timeout = 10_000 }: { quiet?: number; timeout?: number } = {},
|
|
38
40
|
) {
|
|
39
41
|
await page.waitForFunction(
|
|
42
|
+
/* v8 ignore start */
|
|
40
43
|
(q) =>
|
|
41
44
|
new Promise<boolean>((resolve) => {
|
|
42
45
|
let last = performance.now();
|
|
@@ -59,6 +62,7 @@ export async function waitForDomIdle(
|
|
|
59
62
|
};
|
|
60
63
|
tick();
|
|
61
64
|
}),
|
|
65
|
+
/* v8 ignore stop */
|
|
62
66
|
quiet,
|
|
63
67
|
{ timeout },
|
|
64
68
|
);
|
|
@@ -68,19 +72,22 @@ export async function waitForDomIdle(
|
|
|
68
72
|
// This helps with libraries that slide months using WAAPI (common in React UI libs).
|
|
69
73
|
export async function waitForAnimationsToFinish(root: Locator) {
|
|
70
74
|
await root.page().waitForFunction(
|
|
75
|
+
/* v8 ignore start */
|
|
71
76
|
(el) => {
|
|
72
77
|
const animations = (el as HTMLElement).getAnimations?.({ subtree: true }) ?? [];
|
|
73
78
|
return animations.every((a) => a.playState !== 'running');
|
|
74
79
|
},
|
|
80
|
+
/* v8 ignore stop */
|
|
75
81
|
await root.elementHandle(),
|
|
76
82
|
);
|
|
77
83
|
|
|
84
|
+
/* v8 ignore next */
|
|
78
85
|
await root.evaluate(() => new Promise<void>((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))));
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
export async function waitForUrlChange(page: Page, prevUrl: string, timeout: number) {
|
|
82
89
|
try {
|
|
83
|
-
await page.waitForFunction((u) => location.href !== u, prevUrl, { timeout });
|
|
90
|
+
await page.waitForFunction(/* v8 ignore next */ (u) => location.href !== u, prevUrl, { timeout });
|
|
84
91
|
return true;
|
|
85
92
|
} catch {
|
|
86
93
|
return false;
|
|
@@ -95,6 +102,7 @@ export async function waitUntilEnabled(page: Page, target: Locator, timeout: num
|
|
|
95
102
|
|
|
96
103
|
await page
|
|
97
104
|
.waitForFunction(
|
|
105
|
+
/* v8 ignore start */
|
|
98
106
|
(el) => {
|
|
99
107
|
if (!el || !(el as Element).isConnected) return true; // detached → treat as settled
|
|
100
108
|
const aria = (el as HTMLElement).getAttribute('aria-disabled');
|
|
@@ -104,6 +112,7 @@ export async function waitUntilEnabled(page: Page, target: Locator, timeout: num
|
|
|
104
112
|
(el as HTMLElement).getAttribute('disabled') !== null;
|
|
105
113
|
return !disabled;
|
|
106
114
|
},
|
|
115
|
+
/* v8 ignore stop */
|
|
107
116
|
handle,
|
|
108
117
|
{ timeout },
|
|
109
118
|
)
|
|
@@ -158,7 +167,7 @@ async function elementKind(target: Locator): Promise<'link' | 'button' | 'other'
|
|
|
158
167
|
if (role === 'link') return 'link';
|
|
159
168
|
if (role === 'button') return 'button';
|
|
160
169
|
|
|
161
|
-
const tag = await target.evaluate((el) => el.tagName.toLowerCase(), null, { timeout: PROBE }).catch(() => '');
|
|
170
|
+
const tag = await target.evaluate(/* v8 ignore next */ (el) => el.tagName.toLowerCase(), null, { timeout: PROBE }).catch(() => '');
|
|
162
171
|
if (tag === 'a') return 'link';
|
|
163
172
|
|
|
164
173
|
if (tag === 'button') return 'button';
|