@a11y-skills/audit 0.1.0 → 0.3.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.
- package/CHANGELOG.md +65 -0
- package/README.ja.md +76 -6
- package/README.md +78 -6
- package/dist/constants.d.ts +84 -0
- package/dist/constants.js +228 -0
- package/dist/detectors/index.d.ts +1 -0
- package/dist/detectors/index.js +1 -0
- package/dist/detectors/pause-control.d.ts +18 -0
- package/dist/detectors/pause-control.js +206 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/playwright/index.d.ts +7 -1
- package/dist/playwright/index.js +7 -1
- package/dist/playwright/runAutoPlayDetection.d.ts +36 -0
- package/dist/playwright/runAutoPlayDetection.js +143 -0
- package/dist/playwright/runAutocompleteAudit.d.ts +27 -0
- package/dist/playwright/runAutocompleteAudit.js +227 -0
- package/dist/playwright/runAxeAudit.d.ts +4 -0
- package/dist/playwright/runAxeAudit.js +26 -30
- package/dist/playwright/runFocusIndicatorCheck.js +55 -12
- package/dist/playwright/runOrientationCheck.d.ts +40 -0
- package/dist/playwright/runOrientationCheck.js +170 -0
- package/dist/playwright/runReflowCheck.js +18 -11
- package/dist/playwright/runTargetSizeCheck.js +42 -10
- package/dist/playwright/runTextSpacingCheck.d.ts +25 -0
- package/dist/playwright/runTextSpacingCheck.js +285 -0
- package/dist/playwright/runTimeLimitDetector.d.ts +31 -0
- package/dist/playwright/runTimeLimitDetector.js +219 -0
- package/dist/playwright/runZoomCheck.d.ts +42 -0
- package/dist/playwright/runZoomCheck.js +174 -0
- package/dist/schemas/index.d.ts +20 -1
- package/dist/schemas/index.js +404 -186
- package/dist/test-entries/auto-play-detection.d.ts +7 -0
- package/dist/test-entries/auto-play-detection.js +13 -0
- package/dist/test-entries/autocomplete-audit.d.ts +5 -0
- package/dist/test-entries/autocomplete-audit.js +11 -0
- package/dist/test-entries/orientation-check.d.ts +8 -0
- package/dist/test-entries/orientation-check.js +12 -0
- package/dist/test-entries/text-spacing-check.d.ts +5 -0
- package/dist/test-entries/text-spacing-check.js +11 -0
- package/dist/test-entries/time-limit-detector.d.ts +8 -0
- package/dist/test-entries/time-limit-detector.js +12 -0
- package/dist/test-entries/zoom-200-check.d.ts +5 -0
- package/dist/test-entries/zoom-200-check.js +11 -0
- package/dist/types.d.ts +275 -40
- package/dist/types.js +9 -0
- package/dist/utils/axe-format.d.ts +88 -0
- package/dist/utils/axe-format.js +361 -0
- package/dist/utils/image-compare.d.ts +24 -0
- package/dist/utils/image-compare.js +49 -0
- package/dist/utils/layout.d.ts +2 -0
- package/dist/utils/layout.js +20 -1
- package/dist/utils/recommendations.d.ts +18 -0
- package/dist/utils/recommendations.js +88 -0
- package/dist/utils/rule-registry.d.ts +216 -0
- package/dist/utils/rule-registry.js +220 -0
- package/dist/utils/test-harness.d.ts +8 -2
- package/dist/utils/test-harness.js +13 -6
- package/package.json +32 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for auto-play detection (WCAG 1.4.2 / 2.2.2).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/auto-play-detection";`
|
|
4
|
+
*
|
|
5
|
+
* Requires the optional `pixelmatch` + `pngjs` deps.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from '@playwright/test';
|
|
8
|
+
import { runAutoPlayDetection } from '../playwright/runAutoPlayDetection.js';
|
|
9
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
10
|
+
test('auto-play content detection', async ({ page }) => {
|
|
11
|
+
await page.goto(requireTargetUrl(), { waitUntil: 'networkidle' });
|
|
12
|
+
await runAutoPlayDetection({ page });
|
|
13
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the autocomplete audit (WCAG 1.3.5).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/autocomplete-audit";`
|
|
4
|
+
*/
|
|
5
|
+
import { test } from '@playwright/test';
|
|
6
|
+
import { runAutocompleteAudit } from '../playwright/runAutocompleteAudit.js';
|
|
7
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
8
|
+
test('autocomplete audit (WCAG 1.3.5)', async ({ page }) => {
|
|
9
|
+
await page.goto(requireTargetUrl(), { waitUntil: 'networkidle' });
|
|
10
|
+
await runAutocompleteAudit({ page });
|
|
11
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the orientation check (WCAG 1.3.4).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/orientation-check";`
|
|
4
|
+
*
|
|
5
|
+
* This check owns navigation (it loads the page at two viewports), so the
|
|
6
|
+
* target URL comes from `TEST_PAGE` via the function.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the orientation check (WCAG 1.3.4).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/orientation-check";`
|
|
4
|
+
*
|
|
5
|
+
* This check owns navigation (it loads the page at two viewports), so the
|
|
6
|
+
* target URL comes from `TEST_PAGE` via the function.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from '@playwright/test';
|
|
9
|
+
import { runOrientationCheck } from '../playwright/runOrientationCheck.js';
|
|
10
|
+
test('orientation check (WCAG 1.3.4)', async ({ page }) => {
|
|
11
|
+
await runOrientationCheck({ page, screenshot: true });
|
|
12
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the text spacing check (WCAG 1.4.12).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/text-spacing-check";`
|
|
4
|
+
*/
|
|
5
|
+
import { test } from '@playwright/test';
|
|
6
|
+
import { runTextSpacingCheck } from '../playwright/runTextSpacingCheck.js';
|
|
7
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
8
|
+
test('text spacing check (WCAG 1.4.12)', async ({ page }) => {
|
|
9
|
+
await page.goto(requireTargetUrl(), { waitUntil: 'networkidle' });
|
|
10
|
+
await runTextSpacingCheck({ page, screenshot: true });
|
|
11
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the time limit detector (WCAG 2.2.1).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/time-limit-detector";`
|
|
4
|
+
*
|
|
5
|
+
* This check installs a timer hook before navigation, so it owns navigation;
|
|
6
|
+
* the target URL comes from `TEST_PAGE` via the function.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the time limit detector (WCAG 2.2.1).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/time-limit-detector";`
|
|
4
|
+
*
|
|
5
|
+
* This check installs a timer hook before navigation, so it owns navigation;
|
|
6
|
+
* the target URL comes from `TEST_PAGE` via the function.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from '@playwright/test';
|
|
9
|
+
import { runTimeLimitDetector } from '../playwright/runTimeLimitDetector.js';
|
|
10
|
+
test('time limit detector (WCAG 2.2.1)', async ({ page }) => {
|
|
11
|
+
await runTimeLimitDetector({ page });
|
|
12
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the zoom 200% check (WCAG 1.4.4).
|
|
3
|
+
* Run from a one-line local spec: `import "@a11y-skills/audit/test-entries/zoom-200-check";`
|
|
4
|
+
*/
|
|
5
|
+
import { test } from '@playwright/test';
|
|
6
|
+
import { runZoomCheck } from '../playwright/runZoomCheck.js';
|
|
7
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
8
|
+
test('zoom 200% check (WCAG 1.4.4)', async ({ page }) => {
|
|
9
|
+
// Let runZoomCheck navigate so the base viewport is applied before load.
|
|
10
|
+
await runZoomCheck({ page, targetUrl: requireTargetUrl(), screenshot: true });
|
|
11
|
+
});
|
package/dist/types.d.ts
CHANGED
|
@@ -1,50 +1,117 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for the WCAG audit checks shipped in @a11y-skills/audit.
|
|
3
|
+
*
|
|
4
|
+
* Every check returns the same axe-style envelope (`AuditCheckResult`):
|
|
5
|
+
* findings are normalized into `violations` / `incomplete` / `passes` /
|
|
6
|
+
* `inapplicable` rule arrays, while the check-specific evidence (measurements,
|
|
7
|
+
* screenshots, raw element records) lives under `details`.
|
|
8
|
+
*
|
|
9
|
+
* Classification policy: a finding is only a `violation` when the detection
|
|
10
|
+
* has no known blind spots and no WCAG exception could apply. Everything else
|
|
11
|
+
* (most heuristic detections) lands in `incomplete` — the manual-review queue.
|
|
3
12
|
*/
|
|
4
13
|
import type { AUDIT_DISCLAIMER } from './constants.js';
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
/** Identifier of the check that produced a result. */
|
|
15
|
+
export type CheckSource = 'axe-audit' | 'focus-indicator-check' | 'reflow-check' | 'target-size-check' | 'text-spacing-check' | 'zoom-200-check' | 'orientation-check' | 'autocomplete-audit' | 'time-limit-detector' | 'auto-play-detection';
|
|
16
|
+
export type NormalizedImpact = 'critical' | 'serious' | 'moderate' | 'minor';
|
|
17
|
+
/** One affected element (or the page itself, `target: ['html']`). */
|
|
18
|
+
export interface NormalizedNode {
|
|
19
|
+
/** CSS selector path. Page-level findings use `['html']`. */
|
|
7
20
|
target: string[];
|
|
8
|
-
|
|
21
|
+
/**
|
|
22
|
+
* outerHTML snippet (possibly truncated). When the source element's HTML
|
|
23
|
+
* could not be captured, a short synthetic representation is generated from
|
|
24
|
+
* the tag/role/name instead — never an empty string.
|
|
25
|
+
*/
|
|
26
|
+
html: string;
|
|
27
|
+
/** Whether `html` was truncated to the snippet length limit. */
|
|
28
|
+
htmlTruncated: boolean;
|
|
29
|
+
/** Human-readable description of why this node was flagged. */
|
|
30
|
+
failureSummary: string;
|
|
9
31
|
}
|
|
10
|
-
|
|
32
|
+
/** One rule's outcome, axe-style. */
|
|
33
|
+
export interface NormalizedRuleResult {
|
|
34
|
+
/** Namespaced rule id, e.g. `a11y-skills/focus-visible` (axe rules keep their own ids). */
|
|
11
35
|
id: string;
|
|
12
|
-
impact:
|
|
36
|
+
impact: NormalizedImpact | null;
|
|
13
37
|
description: string;
|
|
14
38
|
help: string;
|
|
39
|
+
/** W3C Understanding document (or axe docs for axe rules). */
|
|
15
40
|
helpUrl: string;
|
|
41
|
+
/** axe-style tags: `a11y-skills`, `wcag2aa` / `wcag21aa` / `wcag22aa`, `wcag247`-style SC tags. */
|
|
16
42
|
tags: string[];
|
|
17
|
-
nodes:
|
|
43
|
+
nodes: NormalizedNode[];
|
|
44
|
+
}
|
|
45
|
+
/** Rule-level counts derived from the four buckets (not from `details`). */
|
|
46
|
+
export interface AuditResultSummary {
|
|
47
|
+
/** Number of rules in `violations`. */
|
|
48
|
+
violationCount: number;
|
|
49
|
+
/** Number of rules in `incomplete`. */
|
|
50
|
+
incompleteCount: number;
|
|
51
|
+
/** Number of rules in `passes`. */
|
|
52
|
+
passCount: number;
|
|
53
|
+
/** Number of elements the check examined, when the check can count them. */
|
|
54
|
+
checkedNodes?: number;
|
|
18
55
|
}
|
|
19
|
-
|
|
56
|
+
/** Common envelope returned (and saved as JSON) by every check. */
|
|
57
|
+
export interface AuditCheckResult<TDetails> {
|
|
58
|
+
source: CheckSource;
|
|
20
59
|
url: string;
|
|
21
60
|
timestamp: string;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
/** Confirmed findings — detection has no blind spot and no exception can apply. */
|
|
62
|
+
violations: NormalizedRuleResult[];
|
|
63
|
+
/** Findings that need manual confirmation (heuristic detections, possible exceptions). */
|
|
64
|
+
incomplete: NormalizedRuleResult[];
|
|
65
|
+
/** Rules that ran and found nothing (nodes omitted). */
|
|
66
|
+
passes: NormalizedRuleResult[];
|
|
67
|
+
/** Rules that had nothing to examine on this page. */
|
|
68
|
+
inapplicable: NormalizedRuleResult[];
|
|
69
|
+
summary: AuditResultSummary;
|
|
70
|
+
/** Check-specific evidence; sufficient to re-derive the buckets above. */
|
|
71
|
+
details: TDetails;
|
|
27
72
|
disclaimer: typeof AUDIT_DISCLAIMER;
|
|
28
73
|
}
|
|
74
|
+
/** Execution configuration; rule/node data is fully held in the envelope buckets. */
|
|
75
|
+
export interface AxeAuditDetails {
|
|
76
|
+
/** axe-core tags the run was filtered by. */
|
|
77
|
+
tagsRun: string[];
|
|
78
|
+
/** Rule overrides forwarded to axe, if any. */
|
|
79
|
+
rulesOverride: Record<string, {
|
|
80
|
+
enabled: boolean;
|
|
81
|
+
}> | null;
|
|
82
|
+
/** Raw axe result counts (rule-level). */
|
|
83
|
+
violationRuleCount: number;
|
|
84
|
+
passRuleCount: number;
|
|
85
|
+
incompleteRuleCount: number;
|
|
86
|
+
inapplicableRuleCount: number;
|
|
87
|
+
}
|
|
88
|
+
export type AxeAuditResult = AuditCheckResult<AxeAuditDetails>;
|
|
29
89
|
export interface FocusRecord {
|
|
30
90
|
id: number;
|
|
31
91
|
tag: string;
|
|
32
92
|
role: string | null;
|
|
33
93
|
name: string;
|
|
94
|
+
selector: string;
|
|
95
|
+
html: string;
|
|
96
|
+
htmlTruncated: boolean;
|
|
34
97
|
hasFocusStyle: boolean;
|
|
35
98
|
diff: Record<string, string>;
|
|
36
99
|
}
|
|
100
|
+
/** Reference to an element captured in the browser context. */
|
|
101
|
+
export interface FocusElementRef {
|
|
102
|
+
tag: string;
|
|
103
|
+
role: string | null;
|
|
104
|
+
name: string;
|
|
105
|
+
selector: string;
|
|
106
|
+
html: string;
|
|
107
|
+
htmlTruncated: boolean;
|
|
108
|
+
}
|
|
37
109
|
/**
|
|
38
110
|
* WCAG 3.2.1 On Focus violation - context change triggered by focus
|
|
39
111
|
*/
|
|
40
112
|
export interface OnFocusViolation {
|
|
41
113
|
/** Element that triggered the navigation */
|
|
42
|
-
element:
|
|
43
|
-
tag: string;
|
|
44
|
-
role: string | null;
|
|
45
|
-
name: string;
|
|
46
|
-
selector: string;
|
|
47
|
-
};
|
|
114
|
+
element: FocusElementRef;
|
|
48
115
|
/** URL before focus */
|
|
49
116
|
fromUrl: string;
|
|
50
117
|
/** URL after focus (navigation target) */
|
|
@@ -52,20 +119,15 @@ export interface OnFocusViolation {
|
|
|
52
119
|
/** Type of context change */
|
|
53
120
|
changeType: 'navigation' | 'new-window' | 'dialog';
|
|
54
121
|
}
|
|
55
|
-
export interface
|
|
56
|
-
url: string;
|
|
122
|
+
export interface FocusCheckDetails {
|
|
57
123
|
totalFocusableElements: number;
|
|
58
124
|
elementsWithFocusStyle: number;
|
|
59
125
|
elementsWithoutFocusStyle: number;
|
|
60
|
-
/** WCAG 2.4.7
|
|
61
|
-
issues:
|
|
62
|
-
tag: string;
|
|
63
|
-
role: string | null;
|
|
64
|
-
name: string;
|
|
65
|
-
}>;
|
|
126
|
+
/** WCAG 2.4.7 findings (no computed-style change on focus) */
|
|
127
|
+
issues: FocusElementRef[];
|
|
66
128
|
/** WCAG 3.2.1 violations - focus triggered context change */
|
|
67
129
|
onFocusViolations: OnFocusViolation[];
|
|
68
|
-
/** WCAG 2.4.12
|
|
130
|
+
/** WCAG 2.4.11/2.4.12 findings - focus obscured by fixed/sticky elements */
|
|
69
131
|
focusObscuredIssues: FocusObscuredIssue[];
|
|
70
132
|
elementsWithObscuredFocus: number;
|
|
71
133
|
allElements: FocusRecord[];
|
|
@@ -78,6 +140,7 @@ export interface FocusCheckResult {
|
|
|
78
140
|
*/
|
|
79
141
|
screenshotPath: string;
|
|
80
142
|
}
|
|
143
|
+
export type FocusCheckResult = AuditCheckResult<FocusCheckDetails>;
|
|
81
144
|
/**
|
|
82
145
|
* Bounding rect for overlap calculations
|
|
83
146
|
*/
|
|
@@ -104,16 +167,11 @@ export interface FocusObscuredOverlap {
|
|
|
104
167
|
overlapArea: number;
|
|
105
168
|
}
|
|
106
169
|
/**
|
|
107
|
-
* WCAG 2.4.12
|
|
170
|
+
* WCAG 2.4.11/2.4.12 finding - focus indicator hidden by fixed/sticky content
|
|
108
171
|
*/
|
|
109
172
|
export interface FocusObscuredIssue {
|
|
110
173
|
/** The focused element that is obscured */
|
|
111
|
-
element:
|
|
112
|
-
tag: string;
|
|
113
|
-
role: string | null;
|
|
114
|
-
name: string;
|
|
115
|
-
selector: string;
|
|
116
|
-
};
|
|
174
|
+
element: FocusElementRef;
|
|
117
175
|
/** Bounding rect of the focused element */
|
|
118
176
|
elementRect: BoundingRect;
|
|
119
177
|
/** List of overlapping fixed/sticky elements */
|
|
@@ -124,6 +182,8 @@ export interface FocusObscuredIssue {
|
|
|
124
182
|
export interface ReflowIssue {
|
|
125
183
|
selector: string;
|
|
126
184
|
tagName: string;
|
|
185
|
+
html: string;
|
|
186
|
+
htmlTruncated: boolean;
|
|
127
187
|
rect: {
|
|
128
188
|
left: number;
|
|
129
189
|
right: number;
|
|
@@ -135,6 +195,8 @@ export interface ReflowIssue {
|
|
|
135
195
|
export interface ClippedTextElement {
|
|
136
196
|
selector: string;
|
|
137
197
|
tagName: string;
|
|
198
|
+
html: string;
|
|
199
|
+
htmlTruncated: boolean;
|
|
138
200
|
scrollWidth: number;
|
|
139
201
|
clientWidth: number;
|
|
140
202
|
scrollHeight: number;
|
|
@@ -142,8 +204,7 @@ export interface ClippedTextElement {
|
|
|
142
204
|
overflow: string;
|
|
143
205
|
overflowX: string;
|
|
144
206
|
}
|
|
145
|
-
export interface
|
|
146
|
-
url: string;
|
|
207
|
+
export interface ReflowCheckDetails {
|
|
147
208
|
viewport: {
|
|
148
209
|
width: number;
|
|
149
210
|
height: number;
|
|
@@ -154,6 +215,7 @@ export interface ReflowCheckResult {
|
|
|
154
215
|
overflowingElements: ReflowIssue[];
|
|
155
216
|
clippedTextElements: ClippedTextElement[];
|
|
156
217
|
}
|
|
218
|
+
export type ReflowCheckResult = AuditCheckResult<ReflowCheckDetails>;
|
|
157
219
|
/**
|
|
158
220
|
* Exception types for WCAG 2.5.8 Target Size (Minimum)
|
|
159
221
|
* - inline: Target is in a sentence or text block
|
|
@@ -163,11 +225,22 @@ export interface ReflowCheckResult {
|
|
|
163
225
|
* - essential-review: May be essential exception but requires manual review
|
|
164
226
|
*/
|
|
165
227
|
export type TargetSizeException = 'inline' | 'redundant' | 'ua-control' | 'spacing' | 'essential-review';
|
|
228
|
+
/**
|
|
229
|
+
* How thoroughly the SC 2.5.8 exceptions were assessed for a target.
|
|
230
|
+
* - ruled-out: every exception was checked and none applies — the finding is a
|
|
231
|
+
* confirmed violation
|
|
232
|
+
* - possible: a heuristic matched an exception; needs manual confirmation
|
|
233
|
+
* - not-assessed: the heuristics found no exception, but they cannot rule out
|
|
234
|
+
* the essential exception — needs manual confirmation
|
|
235
|
+
*/
|
|
236
|
+
export type TargetSizeExceptionAssessment = 'ruled-out' | 'possible' | 'not-assessed';
|
|
166
237
|
export interface TargetSizeIssue {
|
|
167
238
|
/** CSS selector for the element */
|
|
168
239
|
selector: string;
|
|
169
240
|
/** HTML tag name */
|
|
170
241
|
tagName: string;
|
|
242
|
+
html: string;
|
|
243
|
+
htmlTruncated: boolean;
|
|
171
244
|
/** ARIA role if present */
|
|
172
245
|
role: string | null;
|
|
173
246
|
/** Computed accessible name */
|
|
@@ -184,6 +257,8 @@ export interface TargetSizeIssue {
|
|
|
184
257
|
exception: TargetSizeException | null;
|
|
185
258
|
/** Human-readable exception details */
|
|
186
259
|
exceptionDetails: string | null;
|
|
260
|
+
/** Exception-coverage of the assessment (drives violation vs incomplete) */
|
|
261
|
+
exceptionAssessment: TargetSizeExceptionAssessment;
|
|
187
262
|
/** Link href for redundancy check */
|
|
188
263
|
href: string | null;
|
|
189
264
|
}
|
|
@@ -197,9 +272,7 @@ export interface TargetSizeSummary {
|
|
|
197
272
|
/** Number of targets with possible exceptions */
|
|
198
273
|
exceptedCount: number;
|
|
199
274
|
}
|
|
200
|
-
export interface
|
|
201
|
-
/** Page URL */
|
|
202
|
-
url: string;
|
|
275
|
+
export interface TargetSizeCheckDetails {
|
|
203
276
|
/** Total interactive elements checked */
|
|
204
277
|
totalTargetsChecked: number;
|
|
205
278
|
/** Elements failing AA threshold (< 24px) */
|
|
@@ -210,6 +283,168 @@ export interface TargetSizeCheckResult {
|
|
|
210
283
|
passedTargets: number;
|
|
211
284
|
/** Elements with possible exceptions */
|
|
212
285
|
exceptedTargets: TargetSizeIssue[];
|
|
213
|
-
/**
|
|
286
|
+
/** Per-target counts */
|
|
214
287
|
summary: TargetSizeSummary;
|
|
215
288
|
}
|
|
289
|
+
export type TargetSizeCheckResult = AuditCheckResult<TargetSizeCheckDetails>;
|
|
290
|
+
export interface TextSpacingIssue {
|
|
291
|
+
selector: string;
|
|
292
|
+
tagName: string;
|
|
293
|
+
html: string;
|
|
294
|
+
htmlTruncated: boolean;
|
|
295
|
+
beforeMetrics: {
|
|
296
|
+
scrollWidth: number;
|
|
297
|
+
scrollHeight: number;
|
|
298
|
+
clientWidth: number;
|
|
299
|
+
clientHeight: number;
|
|
300
|
+
};
|
|
301
|
+
afterMetrics: {
|
|
302
|
+
scrollWidth: number;
|
|
303
|
+
scrollHeight: number;
|
|
304
|
+
clientWidth: number;
|
|
305
|
+
clientHeight: number;
|
|
306
|
+
};
|
|
307
|
+
overflow: string;
|
|
308
|
+
overflowX: string;
|
|
309
|
+
overflowY: string;
|
|
310
|
+
issueType: 'horizontal-clip' | 'vertical-clip' | 'both';
|
|
311
|
+
}
|
|
312
|
+
export interface TextSpacingCheckDetails {
|
|
313
|
+
clippedElements: TextSpacingIssue[];
|
|
314
|
+
totalElementsChecked: number;
|
|
315
|
+
}
|
|
316
|
+
export type TextSpacingCheckResult = AuditCheckResult<TextSpacingCheckDetails>;
|
|
317
|
+
export interface ZoomIssue {
|
|
318
|
+
selector: string;
|
|
319
|
+
tagName: string;
|
|
320
|
+
html: string;
|
|
321
|
+
htmlTruncated: boolean;
|
|
322
|
+
scrollWidth: number;
|
|
323
|
+
clientWidth: number;
|
|
324
|
+
scrollHeight: number;
|
|
325
|
+
clientHeight: number;
|
|
326
|
+
issueType: 'horizontal-scroll' | 'clipped-content';
|
|
327
|
+
}
|
|
328
|
+
export interface ZoomCheckDetails {
|
|
329
|
+
zoomFactor: number;
|
|
330
|
+
viewport: {
|
|
331
|
+
width: number;
|
|
332
|
+
height: number;
|
|
333
|
+
};
|
|
334
|
+
hasHorizontalScroll: boolean;
|
|
335
|
+
documentScrollWidth: number;
|
|
336
|
+
documentClientWidth: number;
|
|
337
|
+
clippedElements: ZoomIssue[];
|
|
338
|
+
}
|
|
339
|
+
export type ZoomCheckResult = AuditCheckResult<ZoomCheckDetails>;
|
|
340
|
+
export interface OrientationState {
|
|
341
|
+
lockMessageFound: boolean;
|
|
342
|
+
lockMessageText: string | null;
|
|
343
|
+
mainContentHidden: boolean;
|
|
344
|
+
bodyWidth: number;
|
|
345
|
+
bodyHeight: number;
|
|
346
|
+
visibleTextLength: number;
|
|
347
|
+
}
|
|
348
|
+
export interface OrientationCheckDetails {
|
|
349
|
+
portrait: OrientationState;
|
|
350
|
+
landscape: OrientationState;
|
|
351
|
+
hasOrientationLock: boolean;
|
|
352
|
+
lockDetectedIn: 'portrait' | 'landscape' | 'both' | 'none';
|
|
353
|
+
}
|
|
354
|
+
export type OrientationCheckResult = AuditCheckResult<OrientationCheckDetails>;
|
|
355
|
+
export interface AutocompleteIssue {
|
|
356
|
+
selector: string;
|
|
357
|
+
tagName: string;
|
|
358
|
+
html: string;
|
|
359
|
+
htmlTruncated: boolean;
|
|
360
|
+
inputType: string;
|
|
361
|
+
name: string | null;
|
|
362
|
+
id: string | null;
|
|
363
|
+
labelText: string | null;
|
|
364
|
+
currentAutocomplete: string | null;
|
|
365
|
+
expectedToken: string;
|
|
366
|
+
matchedBy: 'name' | 'id' | 'label' | 'placeholder';
|
|
367
|
+
issueType: 'missing' | 'invalid';
|
|
368
|
+
}
|
|
369
|
+
export interface AutocompleteAuditDetails {
|
|
370
|
+
totalFieldsChecked: number;
|
|
371
|
+
missingAutocomplete: AutocompleteIssue[];
|
|
372
|
+
invalidAutocomplete: AutocompleteIssue[];
|
|
373
|
+
}
|
|
374
|
+
export type AutocompleteAuditResult = AuditCheckResult<AutocompleteAuditDetails>;
|
|
375
|
+
export interface MetaRefreshInfo {
|
|
376
|
+
content: string;
|
|
377
|
+
seconds: number;
|
|
378
|
+
url: string | null;
|
|
379
|
+
html: string;
|
|
380
|
+
htmlTruncated: boolean;
|
|
381
|
+
}
|
|
382
|
+
export interface TimerInfo {
|
|
383
|
+
type: 'setTimeout' | 'setInterval';
|
|
384
|
+
delayMs: number;
|
|
385
|
+
callStack: string | null;
|
|
386
|
+
}
|
|
387
|
+
export interface CountdownIndicator {
|
|
388
|
+
selector: string;
|
|
389
|
+
text: string;
|
|
390
|
+
tagName: string;
|
|
391
|
+
html: string;
|
|
392
|
+
htmlTruncated: boolean;
|
|
393
|
+
}
|
|
394
|
+
export interface TimeLimitDetectorDetails {
|
|
395
|
+
metaRefresh: MetaRefreshInfo[];
|
|
396
|
+
timers: TimerInfo[];
|
|
397
|
+
countdownIndicators: CountdownIndicator[];
|
|
398
|
+
hasTimeLimits: boolean;
|
|
399
|
+
}
|
|
400
|
+
export type TimeLimitDetectorResult = AuditCheckResult<TimeLimitDetectorDetails>;
|
|
401
|
+
export interface ScreenshotRecord {
|
|
402
|
+
time: string;
|
|
403
|
+
path: string;
|
|
404
|
+
}
|
|
405
|
+
export interface ComparisonResult {
|
|
406
|
+
compare: string;
|
|
407
|
+
diffPixels: number;
|
|
408
|
+
totalPixels: number;
|
|
409
|
+
diffPercent: string;
|
|
410
|
+
hasChange: boolean;
|
|
411
|
+
}
|
|
412
|
+
export interface ImageDiffResult {
|
|
413
|
+
diffPixels: number;
|
|
414
|
+
totalPixels: number;
|
|
415
|
+
diffPercent: number;
|
|
416
|
+
}
|
|
417
|
+
export interface PauseControl {
|
|
418
|
+
element: string;
|
|
419
|
+
name: string;
|
|
420
|
+
matchedBy: 'accessible-name' | 'class-name-near-carousel' | 'svg-icon-pattern';
|
|
421
|
+
selector: string;
|
|
422
|
+
}
|
|
423
|
+
export interface CarouselIndicator {
|
|
424
|
+
element: string;
|
|
425
|
+
name: string;
|
|
426
|
+
}
|
|
427
|
+
export interface PauseControlInfo {
|
|
428
|
+
found: boolean;
|
|
429
|
+
controls: PauseControl[];
|
|
430
|
+
carouselIndicators: CarouselIndicator[];
|
|
431
|
+
hasAccessibleName: boolean;
|
|
432
|
+
}
|
|
433
|
+
export interface PauseVerificationResult {
|
|
434
|
+
attempted: boolean;
|
|
435
|
+
controlClicked: string | null;
|
|
436
|
+
beforeClickDiffPercent: string | null;
|
|
437
|
+
afterClickDiffPercent: string | null;
|
|
438
|
+
pauseWorked: boolean | null;
|
|
439
|
+
error: string | null;
|
|
440
|
+
}
|
|
441
|
+
export interface AutoPlayDetectionDetails {
|
|
442
|
+
screenshotRecords: ScreenshotRecord[];
|
|
443
|
+
comparisons: ComparisonResult[];
|
|
444
|
+
hasAutoPlayContent: boolean;
|
|
445
|
+
stopsWithin5Seconds: boolean;
|
|
446
|
+
pauseControls: PauseControlInfo;
|
|
447
|
+
pauseVerification: PauseVerificationResult;
|
|
448
|
+
recommendation: string;
|
|
449
|
+
}
|
|
450
|
+
export type AutoPlayDetectionResult = AuditCheckResult<AutoPlayDetectionDetails>;
|
package/dist/types.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for the WCAG audit checks shipped in @a11y-skills/audit.
|
|
3
|
+
*
|
|
4
|
+
* Every check returns the same axe-style envelope (`AuditCheckResult`):
|
|
5
|
+
* findings are normalized into `violations` / `incomplete` / `passes` /
|
|
6
|
+
* `inapplicable` rule arrays, while the check-specific evidence (measurements,
|
|
7
|
+
* screenshots, raw element records) lives under `details`.
|
|
8
|
+
*
|
|
9
|
+
* Classification policy: a finding is only a `violation` when the detection
|
|
10
|
+
* has no known blind spots and no WCAG exception could apply. Everything else
|
|
11
|
+
* (most heuristic detections) lands in `incomplete` — the manual-review queue.
|
|
3
12
|
*/
|
|
4
13
|
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure mappers from check-specific `details` to the axe-style buckets
|
|
3
|
+
* (`violations` / `incomplete` / `passes` / `inapplicable`), plus the envelope
|
|
4
|
+
* assembler and the opt-in cross-check merge.
|
|
5
|
+
*
|
|
6
|
+
* Everything here is browser-independent: the runners gather evidence into a
|
|
7
|
+
* details object, and these functions derive the normalized view from it.
|
|
8
|
+
* Because the details objects are part of the saved JSON, the buckets can be
|
|
9
|
+
* re-derived from a result file at any time.
|
|
10
|
+
*/
|
|
11
|
+
import type { AuditCheckResult, AuditResultSummary, AutocompleteAuditDetails, AutoPlayDetectionDetails, CheckSource, FocusCheckDetails, NormalizedRuleResult, OrientationCheckDetails, ReflowCheckDetails, TargetSizeCheckDetails, TextSpacingCheckDetails, TimeLimitDetectorDetails, ZoomCheckDetails } from '../types.js';
|
|
12
|
+
import { AUDIT_DISCLAIMER } from '../constants.js';
|
|
13
|
+
export interface NormalizedBuckets {
|
|
14
|
+
violations: NormalizedRuleResult[];
|
|
15
|
+
incomplete: NormalizedRuleResult[];
|
|
16
|
+
passes: NormalizedRuleResult[];
|
|
17
|
+
inapplicable: NormalizedRuleResult[];
|
|
18
|
+
/** Number of elements the check examined, when countable. */
|
|
19
|
+
checkedNodes?: number;
|
|
20
|
+
}
|
|
21
|
+
/** Assemble the common envelope from a check's details and buckets. */
|
|
22
|
+
export declare function buildAuditResult<TDetails>(args: {
|
|
23
|
+
source: CheckSource;
|
|
24
|
+
url: string;
|
|
25
|
+
details: TDetails;
|
|
26
|
+
buckets: NormalizedBuckets;
|
|
27
|
+
timestamp?: string;
|
|
28
|
+
}): AuditCheckResult<TDetails>;
|
|
29
|
+
export declare function normalizeFocusCheck(details: FocusCheckDetails): NormalizedBuckets;
|
|
30
|
+
export declare function normalizeReflowCheck(details: ReflowCheckDetails): NormalizedBuckets;
|
|
31
|
+
export declare function normalizeTargetSizeCheck(details: TargetSizeCheckDetails): NormalizedBuckets;
|
|
32
|
+
export declare function normalizeTextSpacingCheck(details: TextSpacingCheckDetails): NormalizedBuckets;
|
|
33
|
+
export declare function normalizeZoomCheck(details: ZoomCheckDetails): NormalizedBuckets;
|
|
34
|
+
export declare function normalizeOrientationCheck(details: OrientationCheckDetails): NormalizedBuckets;
|
|
35
|
+
export declare function normalizeAutocompleteAudit(details: AutocompleteAuditDetails): NormalizedBuckets;
|
|
36
|
+
export declare function normalizeTimeLimitDetector(details: TimeLimitDetectorDetails): NormalizedBuckets;
|
|
37
|
+
export declare function normalizeAutoPlayDetection(details: AutoPlayDetectionDetails): NormalizedBuckets;
|
|
38
|
+
/** Structural subset of an axe-core rule result (avoids a hard axe dependency). */
|
|
39
|
+
export interface RawAxeRule {
|
|
40
|
+
id: string;
|
|
41
|
+
impact?: string | null;
|
|
42
|
+
description: string;
|
|
43
|
+
help: string;
|
|
44
|
+
helpUrl: string;
|
|
45
|
+
tags: string[];
|
|
46
|
+
nodes: Array<{
|
|
47
|
+
html: string;
|
|
48
|
+
target: unknown[];
|
|
49
|
+
failureSummary?: string | undefined;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
export interface RawAxeResults {
|
|
53
|
+
violations: RawAxeRule[];
|
|
54
|
+
incomplete: RawAxeRule[];
|
|
55
|
+
passes: RawAxeRule[];
|
|
56
|
+
inapplicable: RawAxeRule[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Normalize raw axe-core results into the common buckets. Must be fed the RAW
|
|
60
|
+
* `AxeResults` (before any reduction) — pass/incomplete details are not
|
|
61
|
+
* recoverable afterwards. Node lists are kept for violations and incomplete;
|
|
62
|
+
* passes/inapplicable carry rule metadata only.
|
|
63
|
+
*/
|
|
64
|
+
export declare function normalizeAxeResults(raw: RawAxeResults): NormalizedBuckets;
|
|
65
|
+
/** Combined view over several checks of the SAME page. */
|
|
66
|
+
export interface MergedAuditResult {
|
|
67
|
+
url: string;
|
|
68
|
+
/** Latest timestamp among the merged results. */
|
|
69
|
+
timestamp: string;
|
|
70
|
+
sources: CheckSource[];
|
|
71
|
+
violations: NormalizedRuleResult[];
|
|
72
|
+
incomplete: NormalizedRuleResult[];
|
|
73
|
+
passes: NormalizedRuleResult[];
|
|
74
|
+
inapplicable: NormalizedRuleResult[];
|
|
75
|
+
summary: AuditResultSummary;
|
|
76
|
+
disclaimer: typeof AUDIT_DISCLAIMER;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Merge several check results for the same URL into one normalized view.
|
|
80
|
+
*
|
|
81
|
+
* - Results with differing URLs are rejected (throws).
|
|
82
|
+
* - The same rule id is merged into one entry; nodes are deduplicated by
|
|
83
|
+
* `target` + `failureSummary`. Identical selectors inside different frames
|
|
84
|
+
* or shadow roots are NOT distinguished.
|
|
85
|
+
* - A rule appearing in several buckets is placed in the highest-priority one:
|
|
86
|
+
* violations > incomplete > passes > inapplicable.
|
|
87
|
+
*/
|
|
88
|
+
export declare function mergeNormalizedResults(results: Array<AuditCheckResult<unknown>>): MergedAuditResult;
|