@letsrunit/playwright 0.18.2 → 0.18.3

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.
@@ -111,10 +111,15 @@ async function getSliderElement(el: Locator, options?: SetOptions): Promise<Loca
111
111
  const role = await el.getAttribute('role', options).catch(() => null);
112
112
  if (role === 'slider') return el;
113
113
 
114
- const slider = el.getByRole('slider');
115
- if ((await slider.count()) > 0) {
116
- return slider.first();
117
- }
114
+ const sliderByRole = el.getByRole('slider');
115
+ if ((await sliderByRole.count()) > 0) return sliderByRole.first();
116
+
117
+ const sliderByAria = el.locator('[aria-valuenow][aria-valuemin][aria-valuemax]');
118
+ if ((await sliderByAria.count()) > 0) return sliderByAria.first();
119
+
120
+ const sliderByValueNow = el.locator('[aria-valuenow]');
121
+ if ((await sliderByValueNow.count()) > 0) return sliderByValueNow.first();
122
+
118
123
  return null;
119
124
  }
120
125
 
@@ -1,15 +1,38 @@
1
1
  import type { Loc, SetOptions, Value } from './types';
2
2
 
3
+ async function getToggleTarget(el: Loc['el'], options?: SetOptions): Promise<Loc['el'] | null> {
4
+ const role = await el.getAttribute('role', options).catch(
5
+ /* v8 ignore next — attribute might be missing or element might have detached during the check */
6
+ () => null,
7
+ );
8
+ if (role === 'checkbox' || role === 'switch') return el;
9
+
10
+ const byRole = el.locator('[role="switch"], [role="checkbox"]').first();
11
+ if ((await byRole.count()) > 0) return byRole;
12
+
13
+ const byState = el.locator('[aria-checked]').first();
14
+ if ((await byState.count()) > 0) return byState;
15
+
16
+ return null;
17
+ }
18
+
3
19
  export async function setToggle({ el }: Loc, value: Value, options?: SetOptions): Promise<boolean> {
4
20
  if (typeof value !== 'boolean' && value !== null) return false;
5
21
 
6
- const role = await el.getAttribute('role', options).catch(/* v8 ignore next */ () => null);
7
- if (role !== 'checkbox' && role !== 'switch') return false;
22
+ const target = await getToggleTarget(el, options);
23
+ if (!target) return false;
8
24
 
9
- const ariaChecked = await el.getAttribute('aria-checked', options).catch(/* v8 ignore next */ () => null);
25
+ const ariaChecked = await target.getAttribute('aria-checked', options).catch(
26
+ /* v8 ignore next — attribute might be missing or element might have detached during the check */
27
+ () => null,
28
+ );
10
29
  const isChecked = ariaChecked === 'true';
11
30
 
12
- if (Boolean(value) !== isChecked) await el.click(options);
31
+ if (Boolean(value) !== isChecked) await target.click(options);
13
32
 
14
- return true;
33
+ const nextAriaChecked = await target.getAttribute('aria-checked', options).catch(
34
+ /* v8 ignore next — attribute might be missing or element might have detached during the check */
35
+ () => null,
36
+ );
37
+ return nextAriaChecked === String(Boolean(value));
15
38
  }
package/src/scrub-html.ts CHANGED
@@ -182,7 +182,7 @@ export async function realScrubHtml(
182
182
  const doc = dom.window.document;
183
183
 
184
184
  if (o.pickMain) pickMain(doc);
185
- dropInfraAndSvg(doc, !!o.dropSvg);
185
+ dropInfraAndSvg(doc, Boolean(o.dropSvg));
186
186
  if (o.dropHidden) dropHiddenTrees(doc);
187
187
  if (o.stripAttributes) stripAttributesAndSanitize(doc, o.stripAttributes);
188
188
  if (o.dropComments) dropHtmlComments(doc);
@@ -199,7 +199,7 @@ export async function realScrubHtml(
199
199
  function hasHiddenAncestor(el: Element): boolean {
200
200
  let p: Element | null = el.parentElement;
201
201
  while (p) {
202
- /* v8 ignore next */
202
+ /* v8 ignore next — JSDOM might not have these attributes/values, but we want to check them if they exist */
203
203
  if (p.hasAttribute('hidden') || p.hasAttribute('inert') || p.getAttribute('aria-hidden') === 'true') return true;
204
204
 
205
205
  const style = p.getAttribute('style') || '';
@@ -79,7 +79,11 @@ async function tryClick(page: Page, selectors: string[], _label: string) {
79
79
  }
80
80
 
81
81
  async function closeNativeJsAlerts(page: Page) {
82
- page.on('dialog', /* v8 ignore next */ (d) => d.accept().catch(() => {}));
82
+ page.on(
83
+ 'dialog',
84
+ /* v8 ignore next — callback runs in browser context, not Node */
85
+ (d) => d.accept().catch(() => {}),
86
+ );
83
87
  }
84
88
 
85
89
  // 1) Known cookie CMPs (quick wins)
@@ -175,7 +179,7 @@ async function sweepOverlays(page: Page, regex: TrRegExps): Promise<boolean> {
175
179
 
176
180
  return await page
177
181
  .evaluate(
178
- /* v8 ignore start */
182
+ /* v8 ignore start — callback runs in browser context, not Node */
179
183
  ([source, flags]) => {
180
184
  const acceptRx = new RegExp(source, flags);
181
185
  const isBig = (el: Element) => {
@@ -227,7 +231,7 @@ async function sweepOverlays(page: Page, regex: TrRegExps): Promise<boolean> {
227
231
  /* v8 ignore stop */
228
232
  [acceptRxSource, acceptRxFlags],
229
233
  )
230
- .catch(/* v8 ignore next */ () => false);
234
+ .catch(() => false);
231
235
  }
232
236
 
233
237
  export async function suppressInterferences(page: Page, opts: Options = {}) {
@@ -268,9 +272,7 @@ export async function suppressInterferences(page: Page, opts: Options = {}) {
268
272
 
269
273
  // Optional: bail if we are hitting a loop of re-spawning popups
270
274
  if (actions >= maxActions) {
271
- /* v8 ignore start */
272
275
  if (opts.verbose) console.log(`[suppressInterferences] Max actions ${maxActions} reached, stopping.`);
273
- /* v8 ignore stop */
274
276
  break;
275
277
  }
276
278
 
package/src/wait.ts CHANGED
@@ -15,7 +15,7 @@ export async function waitForMeta(page: Page, timeout = 2500) {
15
15
 
16
16
  await page
17
17
  .waitForFunction(
18
- /* v8 ignore start */
18
+ /* v8 ignore start — callback runs in browser context, not Node */
19
19
  () => {
20
20
  const head = document.head;
21
21
  if (!head) return false;
@@ -39,7 +39,7 @@ export async function waitForDomIdle(
39
39
  { quiet = 500, timeout = 10_000 }: { quiet?: number; timeout?: number } = {},
40
40
  ) {
41
41
  await page.waitForFunction(
42
- /* v8 ignore start */
42
+ /* v8 ignore start — callback runs in browser context, not Node */
43
43
  (q) =>
44
44
  new Promise<boolean>((resolve) => {
45
45
  let last = performance.now();
@@ -72,7 +72,7 @@ export async function waitForDomIdle(
72
72
  // This helps with libraries that slide months using WAAPI (common in React UI libs).
73
73
  export async function waitForAnimationsToFinish(root: Locator) {
74
74
  await root.page().waitForFunction(
75
- /* v8 ignore start */
75
+ /* v8 ignore start — callback runs in browser context, not Node */
76
76
  (el) => {
77
77
  const animations = (el as HTMLElement).getAnimations?.({ subtree: true }) ?? [];
78
78
  return animations.every((a) => a.playState !== 'running');
@@ -81,13 +81,20 @@ export async function waitForAnimationsToFinish(root: Locator) {
81
81
  await root.elementHandle(),
82
82
  );
83
83
 
84
- /* v8 ignore next */
85
- await root.evaluate(() => new Promise<void>((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))));
84
+ await root.evaluate(
85
+ /* v8 ignore next callback runs in browser context, not Node */
86
+ () => new Promise<void>((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))),
87
+ );
86
88
  }
87
89
 
88
90
  export async function waitForUrlChange(page: Page, prevUrl: string, timeout: number) {
89
91
  try {
90
- await page.waitForFunction(/* v8 ignore next */ (u) => location.href !== u, prevUrl, { timeout });
92
+ await page.waitForFunction(
93
+ /* v8 ignore next — callback runs in browser context, not Node */
94
+ (u) => location.href !== u,
95
+ prevUrl,
96
+ { timeout },
97
+ );
91
98
  return true;
92
99
  } catch {
93
100
  return false;
@@ -102,7 +109,7 @@ export async function waitUntilEnabled(page: Page, target: Locator, timeout: num
102
109
 
103
110
  await page
104
111
  .waitForFunction(
105
- /* v8 ignore start */
112
+ /* v8 ignore start — callback runs in browser context, not Node */
106
113
  (el) => {
107
114
  if (!el || !(el as Element).isConnected) return true; // detached → treat as settled
108
115
  const aria = (el as HTMLElement).getAttribute('aria-disabled');
@@ -167,7 +174,14 @@ async function elementKind(target: Locator): Promise<'link' | 'button' | 'other'
167
174
  if (role === 'link') return 'link';
168
175
  if (role === 'button') return 'button';
169
176
 
170
- const tag = await target.evaluate(/* v8 ignore next */ (el) => el.tagName.toLowerCase(), null, { timeout: PROBE }).catch(() => '');
177
+ const tag = await target
178
+ .evaluate(
179
+ /* v8 ignore next — callback runs in browser context, not Node */
180
+ (el) => el.tagName.toLowerCase(),
181
+ null,
182
+ { timeout: PROBE },
183
+ )
184
+ .catch(() => '');
171
185
  if (tag === 'a') return 'link';
172
186
 
173
187
  if (tag === 'button') return 'button';