@lullabot/playwright-testing 1.0.0 → 1.1.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.
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 +110 -88
  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 +140 -108
  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 +869 -433
  34. package/src/accessibility-baseline.ts +41 -21
  35. package/src/accessible-screenshot.test.ts +551 -423
  36. package/src/accessible-screenshot.ts +358 -251
  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 +660 -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,7 +1,7 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { promises as fs } from 'fs'
3
- import os from 'os'
4
- import path from 'path'
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { promises as fs } from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
5
  import {
6
6
  baselineFilePath,
7
7
  buildSeed,
@@ -10,172 +10,208 @@ import {
10
10
  resetAccessibilityScanCounts,
11
11
  snapshotExists,
12
12
  writeBaselineFile,
13
- } from './accessibility-baseline-file'
14
-
15
- function makeTestInfo(opts: { dir: string; title?: string; titlePath?: string[] }) {
16
- const title = opts.title ?? 'standalone accessibility check works'
17
- const titlePath = opts.titlePath ?? ['file.spec.ts', title]
13
+ } from "./accessibility-baseline-file";
14
+
15
+ function makeTestInfo(opts: {
16
+ dir: string;
17
+ title?: string;
18
+ titlePath?: string[];
19
+ }) {
20
+ const title = opts.title ?? "standalone accessibility check works";
21
+ const titlePath = opts.titlePath ?? ["file.spec.ts", title];
18
22
  return {
19
23
  testId: `${title}-${Math.random()}`,
20
24
  title,
21
25
  titlePath,
22
26
  snapshotPath: (...segs: string[]) => path.join(opts.dir, ...segs),
23
- }
27
+ };
24
28
  }
25
29
 
26
- describe('accessibility-baseline-file', () => {
27
- let tmpDir: string
30
+ describe("accessibility-baseline-file", () => {
31
+ let tmpDir: string;
28
32
 
29
33
  beforeEach(async () => {
30
- tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'a11y-baseline-'))
31
- })
34
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "a11y-baseline-"));
35
+ });
32
36
 
33
37
  afterEach(async () => {
34
- await fs.rm(tmpDir, { recursive: true, force: true })
35
- })
36
-
37
- describe('baselineFilePath', () => {
38
- it('builds a deterministic projectless path with counter and scan-kind suffix', () => {
39
- const ti = makeTestInfo({ dir: tmpDir })
40
- const wcag = baselineFilePath(ti, 'wcag', 1)
41
- const bp = baselineFilePath(ti, 'best-practice', 1)
42
- expect(wcag).toBe(path.join(tmpDir, 'standalone-accessibility-check-works-1.a11y-baseline.json'))
43
- expect(bp).toBe(path.join(tmpDir, 'standalone-accessibility-check-works-1.a11y-baseline-best-practice.json'))
44
- })
45
-
46
- it('uses an incremented counter for multi-call tests', () => {
47
- const ti = makeTestInfo({ dir: tmpDir })
48
- const path2 = baselineFilePath(ti, 'wcag', 2)
49
- expect(path2).toContain('-2.a11y-baseline.json')
50
- })
51
- })
52
-
53
- describe('nextAccessibilityScanCount', () => {
54
- it('returns sequential numbers per (testInfo, scan)', () => {
55
- const ti = {} as any
38
+ await fs.rm(tmpDir, { recursive: true, force: true });
39
+ });
40
+
41
+ describe("baselineFilePath", () => {
42
+ it("builds a deterministic projectless path with counter and scan-kind suffix", () => {
43
+ const ti = makeTestInfo({ dir: tmpDir });
44
+ const wcag = baselineFilePath(ti, "wcag", 1);
45
+ const bp = baselineFilePath(ti, "best-practice", 1);
46
+ expect(wcag).toBe(
47
+ path.join(
48
+ tmpDir,
49
+ "standalone-accessibility-check-works-1.a11y-baseline.json",
50
+ ),
51
+ );
52
+ expect(bp).toBe(
53
+ path.join(
54
+ tmpDir,
55
+ "standalone-accessibility-check-works-1.a11y-baseline-best-practice.json",
56
+ ),
57
+ );
58
+ });
59
+
60
+ it("uses an incremented counter for multi-call tests", () => {
61
+ const ti = makeTestInfo({ dir: tmpDir });
62
+ const path2 = baselineFilePath(ti, "wcag", 2);
63
+ expect(path2).toContain("-2.a11y-baseline.json");
64
+ });
65
+ });
66
+
67
+ describe("nextAccessibilityScanCount", () => {
68
+ it("returns sequential numbers per (testInfo, scan)", () => {
69
+ const ti = {} as any;
56
70
  try {
57
- expect(nextAccessibilityScanCount(ti, 'wcag')).toBe(1)
58
- expect(nextAccessibilityScanCount(ti, 'wcag')).toBe(2)
59
- expect(nextAccessibilityScanCount(ti, 'best-practice')).toBe(1)
60
- expect(nextAccessibilityScanCount(ti, 'wcag')).toBe(3)
71
+ expect(nextAccessibilityScanCount(ti, "wcag")).toBe(1);
72
+ expect(nextAccessibilityScanCount(ti, "wcag")).toBe(2);
73
+ expect(nextAccessibilityScanCount(ti, "best-practice")).toBe(1);
74
+ expect(nextAccessibilityScanCount(ti, "wcag")).toBe(3);
61
75
  } finally {
62
- resetAccessibilityScanCounts(ti)
76
+ resetAccessibilityScanCounts(ti);
63
77
  }
64
- })
65
- })
66
-
67
- describe('snapshotExists', () => {
68
- it('returns false when the snapshots dir is missing', async () => {
69
- const ti = makeTestInfo({ dir: path.join(tmpDir, 'does-not-exist') })
70
- expect(await snapshotExists(ti)).toBe(false)
71
- })
72
-
73
- it('returns false when no snapshot file matches the test slug', async () => {
74
- await fs.writeFile(path.join(tmpDir, 'other-test-1-chromium-linux.txt'), 'data')
75
- const ti = makeTestInfo({ dir: tmpDir })
76
- expect(await snapshotExists(ti)).toBe(false)
77
- })
78
-
79
- it('returns true when a snapshot file matching the test slug exists', async () => {
78
+ });
79
+ });
80
+
81
+ describe("snapshotExists", () => {
82
+ it("returns false when the snapshots dir is missing", async () => {
83
+ const ti = makeTestInfo({ dir: path.join(tmpDir, "does-not-exist") });
84
+ expect(await snapshotExists(ti)).toBe(false);
85
+ });
86
+
87
+ it("returns false when no snapshot file matches the test slug", async () => {
80
88
  await fs.writeFile(
81
- path.join(tmpDir, 'standalone-accessibility-check-works-1-chromium-linux.txt'),
82
- 'data',
83
- )
84
- const ti = makeTestInfo({ dir: tmpDir })
85
- expect(await snapshotExists(ti)).toBe(true)
86
- })
87
- })
88
-
89
- describe('snapshotExists with Playwright-named snapshots', () => {
90
- const touch = (name: string) => fs.writeFile(path.join(tmpDir, name), 'data')
91
-
92
- it('matches a capitalised title with punctuation and project suffix', async () => {
93
- await touch('Test-error-pages-ID-3303-2-desktop-chrome-linux.txt')
94
- const ti = makeTestInfo({ dir: tmpDir, title: 'Test error pages: ID 3303' })
95
- expect(await snapshotExists(ti)).toBe(true)
96
- })
97
-
98
- it('matches with different project suffixes', async () => {
99
- await touch('Login-Works-1-mobile-safari-darwin.txt')
100
- const ti = makeTestInfo({ dir: tmpDir, title: 'Login Works' })
101
- expect(await snapshotExists(ti)).toBe(true)
102
- })
103
-
104
- it('does not match a title that differs only by case', async () => {
105
- await touch('login-works-1-desktop-chrome-linux.txt')
106
- const ti = makeTestInfo({ dir: tmpDir, title: 'Login Works' })
107
- expect(await snapshotExists(ti)).toBe(false)
108
- })
109
-
110
- it('returns false when nothing matches', async () => {
111
- await touch('Other-Test-1-desktop-chrome-linux.txt')
112
- const ti = makeTestInfo({ dir: tmpDir, title: 'Login Works' })
113
- expect(await snapshotExists(ti)).toBe(false)
114
- })
115
-
116
- it('ignores a matching .png without a .txt', async () => {
117
- await touch('Login-Works-1-desktop-chrome-linux.png')
118
- const ti = makeTestInfo({ dir: tmpDir, title: 'Login Works' })
119
- expect(await snapshotExists(ti)).toBe(false)
120
- })
121
-
122
- it('does not match a different test whose title extends this one', async () => {
123
- await touch('CRUD-tests-Video-Promo-1-desktop-chrome-linux.txt')
124
- const ti = makeTestInfo({ dir: tmpDir, title: 'CRUD tests - Video' })
125
- expect(await snapshotExists(ti)).toBe(false)
126
- })
127
- })
128
-
129
- describe('readBaselineFile / writeBaselineFile', () => {
130
- it('round-trips object schema with note and violations', async () => {
131
- const file = path.join(tmpDir, 'rt.a11y-baseline.json')
89
+ path.join(tmpDir, "other-test-1-chromium-linux.txt"),
90
+ "data",
91
+ );
92
+ const ti = makeTestInfo({ dir: tmpDir });
93
+ expect(await snapshotExists(ti)).toBe(false);
94
+ });
95
+
96
+ it("returns true when a snapshot file matching the test slug exists", async () => {
97
+ await fs.writeFile(
98
+ path.join(
99
+ tmpDir,
100
+ "standalone-accessibility-check-works-1-chromium-linux.txt",
101
+ ),
102
+ "data",
103
+ );
104
+ const ti = makeTestInfo({ dir: tmpDir });
105
+ expect(await snapshotExists(ti)).toBe(true);
106
+ });
107
+ });
108
+
109
+ describe("snapshotExists with Playwright-named snapshots", () => {
110
+ const touch = (name: string) =>
111
+ fs.writeFile(path.join(tmpDir, name), "data");
112
+
113
+ it("matches a capitalised title with punctuation and project suffix", async () => {
114
+ await touch("Test-error-pages-ID-3303-2-desktop-chrome-linux.txt");
115
+ const ti = makeTestInfo({
116
+ dir: tmpDir,
117
+ title: "Test error pages: ID 3303",
118
+ });
119
+ expect(await snapshotExists(ti)).toBe(true);
120
+ });
121
+
122
+ it("matches with different project suffixes", async () => {
123
+ await touch("Login-Works-1-mobile-safari-darwin.txt");
124
+ const ti = makeTestInfo({ dir: tmpDir, title: "Login Works" });
125
+ expect(await snapshotExists(ti)).toBe(true);
126
+ });
127
+
128
+ it("does not match a title that differs only by case", async () => {
129
+ await touch("login-works-1-desktop-chrome-linux.txt");
130
+ const ti = makeTestInfo({ dir: tmpDir, title: "Login Works" });
131
+ expect(await snapshotExists(ti)).toBe(false);
132
+ });
133
+
134
+ it("returns false when nothing matches", async () => {
135
+ await touch("Other-Test-1-desktop-chrome-linux.txt");
136
+ const ti = makeTestInfo({ dir: tmpDir, title: "Login Works" });
137
+ expect(await snapshotExists(ti)).toBe(false);
138
+ });
139
+
140
+ it("ignores a matching .png without a .txt", async () => {
141
+ await touch("Login-Works-1-desktop-chrome-linux.png");
142
+ const ti = makeTestInfo({ dir: tmpDir, title: "Login Works" });
143
+ expect(await snapshotExists(ti)).toBe(false);
144
+ });
145
+
146
+ it("does not match a different test whose title extends this one", async () => {
147
+ await touch("CRUD-tests-Video-Promo-1-desktop-chrome-linux.txt");
148
+ const ti = makeTestInfo({ dir: tmpDir, title: "CRUD tests - Video" });
149
+ expect(await snapshotExists(ti)).toBe(false);
150
+ });
151
+ });
152
+
153
+ describe("readBaselineFile / writeBaselineFile", () => {
154
+ it("round-trips object schema with note and violations", async () => {
155
+ const file = path.join(tmpDir, "rt.a11y-baseline.json");
132
156
  await writeBaselineFile(file, {
133
- note: 'No accessibility violations found',
157
+ note: "No accessibility violations found",
158
+ violations: [],
159
+ });
160
+ const back = await readBaselineFile(file);
161
+ expect(back).toEqual({
162
+ note: "No accessibility violations found",
134
163
  violations: [],
135
- })
136
- const back = await readBaselineFile(file)
137
- expect(back).toEqual({ note: 'No accessibility violations found', violations: [] })
138
- })
139
-
140
- it('returns null when file is missing', async () => {
141
- const file = path.join(tmpDir, 'missing.json')
142
- expect(await readBaselineFile(file)).toBeNull()
143
- })
144
-
145
- it('does not throw on EEXIST (idempotent first-write)', async () => {
146
- const file = path.join(tmpDir, 'idem.json')
147
- await writeBaselineFile(file, { note: 'first', violations: [] })
148
- await writeBaselineFile(file, { note: 'second', violations: [] })
149
- const back = await readBaselineFile(file)
150
- expect(back?.note).toBe('first')
151
- })
152
-
153
- it('throws a clear error on malformed JSON', async () => {
154
- const file = path.join(tmpDir, 'bad.json')
155
- await fs.writeFile(file, '{not json')
156
- await expect(readBaselineFile(file)).rejects.toThrow(/Malformed baseline file/)
157
- })
158
-
159
- it('creates parent directories on write', async () => {
160
- const file = path.join(tmpDir, 'nested', 'sub', 'baseline.json')
161
- await writeBaselineFile(file, { note: 'x', violations: [] })
162
- expect((await readBaselineFile(file))?.note).toBe('x')
163
- })
164
- })
165
-
166
- describe('buildSeed', () => {
167
- it('produces a clean-state payload for zero violations', () => {
164
+ });
165
+ });
166
+
167
+ it("returns null when file is missing", async () => {
168
+ const file = path.join(tmpDir, "missing.json");
169
+ expect(await readBaselineFile(file)).toBeNull();
170
+ });
171
+
172
+ it("does not throw on EEXIST (idempotent first-write)", async () => {
173
+ const file = path.join(tmpDir, "idem.json");
174
+ await writeBaselineFile(file, { note: "first", violations: [] });
175
+ await writeBaselineFile(file, { note: "second", violations: [] });
176
+ const back = await readBaselineFile(file);
177
+ expect(back?.note).toBe("first");
178
+ });
179
+
180
+ it("throws a clear error on malformed JSON", async () => {
181
+ const file = path.join(tmpDir, "bad.json");
182
+ await fs.writeFile(file, "{not json");
183
+ await expect(readBaselineFile(file)).rejects.toThrow(
184
+ /Malformed baseline file/,
185
+ );
186
+ });
187
+
188
+ it("creates parent directories on write", async () => {
189
+ const file = path.join(tmpDir, "nested", "sub", "baseline.json");
190
+ await writeBaselineFile(file, { note: "x", violations: [] });
191
+ expect((await readBaselineFile(file))?.note).toBe("x");
192
+ });
193
+ });
194
+
195
+ describe("buildSeed", () => {
196
+ it("produces a clean-state payload for zero violations", () => {
168
197
  expect(buildSeed([])).toEqual({
169
- note: 'No accessibility violations found',
198
+ note: "No accessibility violations found",
170
199
  violations: [],
171
- })
172
- })
173
-
174
- it('produces a TODO-prompted payload for non-zero violations', () => {
175
- const v = [{ rule: 'color-contrast', targets: ['#x'], reason: 'TODO', willBeFixedIn: 'TODO' }]
176
- const seed = buildSeed(v)
177
- expect(seed.note).toMatch(/TODO/)
178
- expect(seed.violations).toEqual(v)
179
- })
180
- })
181
- })
200
+ });
201
+ });
202
+
203
+ it("produces a TODO-prompted payload for non-zero violations", () => {
204
+ const v = [
205
+ {
206
+ rule: "color-contrast",
207
+ targets: ["#x"],
208
+ reason: "TODO",
209
+ willBeFixedIn: "TODO",
210
+ },
211
+ ];
212
+ const seed = buildSeed(v);
213
+ expect(seed.note).toMatch(/TODO/);
214
+ expect(seed.violations).toEqual(v);
215
+ });
216
+ });
217
+ });