@lullabot/playwright-testing 1.0.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.
- package/bin/github-a11y-summary +2 -2
- package/bin/github-failure-summary +4 -4
- package/lib/accessibility-baseline-file.d.ts +6 -6
- package/lib/accessibility-baseline-file.js +29 -23
- package/lib/accessibility-baseline.js +5 -3
- package/lib/accessible-screenshot.d.ts +4 -4
- package/lib/accessible-screenshot.js +88 -76
- package/lib/focus.js +4 -1
- package/lib/frames.js +4 -4
- package/lib/github/a11y-summary.js +74 -79
- package/lib/github/attachments.d.ts +1 -1
- package/lib/github/attachments.js +36 -34
- package/lib/github/failure-summary.d.ts +4 -4
- package/lib/github/failure-summary.js +139 -107
- package/lib/github/index.d.ts +6 -6
- package/lib/github/report-paths.js +17 -9
- package/lib/hover.js +17 -17
- package/lib/images.js +15 -12
- package/lib/index.d.ts +13 -13
- package/lib/interaction-states.d.ts +2 -2
- package/lib/interaction-states.js +7 -7
- package/lib/mock/index.d.ts +1 -1
- package/lib/mock/youtube.d.ts +1 -1
- package/lib/mock/youtube.js +1 -1
- package/lib/pseudo-state.d.ts +2 -2
- package/lib/pseudo-state.js +15 -7
- package/lib/videos.js +21 -10
- package/lib/visualdiff.d.ts +4 -4
- package/lib/visualdiff.js +35 -27
- package/package.json +3 -3
- package/src/accessibility-baseline-file.test.ts +190 -154
- package/src/accessibility-baseline-file.ts +107 -83
- package/src/accessibility-baseline.test.ts +633 -433
- package/src/accessibility-baseline.ts +41 -21
- package/src/accessible-screenshot.test.ts +551 -423
- package/src/accessible-screenshot.ts +334 -245
- package/src/focus.test.ts +23 -23
- package/src/focus.ts +7 -2
- package/src/fonts.test.ts +13 -13
- package/src/fonts.ts +1 -1
- package/src/frames.test.ts +10 -8
- package/src/frames.ts +5 -5
- package/src/github/a11y-summary.test.ts +436 -332
- package/src/github/a11y-summary.ts +189 -179
- package/src/github/attachments.test.ts +228 -167
- package/src/github/attachments.ts +144 -121
- package/src/github/failure-summary.test.ts +659 -445
- package/src/github/failure-summary.ts +376 -274
- package/src/github/index.test.ts +14 -14
- package/src/github/index.ts +6 -6
- package/src/github/report-paths.test.ts +211 -156
- package/src/github/report-paths.ts +75 -61
- package/src/hover.test.ts +50 -46
- package/src/hover.ts +18 -18
- package/src/images.test.ts +251 -206
- package/src/images.ts +52 -43
- package/src/index.ts +13 -13
- package/src/interaction-states.test.ts +72 -42
- package/src/interaction-states.ts +43 -29
- package/src/mock/index.ts +1 -1
- package/src/mock/youtube.test.ts +26 -26
- package/src/mock/youtube.ts +4 -6
- package/src/pseudo-state.test.ts +58 -51
- package/src/pseudo-state.ts +32 -27
- package/src/videos.test.ts +559 -399
- package/src/videos.ts +68 -44
- package/src/visualdiff.test.ts +416 -315
- package/src/visualdiff.ts +140 -99
package/bin/github-a11y-summary
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { main } = require(
|
|
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
|
|
2
|
-
import type { AccessibilityBaselineEntry } from
|
|
3
|
-
export type ScanKind =
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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(
|
|
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 ===
|
|
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(
|
|
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 ===
|
|
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(
|
|
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 &&
|
|
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] ===
|
|
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,
|
|
167
|
+
raw = await fs_1.promises.readFile(filePath, "utf8");
|
|
166
168
|
}
|
|
167
169
|
catch (err) {
|
|
168
|
-
if (err?.code ===
|
|
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 ||
|
|
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 ===
|
|
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) +
|
|
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 ===
|
|
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:
|
|
205
|
+
return { note: "No accessibility violations found", violations: [] };
|
|
200
206
|
}
|
|
201
207
|
return {
|
|
202
|
-
note:
|
|
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 ||
|
|
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() ===
|
|
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() ||
|
|
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
|
|
5
|
-
import { type AccessibilityBaseline } from
|
|
6
|
-
export type { InteractionState, ScreenshotInteractionState } from
|
|
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?:
|
|
139
|
+
bestPracticeMode?: "soft" | "hard" | "off";
|
|
140
140
|
/** Additional axe rules to enable/disable. */
|
|
141
141
|
rules?: Record<string, {
|
|
142
142
|
enabled: boolean;
|