@lullabot/playwright-testing 0.1.0 → 1.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.
Files changed (68) hide show
  1. package/bin/github-a11y-summary +2 -2
  2. package/bin/github-failure-summary +4 -4
  3. package/lib/accessibility-baseline-file.d.ts +6 -6
  4. package/lib/accessibility-baseline-file.js +29 -23
  5. package/lib/accessibility-baseline.js +5 -3
  6. package/lib/accessible-screenshot.d.ts +4 -4
  7. package/lib/accessible-screenshot.js +88 -76
  8. package/lib/focus.js +4 -1
  9. package/lib/frames.js +4 -4
  10. package/lib/github/a11y-summary.js +74 -79
  11. package/lib/github/attachments.d.ts +1 -1
  12. package/lib/github/attachments.js +36 -34
  13. package/lib/github/failure-summary.d.ts +4 -4
  14. package/lib/github/failure-summary.js +139 -107
  15. package/lib/github/index.d.ts +6 -6
  16. package/lib/github/report-paths.js +17 -9
  17. package/lib/hover.js +17 -17
  18. package/lib/images.js +15 -12
  19. package/lib/index.d.ts +13 -13
  20. package/lib/interaction-states.d.ts +2 -2
  21. package/lib/interaction-states.js +7 -7
  22. package/lib/mock/index.d.ts +1 -1
  23. package/lib/mock/youtube.d.ts +1 -1
  24. package/lib/mock/youtube.js +1 -1
  25. package/lib/pseudo-state.d.ts +2 -2
  26. package/lib/pseudo-state.js +15 -7
  27. package/lib/videos.js +21 -10
  28. package/lib/visualdiff.d.ts +4 -4
  29. package/lib/visualdiff.js +35 -27
  30. package/package.json +3 -3
  31. package/src/accessibility-baseline-file.test.ts +190 -154
  32. package/src/accessibility-baseline-file.ts +107 -83
  33. package/src/accessibility-baseline.test.ts +633 -433
  34. package/src/accessibility-baseline.ts +41 -21
  35. package/src/accessible-screenshot.test.ts +551 -423
  36. package/src/accessible-screenshot.ts +334 -245
  37. package/src/focus.test.ts +23 -23
  38. package/src/focus.ts +7 -2
  39. package/src/fonts.test.ts +13 -13
  40. package/src/fonts.ts +1 -1
  41. package/src/frames.test.ts +10 -8
  42. package/src/frames.ts +5 -5
  43. package/src/github/a11y-summary.test.ts +436 -332
  44. package/src/github/a11y-summary.ts +189 -179
  45. package/src/github/attachments.test.ts +228 -167
  46. package/src/github/attachments.ts +144 -121
  47. package/src/github/failure-summary.test.ts +659 -445
  48. package/src/github/failure-summary.ts +376 -274
  49. package/src/github/index.test.ts +14 -14
  50. package/src/github/index.ts +6 -6
  51. package/src/github/report-paths.test.ts +211 -156
  52. package/src/github/report-paths.ts +75 -61
  53. package/src/hover.test.ts +50 -46
  54. package/src/hover.ts +18 -18
  55. package/src/images.test.ts +251 -206
  56. package/src/images.ts +52 -43
  57. package/src/index.ts +13 -13
  58. package/src/interaction-states.test.ts +72 -42
  59. package/src/interaction-states.ts +43 -29
  60. package/src/mock/index.ts +1 -1
  61. package/src/mock/youtube.test.ts +26 -26
  62. package/src/mock/youtube.ts +4 -6
  63. package/src/pseudo-state.test.ts +58 -51
  64. package/src/pseudo-state.ts +32 -27
  65. package/src/videos.test.ts +559 -399
  66. package/src/videos.ts +68 -44
  67. package/src/visualdiff.test.ts +416 -315
  68. package/src/visualdiff.ts +140 -99
@@ -29,16 +29,16 @@ let a11yActionHintShown = false;
29
29
  * @param options Accessibility options to customise the scan.
30
30
  */
31
31
  async function checkAccessibility(page, testInfo, options) {
32
- const { wcagTags = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'], exclude = [], bestPracticeExclude = [], wcagExclude = [], bestPracticeMode = 'soft', rules, baseline, screenshotViolations = true, } = options ?? {};
32
+ const { wcagTags = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"], exclude = [], bestPracticeExclude = [], wcagExclude = [], bestPracticeMode = "soft", rules, baseline, screenshotViolations = true, } = options ?? {};
33
33
  if (process.env.CI && !a11yActionHintShown) {
34
- console.log('Tip: Use @lullabot/playwright-testing/github to surface accessibility violations in pull requests.');
34
+ console.log("Tip: Use @lullabot/playwright-testing/github to surface accessibility violations in pull requests.");
35
35
  a11yActionHintShown = true;
36
36
  }
37
37
  // Add @a11y annotation (deduplicated).
38
- if (!testInfo.annotations.some(a => a.type === '@a11y')) {
39
- testInfo.annotations.push({ type: '@a11y' });
38
+ if (!testInfo.annotations.some((a) => a.type === "@a11y")) {
39
+ testInfo.annotations.push({ type: "@a11y" });
40
40
  }
41
- if (bestPracticeMode !== 'off') {
41
+ if (bestPracticeMode !== "off") {
42
42
  const bpResults = await runBestPracticeScan(page, testInfo, {
43
43
  exclude: [...exclude, ...bestPracticeExclude],
44
44
  rules,
@@ -49,9 +49,9 @@ async function checkAccessibility(page, testInfo, options) {
49
49
  await dispatchAssertion({
50
50
  testInfo,
51
51
  results: bpResults,
52
- scan: 'best-practice',
52
+ scan: "best-practice",
53
53
  expectFn: test_1.expect.soft,
54
- scanLabel: 'Best-practice scan',
54
+ scanLabel: "Best-practice scan",
55
55
  }, baseline);
56
56
  }
57
57
  const wcagScanResults = await runWcagScan(page, testInfo, {
@@ -65,9 +65,9 @@ async function checkAccessibility(page, testInfo, options) {
65
65
  await dispatchAssertion({
66
66
  testInfo,
67
67
  results: wcagScanResults,
68
- scan: 'wcag',
68
+ scan: "wcag",
69
69
  expectFn: test_1.expect,
70
- scanLabel: 'WCAG scan',
70
+ scanLabel: "WCAG scan",
71
71
  }, baseline);
72
72
  }
73
73
  /**
@@ -93,7 +93,7 @@ async function dispatchAssertion(ctx, inCodeBaseline) {
93
93
  return assertSnapshot(ctx);
94
94
  }
95
95
  const update = ctx.testInfo.config?.updateSnapshots;
96
- if (update === 'all' || update === 'changed') {
96
+ if (update === "all" || update === "changed") {
97
97
  return assertSnapshot(ctx);
98
98
  }
99
99
  const callCount = (0, accessibility_baseline_file_js_1.nextAccessibilityScanCount)(ctx.testInfo, ctx.scan);
@@ -104,25 +104,25 @@ async function dispatchAssertion(ctx, inCodeBaseline) {
104
104
  }
105
105
  // Seed and either pass (local) or fail (CI).
106
106
  const normalized = extractNormalizedViolations(ctx.results);
107
- const seedViolations = normalized.map(v => ({
107
+ const seedViolations = normalized.map((v) => ({
108
108
  rule: v.rule,
109
109
  targets: v.targets,
110
- reason: 'TODO',
111
- willBeFixedIn: 'TODO',
110
+ reason: "TODO",
111
+ willBeFixedIn: "TODO",
112
112
  }));
113
113
  const seed = (0, accessibility_baseline_file_js_1.buildSeed)(seedViolations);
114
114
  await (0, accessibility_baseline_file_js_1.writeBaselineFile)(filePath, seed);
115
115
  await ctx.testInfo.attach(`a11y-${ctx.scan}-baseline-seed`, {
116
116
  path: filePath,
117
- contentType: 'application/json',
117
+ contentType: "application/json",
118
118
  });
119
119
  if (process.env.CI) {
120
120
  const message = `${ctx.scanLabel}: a11y baseline file was missing for this test. Seeded to ${filePath} — download the attached file from CI artifacts (or re-run locally) and commit it before merging.`;
121
- ctx.expectFn(null, message).toBe('a11y baseline file present');
121
+ ctx.expectFn(null, message).toBe("a11y baseline file present");
122
122
  return;
123
123
  }
124
124
  ctx.testInfo.annotations.push({
125
- type: 'Accessibility',
125
+ type: "Accessibility",
126
126
  description: seed.violations.length === 0
127
127
  ? `${ctx.scanLabel}: a11y baseline seeded at ${filePath} (no violations).`
128
128
  : `${ctx.scanLabel}: a11y baseline seeded at ${filePath} with ${seed.violations.length} entries — fill in reason/willBeFixedIn before committing.`,
@@ -137,8 +137,7 @@ async function dispatchAssertion(ctx, inCodeBaseline) {
137
137
  * option drives soft- vs hard-failure behaviour.
138
138
  */
139
139
  async function runBestPracticeScan(page, testInfo, opts) {
140
- const builder = new playwright_1.default({ page })
141
- .withTags(['best-practice']);
140
+ const builder = new playwright_1.default({ page }).withTags(["best-practice"]);
142
141
  for (const selector of opts.exclude) {
143
142
  builder.exclude(selector);
144
143
  }
@@ -146,13 +145,13 @@ async function runBestPracticeScan(page, testInfo, opts) {
146
145
  builder.options({ rules: opts.rules });
147
146
  }
148
147
  const results = await builder.analyze();
149
- await testInfo.attach('a11y-best-practice-scan-results', {
148
+ await testInfo.attach("a11y-best-practice-scan-results", {
150
149
  body: JSON.stringify(results, null, 2),
151
- contentType: 'application/json'
150
+ contentType: "application/json",
152
151
  });
153
152
  testInfo.annotations.push({
154
- type: 'Accessibility',
155
- description: `Best-practice scan: ${results.violations.length} violations (${results.passes.length} rules passed)`
153
+ type: "Accessibility",
154
+ description: `Best-practice scan: ${results.violations.length} violations (${results.passes.length} rules passed)`,
156
155
  });
157
156
  return results;
158
157
  }
@@ -160,8 +159,7 @@ async function runBestPracticeScan(page, testInfo, opts) {
160
159
  * Run the WCAG axe scan, attach results, and return them for assertion.
161
160
  */
162
161
  async function runWcagScan(page, testInfo, opts) {
163
- const builder = new playwright_1.default({ page })
164
- .withTags(opts.wcagTags);
162
+ const builder = new playwright_1.default({ page }).withTags(opts.wcagTags);
165
163
  for (const selector of opts.exclude) {
166
164
  builder.exclude(selector);
167
165
  }
@@ -169,9 +167,9 @@ async function runWcagScan(page, testInfo, opts) {
169
167
  builder.options({ rules: opts.rules });
170
168
  }
171
169
  const results = await builder.analyze();
172
- await testInfo.attach('a11y-wcag-scan-results', {
170
+ await testInfo.attach("a11y-wcag-scan-results", {
173
171
  body: JSON.stringify(results, null, 2),
174
- contentType: 'application/json'
172
+ contentType: "application/json",
175
173
  });
176
174
  return results;
177
175
  }
@@ -182,32 +180,34 @@ async function runWcagScan(page, testInfo, opts) {
182
180
  async function screenshotViolatingElements(page, testInfo, results) {
183
181
  // Collect all raw CSS selectors from violation nodes.
184
182
  const selectors = results.violations
185
- .flatMap(v => v.nodes)
186
- .flatMap(n => n.target)
187
- .filter((t) => typeof t === 'string');
183
+ .flatMap((v) => v.nodes)
184
+ .flatMap((n) => n.target)
185
+ .filter((t) => typeof t === "string");
188
186
  if (selectors.length === 0)
189
187
  return;
190
188
  // Inject highlight outlines on all violating elements.
191
189
  await page.evaluate((sels) => {
192
- const style = document.createElement('style');
193
- style.setAttribute('data-a11y-highlight', 'true');
190
+ const style = document.createElement("style");
191
+ style.setAttribute("data-a11y-highlight", "true");
194
192
  // Use a CSS rule for each selector so the outline persists even if
195
193
  // elements are repositioned during the screenshot.
196
- const rules = sels.map(s => `${s} { outline: 3px solid #e53e3e !important; outline-offset: 2px !important; }`).join('\n');
194
+ const rules = sels
195
+ .map((s) => `${s} { outline: 3px solid #e53e3e !important; outline-offset: 2px !important; }`)
196
+ .join("\n");
197
197
  style.textContent = rules;
198
198
  document.head.appendChild(style);
199
199
  }, selectors);
200
200
  try {
201
201
  const screenshot = await page.screenshot({ fullPage: true });
202
- await testInfo.attach('a11y-violation-screenshot', {
202
+ await testInfo.attach("a11y-violation-screenshot", {
203
203
  body: screenshot,
204
- contentType: 'image/png',
204
+ contentType: "image/png",
205
205
  });
206
206
  }
207
207
  finally {
208
208
  // Remove the injected styles even if capture or report attachment fails.
209
209
  await page.evaluate(() => {
210
- document.querySelector('style[data-a11y-highlight]')?.remove();
210
+ document.querySelector("style[data-a11y-highlight]")?.remove();
211
211
  });
212
212
  }
213
213
  }
@@ -225,13 +225,13 @@ function assertBaseline(ctx, baseline) {
225
225
  if (entry.rule !== violation.rule)
226
226
  return false;
227
227
  // Check for at least one overlapping normalized target.
228
- return entry.targets.some(baselineTarget => violation.targets.some(violationTarget => violationTarget === baselineTarget));
228
+ return entry.targets.some((baselineTarget) => violation.targets.some((violationTarget) => violationTarget === baselineTarget));
229
229
  });
230
230
  if (baselineIndex >= 0) {
231
231
  matchedBaselineIndices.add(baselineIndex);
232
232
  const entry = baseline[baselineIndex];
233
233
  testInfo.annotations.push({
234
- type: 'Baselined a11y violation',
234
+ type: "Baselined a11y violation",
235
235
  description: `${entry.rule}: ${entry.reason} — ${entry.willBeFixedIn}`,
236
236
  });
237
237
  }
@@ -243,21 +243,21 @@ function assertBaseline(ctx, baseline) {
243
243
  baseline.forEach((entry, idx) => {
244
244
  if (!matchedBaselineIndices.has(idx)) {
245
245
  testInfo.annotations.push({
246
- type: 'Stale a11y baseline entry',
247
- description: `${entry.rule} on ${entry.targets.join(', ')} — no longer detected`,
246
+ type: "Stale a11y baseline entry",
247
+ description: `${entry.rule} on ${entry.targets.join(", ")} — no longer detected`,
248
248
  });
249
249
  }
250
250
  });
251
251
  // Summary annotation for baseline mode.
252
252
  const baselinedCount = matchedBaselineIndices.size;
253
253
  testInfo.annotations.push({
254
- type: 'Accessibility',
254
+ type: "Accessibility",
255
255
  description: `${scanLabel}: ${unmatchedViolations.length} new violations (${baselinedCount} baselined)`,
256
256
  });
257
257
  // Fail on unmatched violations with detailed output.
258
258
  if (unmatchedViolations.length > 0) {
259
259
  const details = formatViolationDetails(results, unmatchedViolations);
260
- expectFn(null, details).toBe('no accessibility violations');
260
+ expectFn(null, details).toBe("no accessibility violations");
261
261
  }
262
262
  }
263
263
  /**
@@ -267,22 +267,24 @@ async function assertSnapshot(ctx) {
267
267
  const { testInfo, results, scan, expectFn } = ctx;
268
268
  // Match the legacy summary annotation phrasing for WCAG; best-practice's
269
269
  // pre-existing summary annotation is emitted in runBestPracticeScan.
270
- if (scan === 'wcag') {
270
+ if (scan === "wcag") {
271
271
  testInfo.annotations.push({
272
- type: 'Accessibility',
273
- description: `WCAG scan: ${results.violations.length} violations (${results.passes.length} rules passed)`
272
+ type: "Accessibility",
273
+ description: `WCAG scan: ${results.violations.length} violations (${results.passes.length} rules passed)`,
274
274
  });
275
275
  // If there are violations, attach baseline suggestions and push annotation.
276
276
  if (results.violations.length > 0) {
277
277
  const allViolations = extractNormalizedViolations(results);
278
- const suggestions = allViolations.map(v => formatBaselineSuggestion(v)).join('\n');
279
- await testInfo.attach('a11y-baseline-suggestions', {
278
+ const suggestions = allViolations
279
+ .map((v) => formatBaselineSuggestion(v))
280
+ .join("\n");
281
+ await testInfo.attach("a11y-baseline-suggestions", {
280
282
  body: suggestions,
281
- contentType: 'text/plain',
283
+ contentType: "text/plain",
282
284
  });
283
285
  testInfo.annotations.push({
284
- type: 'Accessibility',
285
- description: 'To manage violations explicitly, switch to baseline mode. See a11y-baseline-suggestions attachment.',
286
+ type: "Accessibility",
287
+ description: "To manage violations explicitly, switch to baseline mode. See a11y-baseline-suggestions attachment.",
286
288
  });
287
289
  }
288
290
  }
@@ -299,11 +301,13 @@ async function assertSnapshot(ctx) {
299
301
  */
300
302
  async function takeAccessibleScreenshot(page, testInfo, options, scrollLocator, locator) {
301
303
  const screenshotOptions = options ?? {};
302
- if (screenshotOptions.clipLocator && (screenshotOptions.clip || (locator && locator !== page))) {
303
- throw new Error('clipLocator cannot be combined with clip or a locator screenshot target.');
304
+ if (screenshotOptions.clipLocator &&
305
+ (screenshotOptions.clip || (locator && locator !== page))) {
306
+ throw new Error("clipLocator cannot be combined with clip or a locator screenshot target.");
304
307
  }
305
- if (screenshotOptions.clipLocator && screenshotOptions.clipLocator.page() !== page) {
306
- throw new Error('clipLocator must belong to the screenshot page.');
308
+ if (screenshotOptions.clipLocator &&
309
+ screenshotOptions.clipLocator.page() !== page) {
310
+ throw new Error("clipLocator must belong to the screenshot page.");
307
311
  }
308
312
  // The default is 5 seconds. However, even on a fast machine it can take
309
313
  // longer than 5 seconds for large pages like node forms to stabilize. This
@@ -347,7 +351,7 @@ async function takeAccessibleScreenshot(page, testInfo, options, scrollLocator,
347
351
  // A hover action cannot hit its target through the transparent shield used
348
352
  // to clear incidental hover. Remove it only after all stability waits, then
349
353
  // apply real hover before real focus so focusing cannot disturb the pointer.
350
- const hasHover = interactionStates.some(({ states }) => states.includes('hover'));
354
+ const hasHover = interactionStates.some(({ states }) => states.includes("hover"));
351
355
  if (hasHover) {
352
356
  await removeHoverShield?.();
353
357
  removeHoverShield = undefined;
@@ -369,15 +373,17 @@ async function takeAccessibleScreenshot(page, testInfo, options, scrollLocator,
369
373
  // Scrolling can expose lazy content. Await fonts and two paint frames
370
374
  // before measuring; the existing waits have already loaded page media.
371
375
  await (0, fonts_js_1.waitForFonts)(page);
372
- await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));
376
+ await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));
373
377
  const bounds = await screenshotOptions.clipLocator.boundingBox();
374
378
  if (!bounds)
375
- throw new Error('clipLocator has no visible bounding box.');
379
+ throw new Error("clipLocator has no visible bounding box.");
376
380
  // boundingBox is in main-frame viewport coordinates, including for
377
381
  // locators inside frames. Read scroll offsets from the main page.
378
382
  const geometry = await page.evaluate(() => ({
379
- x: window.scrollX, y: window.scrollY,
380
- width: window.innerWidth, height: window.innerHeight,
383
+ x: window.scrollX,
384
+ y: window.scrollY,
385
+ width: window.innerWidth,
386
+ height: window.innerHeight,
381
387
  }));
382
388
  const clip = {
383
389
  x: Math.round(bounds.x + (screenshotOptions.fullPage ? geometry.x : 0)),
@@ -386,18 +392,24 @@ async function takeAccessibleScreenshot(page, testInfo, options, scrollLocator,
386
392
  height: Math.round(bounds.height),
387
393
  };
388
394
  if (clip.width <= 0 || clip.height <= 0) {
389
- throw new Error('clipLocator must have positive rounded dimensions.');
395
+ throw new Error("clipLocator must have positive rounded dimensions.");
390
396
  }
391
- if (!screenshotOptions.fullPage && (clip.x < 0 || clip.y < 0 || clip.x + clip.width > geometry.width || clip.y + clip.height > geometry.height)) {
392
- throw new Error('clipLocator does not fit in the viewport; use fullPage: true to capture the whole target.');
397
+ if (!screenshotOptions.fullPage &&
398
+ (clip.x < 0 ||
399
+ clip.y < 0 ||
400
+ clip.x + clip.width > geometry.width ||
401
+ clip.y + clip.height > geometry.height)) {
402
+ throw new Error("clipLocator does not fit in the viewport; use fullPage: true to capture the whole target.");
393
403
  }
394
404
  if (clip.x < 0 || clip.y < 0) {
395
- throw new Error('clipLocator must have non-negative document coordinates.');
405
+ throw new Error("clipLocator must have non-negative document coordinates.");
396
406
  }
397
407
  playwrightScreenshotOptions.clip = clip;
398
408
  }
399
409
  // Soft failure here so we can get accessibility violations too.
400
- await test_1.expect.soft(locatorToScreenshot).toHaveScreenshot(playwrightScreenshotOptions);
410
+ await test_1.expect
411
+ .soft(locatorToScreenshot)
412
+ .toHaveScreenshot(playwrightScreenshotOptions);
401
413
  // Settling a video pauses it, clears `autoplay` and rewinds it. That is only
402
414
  // wanted for the duration of the capture: a test that screenshots a page and
403
415
  // then asserts that a video is playing should still pass.
@@ -435,9 +447,9 @@ async function takeAccessibleScreenshot(page, testInfo, options, scrollLocator,
435
447
  function normalizeTarget(target) {
436
448
  const uniqueHtmlID = /(#[^#]*)--\d+/;
437
449
  const ariaLabelledById = /(aria-labelledby="[^"]+)--\d+"/;
438
- if (typeof target === 'string') {
450
+ if (typeof target === "string") {
439
451
  return target
440
- .replace(uniqueHtmlID, '$1--UNIQUE-ID')
452
+ .replace(uniqueHtmlID, "$1--UNIQUE-ID")
441
453
  .replace(ariaLabelledById, '$1--UNIQUE-ID"');
442
454
  }
443
455
  return target;
@@ -447,12 +459,12 @@ function normalizeTarget(target) {
447
459
  * flat, deduplicated CSS selector strings.
448
460
  */
449
461
  function extractNormalizedViolations(results) {
450
- return results.violations.map(violation => {
462
+ return results.violations.map((violation) => {
451
463
  const flatTargets = [];
452
464
  for (const node of violation.nodes) {
453
465
  for (const target of node.target) {
454
466
  const normalized = normalizeTarget(target);
455
- const str = typeof normalized === 'string' ? normalized : normalized.join(' ');
467
+ const str = typeof normalized === "string" ? normalized : normalized.join(" ");
456
468
  if (!flatTargets.includes(str)) {
457
469
  flatTargets.push(str);
458
470
  }
@@ -462,7 +474,7 @@ function extractNormalizedViolations(results) {
462
474
  rule: violation.id,
463
475
  targets: flatTargets,
464
476
  description: violation.description,
465
- impact: violation.impact ?? 'unknown',
477
+ impact: violation.impact ?? "unknown",
466
478
  helpUrl: violation.helpUrl,
467
479
  };
468
480
  });
@@ -471,7 +483,7 @@ function extractNormalizedViolations(results) {
471
483
  * Format a single violation as a copy-pasteable baseline entry.
472
484
  */
473
485
  function formatBaselineSuggestion(violation) {
474
- const targetsStr = violation.targets.map(t => `'${t}'`).join(', ');
486
+ const targetsStr = violation.targets.map((t) => `'${t}'`).join(", ");
475
487
  return `{
476
488
  rule: '${violation.rule}',
477
489
  targets: [${targetsStr}],
@@ -491,12 +503,12 @@ function formatViolationDetails(results, violations) {
491
503
  lines.push(` ${v.description}`);
492
504
  lines.push(` Help: ${v.helpUrl}`);
493
505
  lines.push(` Targets: ${targetsStr}`);
494
- lines.push('');
495
- lines.push(' Add to your baseline to accept this violation:');
496
- lines.push(' ' + formatBaselineSuggestion(v).split('\n').join('\n '));
497
- lines.push('');
506
+ lines.push("");
507
+ lines.push(" Add to your baseline to accept this violation:");
508
+ lines.push(" " + formatBaselineSuggestion(v).split("\n").join("\n "));
509
+ lines.push("");
498
510
  }
499
- return lines.join('\n');
511
+ return lines.join("\n");
500
512
  }
501
513
  /**
502
514
  * Filter violations down to stable elements.
@@ -507,11 +519,11 @@ function formatViolationDetails(results, violations) {
507
519
  * @param accessibilityScanResults
508
520
  */
509
521
  function violationFingerprints(accessibilityScanResults) {
510
- const violationFps = accessibilityScanResults.violations.map(violation => ({
522
+ const violationFps = accessibilityScanResults.violations.map((violation) => ({
511
523
  rule: violation.id,
512
524
  // These are CSS selectors which uniquely identify each element with
513
525
  // a violation of the rule in question.
514
- targets: violation.nodes.map(node => node.target.map((target) => {
526
+ targets: violation.nodes.map((node) => node.target.map((target) => {
515
527
  return normalizeTarget(target);
516
528
  })),
517
529
  }));
package/lib/focus.js CHANGED
@@ -19,7 +19,10 @@ async function blurActiveElement(page) {
19
19
  // `document.activeElement` falls back to `<body>`/`<html>` when nothing is
20
20
  // focused. Blurring that paints no focus ring but can still fire `focusout`
21
21
  // handlers, so skip it and only blur a genuinely focused control.
22
- if (!el || el === document.body || el === document.documentElement || typeof el.blur !== "function") {
22
+ if (!el ||
23
+ el === document.body ||
24
+ el === document.documentElement ||
25
+ typeof el.blur !== "function") {
23
26
  return false;
24
27
  }
25
28
  el.blur();
package/lib/frames.js CHANGED
@@ -7,20 +7,20 @@ exports.waitForFrames = waitForFrames;
7
7
  * @param page
8
8
  */
9
9
  async function waitForFrames(page) {
10
- const locators = await page.locator('iframe');
10
+ const locators = await page.locator("iframe");
11
11
  // Trigger lazy-loading iframes. Since this should be fast, and we don't want to
12
12
  // have to deal with concurrency bugs, we do this in serial.
13
13
  for (const locator of await locators.all()) {
14
14
  // Ensure iframes are connected to the DOM before trying to scroll to them.
15
15
  // https://github.com/microsoft/playwright/issues/23758
16
- if (await locator.evaluate(iframe => iframe.isConnected)) {
16
+ if (await locator.evaluate((iframe) => iframe.isConnected)) {
17
17
  if (await locator.isVisible()) {
18
18
  await locator.scrollIntoViewIfNeeded();
19
19
  await locator.scrollIntoViewIfNeeded();
20
20
  const element = await locator.elementHandle();
21
21
  const frame = await element?.contentFrame();
22
- await frame?.waitForURL(new RegExp('.*/.*', 'i'));
23
- await frame?.waitForLoadState('load');
22
+ await frame?.waitForURL(new RegExp(".*/.*", "i"));
23
+ await frame?.waitForLoadState("load");
24
24
  }
25
25
  }
26
26
  }