@lullabot/playwright-testing 0.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 (82) hide show
  1. package/README.md +61 -0
  2. package/bin/github-a11y-summary +5 -0
  3. package/bin/github-failure-summary +8 -0
  4. package/lib/accessibility-baseline-file.d.ts +47 -0
  5. package/lib/accessibility-baseline-file.js +205 -0
  6. package/lib/accessibility-baseline.d.ts +19 -0
  7. package/lib/accessibility-baseline.js +38 -0
  8. package/lib/accessible-screenshot.d.ts +181 -0
  9. package/lib/accessible-screenshot.js +519 -0
  10. package/lib/focus.d.ts +14 -0
  11. package/lib/focus.js +28 -0
  12. package/lib/fonts.d.ts +18 -0
  13. package/lib/fonts.js +24 -0
  14. package/lib/frames.d.ts +7 -0
  15. package/lib/frames.js +27 -0
  16. package/lib/github/a11y-summary.d.ts +55 -0
  17. package/lib/github/a11y-summary.js +383 -0
  18. package/lib/github/attachments.d.ts +98 -0
  19. package/lib/github/attachments.js +297 -0
  20. package/lib/github/failure-summary.d.ts +144 -0
  21. package/lib/github/failure-summary.js +567 -0
  22. package/lib/github/index.d.ts +6 -0
  23. package/lib/github/index.js +35 -0
  24. package/lib/github/report-paths.d.ts +38 -0
  25. package/lib/github/report-paths.js +200 -0
  26. package/lib/hover.d.ts +13 -0
  27. package/lib/hover.js +61 -0
  28. package/lib/images.d.ts +118 -0
  29. package/lib/images.js +260 -0
  30. package/lib/index.d.ts +13 -0
  31. package/lib/index.js +29 -0
  32. package/lib/interaction-states.d.ts +22 -0
  33. package/lib/interaction-states.js +75 -0
  34. package/lib/mock/index.d.ts +1 -0
  35. package/lib/mock/index.js +5 -0
  36. package/lib/mock/youtube.d.ts +5 -0
  37. package/lib/mock/youtube.js +38 -0
  38. package/lib/pseudo-state.d.ts +17 -0
  39. package/lib/pseudo-state.js +50 -0
  40. package/lib/videos.d.ts +134 -0
  41. package/lib/videos.js +349 -0
  42. package/lib/visualdiff.d.ts +154 -0
  43. package/lib/visualdiff.js +197 -0
  44. package/package.json +47 -0
  45. package/src/accessibility-baseline-file.test.ts +181 -0
  46. package/src/accessibility-baseline-file.ts +208 -0
  47. package/src/accessibility-baseline.test.ts +601 -0
  48. package/src/accessibility-baseline.ts +50 -0
  49. package/src/accessible-screenshot.test.ts +597 -0
  50. package/src/accessible-screenshot.ts +809 -0
  51. package/src/focus.test.ts +34 -0
  52. package/src/focus.ts +27 -0
  53. package/src/fonts.test.ts +17 -0
  54. package/src/fonts.ts +23 -0
  55. package/src/frames.test.ts +75 -0
  56. package/src/frames.ts +26 -0
  57. package/src/github/a11y-summary.test.ts +439 -0
  58. package/src/github/a11y-summary.ts +421 -0
  59. package/src/github/attachments.test.ts +248 -0
  60. package/src/github/attachments.ts +328 -0
  61. package/src/github/failure-summary.test.ts +636 -0
  62. package/src/github/failure-summary.ts +720 -0
  63. package/src/github/index.test.ts +24 -0
  64. package/src/github/index.ts +35 -0
  65. package/src/github/report-paths.test.ts +222 -0
  66. package/src/github/report-paths.ts +208 -0
  67. package/src/hover.test.ts +76 -0
  68. package/src/hover.ts +64 -0
  69. package/src/images.test.ts +355 -0
  70. package/src/images.ts +299 -0
  71. package/src/index.ts +13 -0
  72. package/src/interaction-states.test.ts +48 -0
  73. package/src/interaction-states.ts +94 -0
  74. package/src/mock/index.ts +1 -0
  75. package/src/mock/youtube.test.ts +38 -0
  76. package/src/mock/youtube.ts +39 -0
  77. package/src/pseudo-state.test.ts +83 -0
  78. package/src/pseudo-state.ts +69 -0
  79. package/src/videos.test.ts +637 -0
  80. package/src/videos.ts +389 -0
  81. package/src/visualdiff.test.ts +452 -0
  82. package/src/visualdiff.ts +381 -0
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # `@lullabot/playwright-testing`
2
+
3
+ Framework-neutral utilities for stable Playwright screenshots, accessibility
4
+ baselines, URL-driven visual comparisons, and GitHub reporting.
5
+
6
+ This package is developed in the
7
+ [`playwright-drupal` monorepo](https://github.com/Lullabot/playwright-drupal),
8
+ but it has no Drupal runtime assumptions. Drupal projects can instead install
9
+ [`@lullabot/playwright-drupal`](https://www.npmjs.com/package/@lullabot/playwright-drupal),
10
+ which applies Drupal-specific defaults and preserves the original API.
11
+
12
+ ## Install
13
+
14
+ ```console
15
+ npm install --save-dev @lullabot/playwright-testing @playwright/test
16
+ ```
17
+
18
+ `@axe-core/playwright` and `@playwright/test` are runtime dependencies of this
19
+ package. Your project should still declare its own compatible
20
+ `@playwright/test` dependency so the test runner and imported types use the same
21
+ version.
22
+
23
+ ## Package entry points
24
+
25
+ - `@lullabot/playwright-testing` exports screenshot stabilization,
26
+ accessibility checks and baselines, visual-diff definitions, interaction and
27
+ pseudo-state helpers, and reusable mocks.
28
+ - `@lullabot/playwright-testing/github` exports the optional GitHub report,
29
+ attachment-upload, and path-remapping APIs.
30
+ - `playwright-testing-a11y-summary` and
31
+ `playwright-testing-failure-summary` expose the reporting commands.
32
+
33
+ The package does not replace Playwright's `test` fixture. Import `test`,
34
+ `expect`, and `testInfo` from `@playwright/test`, then pass the page and test
35
+ metadata to the helpers:
36
+
37
+ ```typescript
38
+ import { expect, test } from '@playwright/test';
39
+ import { takeAccessibleScreenshot } from '@lullabot/playwright-testing';
40
+
41
+ test('home page', async ({ page }, testInfo) => {
42
+ await page.goto('/');
43
+ await takeAccessibleScreenshot(page, testInfo, { fullPage: true });
44
+ await expect(page).toHaveTitle(/Example/);
45
+ });
46
+ ```
47
+
48
+ ## Guides
49
+
50
+ - [Accessibility testing](https://github.com/Lullabot/playwright-drupal/blob/main/packages/playwright-testing/docs/accessibility.md)
51
+ - [Stable screenshots and visual comparisons](https://github.com/Lullabot/playwright-drupal/blob/main/packages/playwright-testing/docs/screenshots-and-visual-comparisons.md)
52
+ - [GitHub reporting](https://github.com/Lullabot/playwright-drupal/blob/main/packages/playwright-testing/docs/github-reporting.md)
53
+
54
+ ## Drupal compatibility
55
+
56
+ Existing `@lullabot/playwright-drupal` imports remain supported. That package
57
+ depends on this one, re-exports the generic APIs, and wraps screenshot and
58
+ visual-diff calls with its Drupal preset. New framework-neutral code can import
59
+ this package directly; Drupal tests that depend on database isolation, Drush,
60
+ login helpers, or the `a11y` fixture should continue importing the Drupal
61
+ package.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { main } = require('../lib/github/a11y-summary.js')
4
+
5
+ main()
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { main } = require('../lib/github/failure-summary.js')
4
+
5
+ main().catch((error) => {
6
+ console.error(error)
7
+ process.exit(1)
8
+ })
@@ -0,0 +1,47 @@
1
+ import type { TestInfo } from '@playwright/test';
2
+ import type { AccessibilityBaselineEntry } from './accessibility-baseline.js';
3
+ export type ScanKind = 'wcag' | 'best-practice';
4
+ export interface OnDiskBaselineFile {
5
+ note: string;
6
+ violations: AccessibilityBaselineEntry[];
7
+ }
8
+ /**
9
+ * Increment and return the 1-indexed invocation number for this scan kind
10
+ * within the current test. Every call to `checkAccessibility()` runs up to
11
+ * two scans (WCAG + best-practice); each scan gets its own counter so that
12
+ * a test calling `checkAccessibility()` twice produces distinct baseline
13
+ * file names (`…-1.a11y-baseline.json`, `…-2.a11y-baseline.json`) even when
14
+ * both scans share a single `checkAccessibility()` call.
15
+ *
16
+ * Counter state is keyed by the TestInfo object (held in a WeakMap so it
17
+ * is discarded with the test). Use `resetAccessibilityScanCounts()` to
18
+ * clear state explicitly — for example, between tests in a unit-test
19
+ * suite that reuses a mocked TestInfo.
20
+ */
21
+ export declare function nextAccessibilityScanCount(testInfo: Pick<TestInfo, 'testId'> | object, scan: ScanKind): number;
22
+ export declare function resetAccessibilityScanCounts(testInfo: object): void;
23
+ /**
24
+ * Build the on-disk baseline file path for a single scan call.
25
+ *
26
+ * Playwright exposes `testInfo.snapshotPath(...name)` as the public way to
27
+ * resolve a file under the test's snapshots directory (honoring any
28
+ * user-configured `snapshotPathTemplate`). We pass a filename that mixes
29
+ * the slugified test title + call counter + a scan-specific suffix, so two
30
+ * different tests in the same spec file don't collide and each scan has
31
+ * its own file.
32
+ *
33
+ * There is no reusable Playwright API that bakes the test title into an
34
+ * explicit-name snapshot path — that logic is private to `toMatchSnapshot()`
35
+ * for its auto-counter naming — so we build the stem ourselves.
36
+ */
37
+ export declare function baselineFilePath(testInfo: Pick<TestInfo, 'snapshotPath' | 'titlePath' | 'title'>, scan: ScanKind, callCount: number): string;
38
+ /**
39
+ * Return true if a Playwright snapshot file already exists on disk for
40
+ * this test — i.e. the test has been previously committed under snapshot
41
+ * mode. Used to preserve snapshot-mode behaviour for existing tests while
42
+ * defaulting new (snapshotless) tests into on-disk baseline mode.
43
+ */
44
+ export declare function snapshotExists(testInfo: Pick<TestInfo, 'snapshotPath' | 'titlePath' | 'title'>): Promise<boolean>;
45
+ export declare function readBaselineFile(filePath: string): Promise<OnDiskBaselineFile | null>;
46
+ export declare function writeBaselineFile(filePath: string, data: OnDiskBaselineFile): Promise<void>;
47
+ export declare function buildSeed(violations: AccessibilityBaselineEntry[]): OnDiskBaselineFile;
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.nextAccessibilityScanCount = nextAccessibilityScanCount;
7
+ exports.resetAccessibilityScanCounts = resetAccessibilityScanCounts;
8
+ exports.baselineFilePath = baselineFilePath;
9
+ exports.snapshotExists = snapshotExists;
10
+ exports.readBaselineFile = readBaselineFile;
11
+ exports.writeBaselineFile = writeBaselineFile;
12
+ exports.buildSeed = buildSeed;
13
+ const fs_1 = require("fs");
14
+ const path_1 = __importDefault(require("path"));
15
+ const scanInvocationCounters = new WeakMap();
16
+ /**
17
+ * Increment and return the 1-indexed invocation number for this scan kind
18
+ * within the current test. Every call to `checkAccessibility()` runs up to
19
+ * two scans (WCAG + best-practice); each scan gets its own counter so that
20
+ * a test calling `checkAccessibility()` twice produces distinct baseline
21
+ * file names (`…-1.a11y-baseline.json`, `…-2.a11y-baseline.json`) even when
22
+ * both scans share a single `checkAccessibility()` call.
23
+ *
24
+ * Counter state is keyed by the TestInfo object (held in a WeakMap so it
25
+ * is discarded with the test). Use `resetAccessibilityScanCounts()` to
26
+ * clear state explicitly — for example, between tests in a unit-test
27
+ * suite that reuses a mocked TestInfo.
28
+ */
29
+ function nextAccessibilityScanCount(testInfo, scan) {
30
+ const key = testInfo;
31
+ let counts = scanInvocationCounters.get(key);
32
+ if (!counts) {
33
+ counts = new Map();
34
+ scanInvocationCounters.set(key, counts);
35
+ }
36
+ const n = (counts.get(scan) ?? 0) + 1;
37
+ counts.set(scan, n);
38
+ return n;
39
+ }
40
+ function resetAccessibilityScanCounts(testInfo) {
41
+ scanInvocationCounters.delete(testInfo);
42
+ }
43
+ /**
44
+ * Slugify a test's fully qualified title the same way we use it as the
45
+ * stem of both on-disk baseline filenames and (for existence checks) the
46
+ * prefix of Playwright's auto-generated snapshot filenames.
47
+ *
48
+ * Implemented as a single-pass character scan to avoid regex-based
49
+ * polynomial backtracking on library-supplied input (CodeQL
50
+ * js/polynomial-redos).
51
+ */
52
+ function slugifyTitle(testInfo) {
53
+ const segments = testInfo.titlePath?.slice(1) ?? [];
54
+ const raw = segments.length > 0 ? segments.join(' ') : testInfo.title;
55
+ let out = '';
56
+ let lastWasHyphen = true; // suppresses leading hyphen
57
+ for (let i = 0; i < raw.length; i++) {
58
+ const code = raw.charCodeAt(i);
59
+ const isLowerAlpha = code >= 97 && code <= 122; // a-z
60
+ const isUpperAlpha = code >= 65 && code <= 90; // A-Z
61
+ const isDigit = code >= 48 && code <= 57; // 0-9
62
+ if (isLowerAlpha || isDigit) {
63
+ out += raw[i];
64
+ lastWasHyphen = false;
65
+ }
66
+ else if (isUpperAlpha) {
67
+ out += String.fromCharCode(code + 32);
68
+ lastWasHyphen = false;
69
+ }
70
+ else if (!lastWasHyphen) {
71
+ out += '-';
72
+ lastWasHyphen = true;
73
+ }
74
+ }
75
+ // Trim trailing hyphen.
76
+ if (out.endsWith('-'))
77
+ out = out.slice(0, -1);
78
+ return out;
79
+ }
80
+ /**
81
+ * Reproduce the stem Playwright uses for auto-named snapshot files: the
82
+ * title path (minus the spec file) joined with spaces, with every run of
83
+ * control/punctuation characters collapsed to a single hyphen. Unlike
84
+ * `slugifyTitle()` this preserves case and does not trim hyphens, so it
85
+ * matches committed snapshot filenames exactly. Mirrors Playwright's
86
+ * `sanitizeForFilePath()`. Very long titles, which Playwright truncates
87
+ * and hashes, are not handled.
88
+ */
89
+ function playwrightSnapshotStem(testInfo) {
90
+ const segments = testInfo.titlePath?.slice(1) ?? [];
91
+ const raw = segments.length > 0 ? segments.join(' ') : testInfo.title;
92
+ let out = '';
93
+ let lastWasReplaced = false;
94
+ for (let i = 0; i < raw.length; i++) {
95
+ const code = raw.charCodeAt(i);
96
+ const replaced = code <= 0x2c ||
97
+ (code >= 0x2e && code <= 0x2f) ||
98
+ (code >= 0x3a && code <= 0x40) ||
99
+ (code >= 0x5b && code <= 0x60) ||
100
+ (code >= 0x7b && code <= 0x7f);
101
+ if (replaced) {
102
+ if (!lastWasReplaced)
103
+ out += '-';
104
+ lastWasReplaced = true;
105
+ }
106
+ else {
107
+ out += raw[i];
108
+ lastWasReplaced = false;
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+ /**
114
+ * Build the on-disk baseline file path for a single scan call.
115
+ *
116
+ * Playwright exposes `testInfo.snapshotPath(...name)` as the public way to
117
+ * resolve a file under the test's snapshots directory (honoring any
118
+ * user-configured `snapshotPathTemplate`). We pass a filename that mixes
119
+ * the slugified test title + call counter + a scan-specific suffix, so two
120
+ * different tests in the same spec file don't collide and each scan has
121
+ * its own file.
122
+ *
123
+ * There is no reusable Playwright API that bakes the test title into an
124
+ * explicit-name snapshot path — that logic is private to `toMatchSnapshot()`
125
+ * for its auto-counter naming — so we build the stem ourselves.
126
+ */
127
+ function baselineFilePath(testInfo, scan, callCount) {
128
+ const slug = slugifyTitle(testInfo);
129
+ const suffix = scan === 'wcag' ? 'a11y-baseline' : 'a11y-baseline-best-practice';
130
+ return testInfo.snapshotPath(`${slug}-${callCount}.${suffix}.json`);
131
+ }
132
+ /**
133
+ * Return true if a Playwright snapshot file already exists on disk for
134
+ * this test — i.e. the test has been previously committed under snapshot
135
+ * mode. Used to preserve snapshot-mode behaviour for existing tests while
136
+ * defaulting new (snapshotless) tests into on-disk baseline mode.
137
+ */
138
+ async function snapshotExists(testInfo) {
139
+ const dir = path_1.default.dirname(testInfo.snapshotPath('a11y-baseline-probe'));
140
+ let entries;
141
+ try {
142
+ entries = await fs_1.promises.readdir(dir);
143
+ }
144
+ catch (err) {
145
+ if (err?.code === 'ENOENT')
146
+ return false;
147
+ throw err;
148
+ }
149
+ // Playwright names snapshots `<stem>-<counter>[-<project>-<platform>].txt`.
150
+ // Require the counter so a title that merely extends this one (e.g.
151
+ // "Video" vs "Video Promo") is not mistaken for this test's snapshot.
152
+ const prefix = `${playwrightSnapshotStem(testInfo)}-`;
153
+ return entries.some(name => {
154
+ if (!name.startsWith(prefix) || !name.endsWith('.txt'))
155
+ return false;
156
+ let i = prefix.length;
157
+ while (i < name.length && name.charCodeAt(i) >= 48 && name.charCodeAt(i) <= 57)
158
+ i++;
159
+ return i > prefix.length && (name[i] === '-' || name[i] === '.');
160
+ });
161
+ }
162
+ async function readBaselineFile(filePath) {
163
+ let raw;
164
+ try {
165
+ raw = await fs_1.promises.readFile(filePath, 'utf8');
166
+ }
167
+ catch (err) {
168
+ if (err?.code === 'ENOENT')
169
+ return null;
170
+ throw err;
171
+ }
172
+ try {
173
+ const parsed = JSON.parse(raw);
174
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.violations)) {
175
+ throw new Error('expected an object with a "violations" array');
176
+ }
177
+ return {
178
+ note: typeof parsed.note === 'string' ? parsed.note : '',
179
+ violations: parsed.violations,
180
+ };
181
+ }
182
+ catch (err) {
183
+ throw new Error(`Malformed baseline file at ${filePath}: ${err.message}`);
184
+ }
185
+ }
186
+ async function writeBaselineFile(filePath, data) {
187
+ await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
188
+ try {
189
+ await fs_1.promises.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', { flag: 'wx' });
190
+ }
191
+ catch (err) {
192
+ if (err?.code === 'EEXIST')
193
+ return;
194
+ throw err;
195
+ }
196
+ }
197
+ function buildSeed(violations) {
198
+ if (violations.length === 0) {
199
+ return { note: 'No accessibility violations found', violations: [] };
200
+ }
201
+ return {
202
+ note: 'TODO: fill in reason and willBeFixedIn for each entry before committing.',
203
+ violations,
204
+ };
205
+ }
@@ -0,0 +1,19 @@
1
+ export interface AccessibilityBaselineEntry {
2
+ /** axe rule ID (e.g. 'color-contrast') */
3
+ rule: string;
4
+ /** CSS selectors for the elements with this violation */
5
+ targets: string[];
6
+ /** Why this violation is accepted */
7
+ reason: string;
8
+ /** Link to tracking ticket */
9
+ willBeFixedIn: string;
10
+ }
11
+ export type AccessibilityBaseline = AccessibilityBaselineEntry[];
12
+ export declare function defineAccessibilityBaseline(entries: AccessibilityBaseline): AccessibilityBaseline;
13
+ /**
14
+ * Validate waiver metadata and reject ambiguous duplicate entries.
15
+ *
16
+ * Seed files deliberately contain TODO placeholders, so callers validate only
17
+ * committed/in-code baselines, not a seed during the local creation run.
18
+ */
19
+ export declare function validateAccessibilityBaseline(entries: AccessibilityBaseline): void;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineAccessibilityBaseline = defineAccessibilityBaseline;
4
+ exports.validateAccessibilityBaseline = validateAccessibilityBaseline;
5
+ function defineAccessibilityBaseline(entries) {
6
+ validateAccessibilityBaseline(entries);
7
+ return entries;
8
+ }
9
+ /**
10
+ * Validate waiver metadata and reject ambiguous duplicate entries.
11
+ *
12
+ * Seed files deliberately contain TODO placeholders, so callers validate only
13
+ * committed/in-code baselines, not a seed during the local creation run.
14
+ */
15
+ function validateAccessibilityBaseline(entries) {
16
+ const seen = new Set();
17
+ entries.forEach((entry, index) => {
18
+ if (!entry.rule.trim()) {
19
+ throw new Error(`Accessibility baseline entry ${index + 1} requires a rule.`);
20
+ }
21
+ if (entry.targets.length === 0 || entry.targets.some(target => !target.trim())) {
22
+ throw new Error(`Accessibility baseline entry ${index + 1} requires at least one non-empty target.`);
23
+ }
24
+ if (!entry.reason.trim() || entry.reason.trim().toUpperCase() === 'TODO') {
25
+ throw new Error(`Accessibility baseline entry ${index + 1} requires a reason.`);
26
+ }
27
+ if (!entry.willBeFixedIn.trim() || entry.willBeFixedIn.trim().toUpperCase() === 'TODO') {
28
+ throw new Error(`Accessibility baseline entry ${index + 1} requires a willBeFixedIn tracking reference.`);
29
+ }
30
+ for (const target of new Set(entry.targets)) {
31
+ const key = JSON.stringify([entry.rule, target]);
32
+ if (seen.has(key)) {
33
+ throw new Error(`Accessibility baseline contains a duplicate waiver for ${entry.rule} on ${target}.`);
34
+ }
35
+ seen.add(key);
36
+ }
37
+ });
38
+ }
@@ -0,0 +1,181 @@
1
+ import { type Locator, type Page, type TestInfo } from "@playwright/test";
2
+ import { type WaitForImagesOptions } from "./images.js";
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';
7
+ export interface ScreenshotStabilizationOptions {
8
+ images?: WaitForImagesOptions;
9
+ videos?: WaitForVideosOptions;
10
+ }
11
+ export interface ScreenshotOptions {
12
+ /**
13
+ * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different
14
+ * treatment depending on their duration:
15
+ * - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
16
+ * - infinite animations are canceled to initial state, and then played over after the screenshot.
17
+ *
18
+ * Defaults to `"disabled"` that disables animations.
19
+ */
20
+ animations?: "disabled" | "allow";
21
+ /**
22
+ * When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be
23
+ * changed. Defaults to `"hide"`.
24
+ */
25
+ caret?: "hide" | "initial";
26
+ /**
27
+ * When `true` (the default), blurs the active element before capturing so a
28
+ * stray focus ring left over from earlier test interactions does not appear in
29
+ * only some runs (an invisible-to-a-human diff that still trips the pixel
30
+ * budget). Set to `false` when the screenshot intentionally captures a focused
31
+ * state.
32
+ */
33
+ blur?: boolean;
34
+ /**
35
+ * When `true` (the default), moves the pointer onto a temporary transparent
36
+ * shield before capturing so stale pointer activity does not leave an
37
+ * unrelated element in its hover state. Set to `false` when the screenshot
38
+ * intentionally captures a hovered state.
39
+ */
40
+ clearHover?: boolean;
41
+ /**
42
+ * Real hover and focus states to apply after the page has settled. These are
43
+ * supported in Chromium, Firefox, and WebKit and remain active through both
44
+ * the screenshot and accessibility scan. At most one locator may receive
45
+ * each state, matching what a real pointer and keyboard can do.
46
+ */
47
+ interactionStates?: ScreenshotInteractionState[];
48
+ /** Configuration for image and video settling. */
49
+ stabilization?: ScreenshotStabilizationOptions;
50
+ /**
51
+ * An object specifying the page area to capture, in CSS pixels.
52
+ */
53
+ clip?: {
54
+ x: number;
55
+ y: number;
56
+ width: number;
57
+ height: number;
58
+ };
59
+ /**
60
+ * When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to
61
+ * `false`.
62
+ */
63
+ fullPage?: boolean;
64
+ /**
65
+ * Derive a page screenshot clip from this locator after readiness waits and
66
+ * scrolling. Round x, y, width and height to integer CSS pixels without
67
+ * modifying rendering. Mutually exclusive with clip and a locator target.
68
+ * Use fullPage for targets larger than the viewport.
69
+ */
70
+ clipLocator?: Locator;
71
+ /**
72
+ * Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink
73
+ * box `#FF00FF` (customized by `maskColor`) that completely covers its bounding box.
74
+ */
75
+ mask?: Array<Locator>;
76
+ /**
77
+ * Specify the color of the overlay box for masked elements, in
78
+ * [CSS color format](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). Default color is pink `#FF00FF`.
79
+ */
80
+ maskColor?: string;
81
+ /**
82
+ * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is
83
+ * configurable with `TestConfig.expect`. Unset by default.
84
+ */
85
+ maxDiffPixelRatio?: number;
86
+ /**
87
+ * An acceptable amount of pixels that could be different. Default is configurable with `TestConfig.expect`. Unset by
88
+ * default.
89
+ */
90
+ maxDiffPixels?: number;
91
+ /**
92
+ * Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images.
93
+ * Defaults to `false`.
94
+ */
95
+ omitBackground?: boolean;
96
+ /**
97
+ * When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this
98
+ * will keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so
99
+ * screenshots of high-dpi devices will be twice as large or even larger.
100
+ *
101
+ * Defaults to `"css"`.
102
+ */
103
+ scale?: "css" | "device";
104
+ /**
105
+ * A stylesheet, or list of stylesheets, to apply while taking the screenshot.
106
+ * The styles pierce Shadow DOM and apply to inner frames.
107
+ */
108
+ stylePath?: string | string[];
109
+ /**
110
+ * An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
111
+ * same pixel in compared images, between zero (strict) and one (lax), default is configurable with
112
+ * `TestConfig.expect`. Defaults to `0.2`.
113
+ */
114
+ threshold?: number;
115
+ /**
116
+ * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`.
117
+ */
118
+ timeout?: number;
119
+ /**
120
+ * Accessibility options passed through to checkAccessibility().
121
+ */
122
+ accessibility?: AccessibilityOptions;
123
+ }
124
+ export interface AccessibilityOptions {
125
+ /** axe tags for WCAG scan. Default: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] */
126
+ wcagTags?: string[];
127
+ /** Additional CSS selectors to exclude from both scans. */
128
+ exclude?: string[];
129
+ /** CSS selectors to exclude only from the best-practice scan. */
130
+ bestPracticeExclude?: string[];
131
+ /** CSS selectors to exclude only from the WCAG scan. */
132
+ wcagExclude?: string[];
133
+ /**
134
+ * Best-practice scan mode.
135
+ * - 'soft': uses expect.soft() (default, current behaviour)
136
+ * - 'hard': uses expect() — test fails immediately on violations
137
+ * - 'off': skips best-practice scan entirely
138
+ */
139
+ bestPracticeMode?: 'soft' | 'hard' | 'off';
140
+ /** Additional axe rules to enable/disable. */
141
+ rules?: Record<string, {
142
+ enabled: boolean;
143
+ }>;
144
+ /** Baseline of known violations. When provided, violations matching the baseline are suppressed and toMatchSnapshot() is skipped. */
145
+ baseline?: AccessibilityBaseline;
146
+ /**
147
+ * When true, captures a full-page screenshot with violating elements
148
+ * highlighted (red outline) and attaches it to the test report.
149
+ * Default: true.
150
+ */
151
+ screenshotViolations?: boolean;
152
+ }
153
+ /**
154
+ * Run accessibility checks on the current page using axe-core.
155
+ *
156
+ * Runs a best-practice scan (unless bestPracticeMode is 'off') and a WCAG scan,
157
+ * attaching JSON results and asserting on violations via snapshots.
158
+ *
159
+ * @param page The Page fixture from the test.
160
+ * @param testInfo The testInfo object from the test.
161
+ * @param options Accessibility options to customise the scan.
162
+ */
163
+ export declare function checkAccessibility(page: Page, testInfo: TestInfo, options?: AccessibilityOptions): Promise<void>;
164
+ /**
165
+ * Take a visual comparison, and also ensure there's no accessibility issues.
166
+ *
167
+ * @param page The Page fixture from the test.
168
+ * @param testInfo The testInfo object from the test.
169
+ * @param options Screenshot options from toHaveScreenshot().
170
+ * @param scrollLocator A locator to ensure is visible before taking the screenshot.
171
+ * @param locator A specific locator to take the screenshot of. aXe still checks the whole page.
172
+ */
173
+ export declare function takeAccessibleScreenshot(page: Page, testInfo: TestInfo, options?: ScreenshotOptions, scrollLocator?: Locator, locator?: Locator | Page): Promise<void>;
174
+ /**
175
+ * Normalize a single CSS selector target for stable comparison.
176
+ *
177
+ * Replaces unique numeric HTML IDs and aria-labelledby suffixes with
178
+ * a stable placeholder so that snapshots and baseline matching are
179
+ * deterministic across runs.
180
+ */
181
+ export declare function normalizeTarget(target: string | string[]): string | string[];