@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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { main } = require('../lib/github/a11y-summary.js')
3
+ const { main } = require("../lib/github/a11y-summary.js");
4
4
 
5
- main()
5
+ main();
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { main } = require('../lib/github/failure-summary.js')
3
+ const { main } = require("../lib/github/failure-summary.js");
4
4
 
5
5
  main().catch((error) => {
6
- console.error(error)
7
- process.exit(1)
8
- })
6
+ console.error(error);
7
+ process.exit(1);
8
+ });
@@ -1,6 +1,6 @@
1
- import type { TestInfo } from '@playwright/test';
2
- import type { AccessibilityBaselineEntry } from './accessibility-baseline.js';
3
- export type ScanKind = 'wcag' | 'best-practice';
1
+ import type { TestInfo } from "@playwright/test";
2
+ import type { AccessibilityBaselineEntry } from "./accessibility-baseline.js";
3
+ export type ScanKind = "wcag" | "best-practice";
4
4
  export interface OnDiskBaselineFile {
5
5
  note: string;
6
6
  violations: AccessibilityBaselineEntry[];
@@ -18,7 +18,7 @@ export interface OnDiskBaselineFile {
18
18
  * clear state explicitly — for example, between tests in a unit-test
19
19
  * suite that reuses a mocked TestInfo.
20
20
  */
21
- export declare function nextAccessibilityScanCount(testInfo: Pick<TestInfo, 'testId'> | object, scan: ScanKind): number;
21
+ export declare function nextAccessibilityScanCount(testInfo: Pick<TestInfo, "testId"> | object, scan: ScanKind): number;
22
22
  export declare function resetAccessibilityScanCounts(testInfo: object): void;
23
23
  /**
24
24
  * Build the on-disk baseline file path for a single scan call.
@@ -34,14 +34,14 @@ export declare function resetAccessibilityScanCounts(testInfo: object): void;
34
34
  * explicit-name snapshot path — that logic is private to `toMatchSnapshot()`
35
35
  * for its auto-counter naming — so we build the stem ourselves.
36
36
  */
37
- export declare function baselineFilePath(testInfo: Pick<TestInfo, 'snapshotPath' | 'titlePath' | 'title'>, scan: ScanKind, callCount: number): string;
37
+ export declare function baselineFilePath(testInfo: Pick<TestInfo, "snapshotPath" | "titlePath" | "title">, scan: ScanKind, callCount: number): string;
38
38
  /**
39
39
  * Return true if a Playwright snapshot file already exists on disk for
40
40
  * this test — i.e. the test has been previously committed under snapshot
41
41
  * mode. Used to preserve snapshot-mode behaviour for existing tests while
42
42
  * defaulting new (snapshotless) tests into on-disk baseline mode.
43
43
  */
44
- export declare function snapshotExists(testInfo: Pick<TestInfo, 'snapshotPath' | 'titlePath' | 'title'>): Promise<boolean>;
44
+ export declare function snapshotExists(testInfo: Pick<TestInfo, "snapshotPath" | "titlePath" | "title">): Promise<boolean>;
45
45
  export declare function readBaselineFile(filePath: string): Promise<OnDiskBaselineFile | null>;
46
46
  export declare function writeBaselineFile(filePath: string, data: OnDiskBaselineFile): Promise<void>;
47
47
  export declare function buildSeed(violations: AccessibilityBaselineEntry[]): OnDiskBaselineFile;
@@ -51,8 +51,8 @@ function resetAccessibilityScanCounts(testInfo) {
51
51
  */
52
52
  function slugifyTitle(testInfo) {
53
53
  const segments = testInfo.titlePath?.slice(1) ?? [];
54
- const raw = segments.length > 0 ? segments.join(' ') : testInfo.title;
55
- let out = '';
54
+ const raw = segments.length > 0 ? segments.join(" ") : testInfo.title;
55
+ let out = "";
56
56
  let lastWasHyphen = true; // suppresses leading hyphen
57
57
  for (let i = 0; i < raw.length; i++) {
58
58
  const code = raw.charCodeAt(i);
@@ -68,12 +68,12 @@ function slugifyTitle(testInfo) {
68
68
  lastWasHyphen = false;
69
69
  }
70
70
  else if (!lastWasHyphen) {
71
- out += '-';
71
+ out += "-";
72
72
  lastWasHyphen = true;
73
73
  }
74
74
  }
75
75
  // Trim trailing hyphen.
76
- if (out.endsWith('-'))
76
+ if (out.endsWith("-"))
77
77
  out = out.slice(0, -1);
78
78
  return out;
79
79
  }
@@ -88,8 +88,8 @@ function slugifyTitle(testInfo) {
88
88
  */
89
89
  function playwrightSnapshotStem(testInfo) {
90
90
  const segments = testInfo.titlePath?.slice(1) ?? [];
91
- const raw = segments.length > 0 ? segments.join(' ') : testInfo.title;
92
- let out = '';
91
+ const raw = segments.length > 0 ? segments.join(" ") : testInfo.title;
92
+ let out = "";
93
93
  let lastWasReplaced = false;
94
94
  for (let i = 0; i < raw.length; i++) {
95
95
  const code = raw.charCodeAt(i);
@@ -100,7 +100,7 @@ function playwrightSnapshotStem(testInfo) {
100
100
  (code >= 0x7b && code <= 0x7f);
101
101
  if (replaced) {
102
102
  if (!lastWasReplaced)
103
- out += '-';
103
+ out += "-";
104
104
  lastWasReplaced = true;
105
105
  }
106
106
  else {
@@ -126,7 +126,7 @@ function playwrightSnapshotStem(testInfo) {
126
126
  */
127
127
  function baselineFilePath(testInfo, scan, callCount) {
128
128
  const slug = slugifyTitle(testInfo);
129
- const suffix = scan === 'wcag' ? 'a11y-baseline' : 'a11y-baseline-best-practice';
129
+ const suffix = scan === "wcag" ? "a11y-baseline" : "a11y-baseline-best-practice";
130
130
  return testInfo.snapshotPath(`${slug}-${callCount}.${suffix}.json`);
131
131
  }
132
132
  /**
@@ -136,13 +136,13 @@ function baselineFilePath(testInfo, scan, callCount) {
136
136
  * defaulting new (snapshotless) tests into on-disk baseline mode.
137
137
  */
138
138
  async function snapshotExists(testInfo) {
139
- const dir = path_1.default.dirname(testInfo.snapshotPath('a11y-baseline-probe'));
139
+ const dir = path_1.default.dirname(testInfo.snapshotPath("a11y-baseline-probe"));
140
140
  let entries;
141
141
  try {
142
142
  entries = await fs_1.promises.readdir(dir);
143
143
  }
144
144
  catch (err) {
145
- if (err?.code === 'ENOENT')
145
+ if (err?.code === "ENOENT")
146
146
  return false;
147
147
  throw err;
148
148
  }
@@ -150,56 +150,62 @@ async function snapshotExists(testInfo) {
150
150
  // Require the counter so a title that merely extends this one (e.g.
151
151
  // "Video" vs "Video Promo") is not mistaken for this test's snapshot.
152
152
  const prefix = `${playwrightSnapshotStem(testInfo)}-`;
153
- return entries.some(name => {
154
- if (!name.startsWith(prefix) || !name.endsWith('.txt'))
153
+ return entries.some((name) => {
154
+ if (!name.startsWith(prefix) || !name.endsWith(".txt"))
155
155
  return false;
156
156
  let i = prefix.length;
157
- while (i < name.length && name.charCodeAt(i) >= 48 && name.charCodeAt(i) <= 57)
157
+ while (i < name.length &&
158
+ name.charCodeAt(i) >= 48 &&
159
+ name.charCodeAt(i) <= 57)
158
160
  i++;
159
- return i > prefix.length && (name[i] === '-' || name[i] === '.');
161
+ return i > prefix.length && (name[i] === "-" || name[i] === ".");
160
162
  });
161
163
  }
162
164
  async function readBaselineFile(filePath) {
163
165
  let raw;
164
166
  try {
165
- raw = await fs_1.promises.readFile(filePath, 'utf8');
167
+ raw = await fs_1.promises.readFile(filePath, "utf8");
166
168
  }
167
169
  catch (err) {
168
- if (err?.code === 'ENOENT')
170
+ if (err?.code === "ENOENT")
169
171
  return null;
170
172
  throw err;
171
173
  }
172
174
  try {
173
175
  const parsed = JSON.parse(raw);
174
- if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.violations)) {
176
+ if (!parsed ||
177
+ typeof parsed !== "object" ||
178
+ !Array.isArray(parsed.violations)) {
175
179
  throw new Error('expected an object with a "violations" array');
176
180
  }
177
181
  return {
178
- note: typeof parsed.note === 'string' ? parsed.note : '',
182
+ note: typeof parsed.note === "string" ? parsed.note : "",
179
183
  violations: parsed.violations,
180
184
  };
181
185
  }
182
186
  catch (err) {
183
- throw new Error(`Malformed baseline file at ${filePath}: ${err.message}`);
187
+ throw new Error(`Malformed baseline file at ${filePath}: ${err.message}`, { cause: err });
184
188
  }
185
189
  }
186
190
  async function writeBaselineFile(filePath, data) {
187
191
  await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
188
192
  try {
189
- await fs_1.promises.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', { flag: 'wx' });
193
+ await fs_1.promises.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", {
194
+ flag: "wx",
195
+ });
190
196
  }
191
197
  catch (err) {
192
- if (err?.code === 'EEXIST')
198
+ if (err?.code === "EEXIST")
193
199
  return;
194
200
  throw err;
195
201
  }
196
202
  }
197
203
  function buildSeed(violations) {
198
204
  if (violations.length === 0) {
199
- return { note: 'No accessibility violations found', violations: [] };
205
+ return { note: "No accessibility violations found", violations: [] };
200
206
  }
201
207
  return {
202
- note: 'TODO: fill in reason and willBeFixedIn for each entry before committing.',
208
+ note: "TODO: fill in reason and willBeFixedIn for each entry before committing.",
203
209
  violations,
204
210
  };
205
211
  }
@@ -18,13 +18,15 @@ function validateAccessibilityBaseline(entries) {
18
18
  if (!entry.rule.trim()) {
19
19
  throw new Error(`Accessibility baseline entry ${index + 1} requires a rule.`);
20
20
  }
21
- if (entry.targets.length === 0 || entry.targets.some(target => !target.trim())) {
21
+ if (entry.targets.length === 0 ||
22
+ entry.targets.some((target) => !target.trim())) {
22
23
  throw new Error(`Accessibility baseline entry ${index + 1} requires at least one non-empty target.`);
23
24
  }
24
- if (!entry.reason.trim() || entry.reason.trim().toUpperCase() === 'TODO') {
25
+ if (!entry.reason.trim() || entry.reason.trim().toUpperCase() === "TODO") {
25
26
  throw new Error(`Accessibility baseline entry ${index + 1} requires a reason.`);
26
27
  }
27
- if (!entry.willBeFixedIn.trim() || entry.willBeFixedIn.trim().toUpperCase() === 'TODO') {
28
+ if (!entry.willBeFixedIn.trim() ||
29
+ entry.willBeFixedIn.trim().toUpperCase() === "TODO") {
28
30
  throw new Error(`Accessibility baseline entry ${index + 1} requires a willBeFixedIn tracking reference.`);
29
31
  }
30
32
  for (const target of new Set(entry.targets)) {
@@ -1,9 +1,9 @@
1
1
  import { type Locator, type Page, type TestInfo } from "@playwright/test";
2
2
  import { type WaitForImagesOptions } from "./images.js";
3
3
  import { type WaitForVideosOptions } from "./videos.js";
4
- import { type ScreenshotInteractionState } from './interaction-states.js';
5
- import { type AccessibilityBaseline } from './accessibility-baseline.js';
6
- export type { InteractionState, ScreenshotInteractionState } from './interaction-states.js';
4
+ import { type ScreenshotInteractionState } from "./interaction-states.js";
5
+ import { type AccessibilityBaseline } from "./accessibility-baseline.js";
6
+ export type { InteractionState, ScreenshotInteractionState, } from "./interaction-states.js";
7
7
  export interface ScreenshotStabilizationOptions {
8
8
  images?: WaitForImagesOptions;
9
9
  videos?: WaitForVideosOptions;
@@ -136,7 +136,7 @@ export interface AccessibilityOptions {
136
136
  * - 'hard': uses expect() — test fails immediately on violations
137
137
  * - 'off': skips best-practice scan entirely
138
138
  */
139
- bestPracticeMode?: 'soft' | 'hard' | 'off';
139
+ bestPracticeMode?: "soft" | "hard" | "off";
140
140
  /** Additional axe rules to enable/disable. */
141
141
  rules?: Record<string, {
142
142
  enabled: boolean;