@masup9/a11y-audit 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.
- package/CHANGELOG.md +34 -0
- package/README.ja.md +128 -0
- package/README.md +130 -0
- package/dist/constants.d.ts +88 -0
- package/dist/constants.js +184 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +13 -0
- package/dist/playwright/index.d.ts +19 -0
- package/dist/playwright/index.js +19 -0
- package/dist/playwright/runAxeAudit.d.ts +28 -0
- package/dist/playwright/runAxeAudit.js +89 -0
- package/dist/playwright/runFocusIndicatorCheck.d.ts +30 -0
- package/dist/playwright/runFocusIndicatorCheck.js +740 -0
- package/dist/playwright/runReflowCheck.d.ts +36 -0
- package/dist/playwright/runReflowCheck.js +68 -0
- package/dist/playwright/runTargetSizeCheck.d.ts +35 -0
- package/dist/playwright/runTargetSizeCheck.js +389 -0
- package/dist/schemas/index.d.ts +34 -0
- package/dist/schemas/index.js +245 -0
- package/dist/test-entries/axe-audit.d.ts +13 -0
- package/dist/test-entries/axe-audit.js +20 -0
- package/dist/test-entries/focus-indicator-check.d.ts +9 -0
- package/dist/test-entries/focus-indicator-check.js +13 -0
- package/dist/test-entries/reflow-check.d.ts +9 -0
- package/dist/test-entries/reflow-check.js +21 -0
- package/dist/test-entries/target-size-check.d.ts +8 -0
- package/dist/test-entries/target-size-check.js +15 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.js +4 -0
- package/dist/utils/annotations.d.ts +67 -0
- package/dist/utils/annotations.js +110 -0
- package/dist/utils/layout.d.ts +23 -0
- package/dist/utils/layout.js +144 -0
- package/dist/utils/test-harness.d.ts +76 -0
- package/dist/utils/test-harness.js +119 -0
- package/package.json +92 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result types and JSON Schemas for the audit checks.
|
|
3
|
+
*
|
|
4
|
+
* The TypeScript types are the source of truth; the JSON Schemas are
|
|
5
|
+
* hand-written for consumers that validate the result files at runtime
|
|
6
|
+
* (e.g. an issue creator that reads `*-result.json`). They are intentionally
|
|
7
|
+
* permissive (no `additionalProperties: false`) so that additive changes to a
|
|
8
|
+
* result shape do not break downstream validation.
|
|
9
|
+
*/
|
|
10
|
+
const DISCLAIMER_SCHEMA = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
message: { type: 'string' },
|
|
14
|
+
messageEn: { type: 'string' },
|
|
15
|
+
coverage: { type: 'string' },
|
|
16
|
+
moreInfo: { type: 'string' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
export const AXE_AUDIT_RESULT_SCHEMA = {
|
|
20
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
21
|
+
$id: 'https://masup9.github.io/a11y-audit/schemas/axe-audit-result.json',
|
|
22
|
+
title: 'AxeAuditResult',
|
|
23
|
+
type: 'object',
|
|
24
|
+
required: [
|
|
25
|
+
'url',
|
|
26
|
+
'timestamp',
|
|
27
|
+
'violations',
|
|
28
|
+
'passes',
|
|
29
|
+
'incomplete',
|
|
30
|
+
'inapplicable',
|
|
31
|
+
'violationCount',
|
|
32
|
+
],
|
|
33
|
+
properties: {
|
|
34
|
+
url: { type: 'string' },
|
|
35
|
+
timestamp: { type: 'string' },
|
|
36
|
+
violations: {
|
|
37
|
+
type: 'array',
|
|
38
|
+
items: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
required: ['id', 'description', 'help', 'helpUrl', 'tags', 'nodes'],
|
|
41
|
+
properties: {
|
|
42
|
+
id: { type: 'string' },
|
|
43
|
+
impact: { type: ['string', 'null'] },
|
|
44
|
+
description: { type: 'string' },
|
|
45
|
+
help: { type: 'string' },
|
|
46
|
+
helpUrl: { type: 'string' },
|
|
47
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
48
|
+
nodes: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
required: ['html', 'target'],
|
|
53
|
+
properties: {
|
|
54
|
+
html: { type: 'string' },
|
|
55
|
+
target: { type: 'array', items: { type: 'string' } },
|
|
56
|
+
failureSummary: { type: ['string', 'null'] },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
passes: { type: 'number' },
|
|
64
|
+
incomplete: { type: 'number' },
|
|
65
|
+
inapplicable: { type: 'number' },
|
|
66
|
+
violationCount: { type: 'number' },
|
|
67
|
+
disclaimer: DISCLAIMER_SCHEMA,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const FOCUS_ELEMENT_REF_SCHEMA = {
|
|
71
|
+
type: 'object',
|
|
72
|
+
required: ['tag', 'name', 'selector'],
|
|
73
|
+
properties: {
|
|
74
|
+
tag: { type: 'string' },
|
|
75
|
+
role: { type: ['string', 'null'] },
|
|
76
|
+
name: { type: 'string' },
|
|
77
|
+
selector: { type: 'string' },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
export const FOCUS_CHECK_RESULT_SCHEMA = {
|
|
81
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
82
|
+
$id: 'https://masup9.github.io/a11y-audit/schemas/focus-check-result.json',
|
|
83
|
+
title: 'FocusCheckResult',
|
|
84
|
+
type: 'object',
|
|
85
|
+
required: [
|
|
86
|
+
'url',
|
|
87
|
+
'totalFocusableElements',
|
|
88
|
+
'elementsWithFocusStyle',
|
|
89
|
+
'elementsWithoutFocusStyle',
|
|
90
|
+
'issues',
|
|
91
|
+
'onFocusViolations',
|
|
92
|
+
'focusObscuredIssues',
|
|
93
|
+
'elementsWithObscuredFocus',
|
|
94
|
+
'allElements',
|
|
95
|
+
'interrupted',
|
|
96
|
+
'screenshotPath',
|
|
97
|
+
],
|
|
98
|
+
properties: {
|
|
99
|
+
url: { type: 'string' },
|
|
100
|
+
totalFocusableElements: { type: 'number' },
|
|
101
|
+
elementsWithFocusStyle: { type: 'number' },
|
|
102
|
+
elementsWithoutFocusStyle: { type: 'number' },
|
|
103
|
+
issues: {
|
|
104
|
+
type: 'array',
|
|
105
|
+
items: {
|
|
106
|
+
type: 'object',
|
|
107
|
+
required: ['tag', 'name'],
|
|
108
|
+
properties: {
|
|
109
|
+
tag: { type: 'string' },
|
|
110
|
+
role: { type: ['string', 'null'] },
|
|
111
|
+
name: { type: 'string' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
onFocusViolations: {
|
|
116
|
+
type: 'array',
|
|
117
|
+
items: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
required: ['element', 'fromUrl', 'toUrl', 'changeType'],
|
|
120
|
+
properties: {
|
|
121
|
+
element: FOCUS_ELEMENT_REF_SCHEMA,
|
|
122
|
+
fromUrl: { type: 'string' },
|
|
123
|
+
toUrl: { type: 'string' },
|
|
124
|
+
changeType: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
enum: ['navigation', 'new-window', 'dialog'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
focusObscuredIssues: {
|
|
132
|
+
type: 'array',
|
|
133
|
+
items: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
required: ['element', 'elementRect', 'overlaps', 'obscuredRatio'],
|
|
136
|
+
properties: {
|
|
137
|
+
element: FOCUS_ELEMENT_REF_SCHEMA,
|
|
138
|
+
elementRect: { type: 'object' },
|
|
139
|
+
overlaps: { type: 'array', items: { type: 'object' } },
|
|
140
|
+
obscuredRatio: { type: 'number' },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
elementsWithObscuredFocus: { type: 'number' },
|
|
145
|
+
allElements: { type: 'array', items: { type: 'object' } },
|
|
146
|
+
interrupted: { type: 'boolean' },
|
|
147
|
+
interruptedAt: { type: 'number' },
|
|
148
|
+
screenshotPath: { type: 'string' },
|
|
149
|
+
disclaimer: DISCLAIMER_SCHEMA,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
export const REFLOW_CHECK_RESULT_SCHEMA = {
|
|
153
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
154
|
+
$id: 'https://masup9.github.io/a11y-audit/schemas/reflow-check-result.json',
|
|
155
|
+
title: 'ReflowCheckResult',
|
|
156
|
+
type: 'object',
|
|
157
|
+
required: [
|
|
158
|
+
'url',
|
|
159
|
+
'viewport',
|
|
160
|
+
'hasHorizontalScroll',
|
|
161
|
+
'documentScrollWidth',
|
|
162
|
+
'documentClientWidth',
|
|
163
|
+
'overflowingElements',
|
|
164
|
+
'clippedTextElements',
|
|
165
|
+
],
|
|
166
|
+
properties: {
|
|
167
|
+
url: { type: 'string' },
|
|
168
|
+
viewport: {
|
|
169
|
+
type: 'object',
|
|
170
|
+
required: ['width', 'height'],
|
|
171
|
+
properties: {
|
|
172
|
+
width: { type: 'number' },
|
|
173
|
+
height: { type: 'number' },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
hasHorizontalScroll: { type: 'boolean' },
|
|
177
|
+
documentScrollWidth: { type: 'number' },
|
|
178
|
+
documentClientWidth: { type: 'number' },
|
|
179
|
+
overflowingElements: {
|
|
180
|
+
type: 'array',
|
|
181
|
+
items: {
|
|
182
|
+
type: 'object',
|
|
183
|
+
required: ['selector', 'tagName', 'rect', 'viewportWidth', 'reason'],
|
|
184
|
+
properties: {
|
|
185
|
+
selector: { type: 'string' },
|
|
186
|
+
tagName: { type: 'string' },
|
|
187
|
+
rect: { type: 'object' },
|
|
188
|
+
viewportWidth: { type: 'number' },
|
|
189
|
+
reason: {
|
|
190
|
+
type: 'string',
|
|
191
|
+
enum: ['overflow-right', 'overflow-left', 'clipped-text'],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
clippedTextElements: { type: 'array', items: { type: 'object' } },
|
|
197
|
+
disclaimer: DISCLAIMER_SCHEMA,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
export const TARGET_SIZE_CHECK_RESULT_SCHEMA = {
|
|
201
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
202
|
+
$id: 'https://masup9.github.io/a11y-audit/schemas/target-size-check-result.json',
|
|
203
|
+
title: 'TargetSizeCheckResult',
|
|
204
|
+
type: 'object',
|
|
205
|
+
required: [
|
|
206
|
+
'url',
|
|
207
|
+
'totalTargetsChecked',
|
|
208
|
+
'failAA',
|
|
209
|
+
'failAAAOnly',
|
|
210
|
+
'passedTargets',
|
|
211
|
+
'exceptedTargets',
|
|
212
|
+
'summary',
|
|
213
|
+
],
|
|
214
|
+
properties: {
|
|
215
|
+
url: { type: 'string' },
|
|
216
|
+
totalTargetsChecked: { type: 'number' },
|
|
217
|
+
failAA: { type: 'array', items: { type: 'object' } },
|
|
218
|
+
failAAAOnly: { type: 'array', items: { type: 'object' } },
|
|
219
|
+
passedTargets: { type: 'number' },
|
|
220
|
+
exceptedTargets: { type: 'array', items: { type: 'object' } },
|
|
221
|
+
summary: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
required: [
|
|
224
|
+
'failAACount',
|
|
225
|
+
'failAAAOnlyCount',
|
|
226
|
+
'passCount',
|
|
227
|
+
'exceptedCount',
|
|
228
|
+
],
|
|
229
|
+
properties: {
|
|
230
|
+
failAACount: { type: 'number' },
|
|
231
|
+
failAAAOnlyCount: { type: 'number' },
|
|
232
|
+
passCount: { type: 'number' },
|
|
233
|
+
exceptedCount: { type: 'number' },
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
disclaimer: DISCLAIMER_SCHEMA,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
/** All result schemas keyed by check id. */
|
|
240
|
+
export const RESULT_SCHEMAS = {
|
|
241
|
+
'axe-audit': AXE_AUDIT_RESULT_SCHEMA,
|
|
242
|
+
'focus-indicator-check': FOCUS_CHECK_RESULT_SCHEMA,
|
|
243
|
+
'reflow-check': REFLOW_CHECK_RESULT_SCHEMA,
|
|
244
|
+
'target-size-check': TARGET_SIZE_CHECK_RESULT_SCHEMA,
|
|
245
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the axe audit.
|
|
3
|
+
*
|
|
4
|
+
* Run it from a one-line local spec (Playwright excludes node_modules from
|
|
5
|
+
* test collection, so a `testMatch` glob into node_modules finds nothing):
|
|
6
|
+
* // tests/a11y/axe.spec.ts
|
|
7
|
+
* import "@masup9/a11y-audit/test-entries/axe-audit";
|
|
8
|
+
*
|
|
9
|
+
* Target URL comes from the `TEST_PAGE` env var; output dir from
|
|
10
|
+
* `A11Y_OUTPUT_DIR` (falling back to cwd). This is a thin wrapper around
|
|
11
|
+
* `runAxeAudit` — all logic lives there.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the axe audit.
|
|
3
|
+
*
|
|
4
|
+
* Run it from a one-line local spec (Playwright excludes node_modules from
|
|
5
|
+
* test collection, so a `testMatch` glob into node_modules finds nothing):
|
|
6
|
+
* // tests/a11y/axe.spec.ts
|
|
7
|
+
* import "@masup9/a11y-audit/test-entries/axe-audit";
|
|
8
|
+
*
|
|
9
|
+
* Target URL comes from the `TEST_PAGE` env var; output dir from
|
|
10
|
+
* `A11Y_OUTPUT_DIR` (falling back to cwd). This is a thin wrapper around
|
|
11
|
+
* `runAxeAudit` — all logic lives there.
|
|
12
|
+
*/
|
|
13
|
+
import { test } from '@playwright/test';
|
|
14
|
+
import { runAxeAudit } from '../playwright/runAxeAudit.js';
|
|
15
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
16
|
+
test('axe-core accessibility audit', async ({ page }) => {
|
|
17
|
+
const targetUrl = requireTargetUrl();
|
|
18
|
+
await page.goto(targetUrl, { waitUntil: 'networkidle' });
|
|
19
|
+
await runAxeAudit({ page });
|
|
20
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the focus indicator check
|
|
3
|
+
* (WCAG 2.4.7 / 2.4.12 / 3.2.1).
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around `runFocusIndicatorCheck`. Target URL from `TEST_PAGE`,
|
|
6
|
+
* output dir from `A11Y_OUTPUT_DIR` (falling back to cwd). Captures the legacy
|
|
7
|
+
* screenshot. This check owns its browser context (see runFocusIndicatorCheck).
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the focus indicator check
|
|
3
|
+
* (WCAG 2.4.7 / 2.4.12 / 3.2.1).
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around `runFocusIndicatorCheck`. Target URL from `TEST_PAGE`,
|
|
6
|
+
* output dir from `A11Y_OUTPUT_DIR` (falling back to cwd). Captures the legacy
|
|
7
|
+
* screenshot. This check owns its browser context (see runFocusIndicatorCheck).
|
|
8
|
+
*/
|
|
9
|
+
import { test } from '@playwright/test';
|
|
10
|
+
import { runFocusIndicatorCheck } from '../playwright/runFocusIndicatorCheck.js';
|
|
11
|
+
test('focus indicator visibility', async ({ browser }) => {
|
|
12
|
+
await runFocusIndicatorCheck({ browser, screenshot: true });
|
|
13
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the reflow check (WCAG 1.4.10).
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around `runReflowCheck`. Target URL from `TEST_PAGE`,
|
|
5
|
+
* output dir from `A11Y_OUTPUT_DIR` (falling back to cwd). Sets the narrow
|
|
6
|
+
* viewport before navigation to match the legacy script behavior, and captures
|
|
7
|
+
* the legacy screenshot.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the reflow check (WCAG 1.4.10).
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around `runReflowCheck`. Target URL from `TEST_PAGE`,
|
|
5
|
+
* output dir from `A11Y_OUTPUT_DIR` (falling back to cwd). Sets the narrow
|
|
6
|
+
* viewport before navigation to match the legacy script behavior, and captures
|
|
7
|
+
* the legacy screenshot.
|
|
8
|
+
*/
|
|
9
|
+
import { test } from '@playwright/test';
|
|
10
|
+
import { runReflowCheck } from '../playwright/runReflowCheck.js';
|
|
11
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
12
|
+
import { REFLOW_VIEWPORT } from '../constants.js';
|
|
13
|
+
test('reflow check (WCAG 1.4.10)', async ({ page }) => {
|
|
14
|
+
await page.setViewportSize({
|
|
15
|
+
width: REFLOW_VIEWPORT.width,
|
|
16
|
+
height: REFLOW_VIEWPORT.height,
|
|
17
|
+
});
|
|
18
|
+
const targetUrl = requireTargetUrl();
|
|
19
|
+
await page.goto(targetUrl, { waitUntil: 'networkidle' });
|
|
20
|
+
await runReflowCheck({ page, screenshot: true });
|
|
21
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the target size check (WCAG 2.5.5 / 2.5.8).
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around `runTargetSizeCheck`. Target URL from `TEST_PAGE`,
|
|
5
|
+
* output dir from `A11Y_OUTPUT_DIR` (falling back to cwd). Captures the legacy
|
|
6
|
+
* annotated screenshot.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility test entry for the target size check (WCAG 2.5.5 / 2.5.8).
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around `runTargetSizeCheck`. Target URL from `TEST_PAGE`,
|
|
5
|
+
* output dir from `A11Y_OUTPUT_DIR` (falling back to cwd). Captures the legacy
|
|
6
|
+
* annotated screenshot.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from '@playwright/test';
|
|
9
|
+
import { runTargetSizeCheck } from '../playwright/runTargetSizeCheck.js';
|
|
10
|
+
import { requireTargetUrl } from '../utils/test-harness.js';
|
|
11
|
+
test('target size check (WCAG 2.5.5 / 2.5.8)', async ({ page }) => {
|
|
12
|
+
const targetUrl = requireTargetUrl();
|
|
13
|
+
await page.goto(targetUrl, { waitUntil: 'networkidle' });
|
|
14
|
+
await runTargetSizeCheck({ page, screenshot: true });
|
|
15
|
+
});
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the WCAG audit checks shipped in @masup9/a11y-audit.
|
|
3
|
+
*/
|
|
4
|
+
import type { AUDIT_DISCLAIMER } from './constants.js';
|
|
5
|
+
export interface AxeViolationNode {
|
|
6
|
+
html: string;
|
|
7
|
+
target: string[];
|
|
8
|
+
failureSummary: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface AxeViolation {
|
|
11
|
+
id: string;
|
|
12
|
+
impact: string | null;
|
|
13
|
+
description: string;
|
|
14
|
+
help: string;
|
|
15
|
+
helpUrl: string;
|
|
16
|
+
tags: string[];
|
|
17
|
+
nodes: AxeViolationNode[];
|
|
18
|
+
}
|
|
19
|
+
export interface AxeAuditResult {
|
|
20
|
+
url: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
violations: AxeViolation[];
|
|
23
|
+
passes: number;
|
|
24
|
+
incomplete: number;
|
|
25
|
+
inapplicable: number;
|
|
26
|
+
violationCount: number;
|
|
27
|
+
disclaimer: typeof AUDIT_DISCLAIMER;
|
|
28
|
+
}
|
|
29
|
+
export interface FocusRecord {
|
|
30
|
+
id: number;
|
|
31
|
+
tag: string;
|
|
32
|
+
role: string | null;
|
|
33
|
+
name: string;
|
|
34
|
+
hasFocusStyle: boolean;
|
|
35
|
+
diff: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* WCAG 3.2.1 On Focus violation - context change triggered by focus
|
|
39
|
+
*/
|
|
40
|
+
export interface OnFocusViolation {
|
|
41
|
+
/** Element that triggered the navigation */
|
|
42
|
+
element: {
|
|
43
|
+
tag: string;
|
|
44
|
+
role: string | null;
|
|
45
|
+
name: string;
|
|
46
|
+
selector: string;
|
|
47
|
+
};
|
|
48
|
+
/** URL before focus */
|
|
49
|
+
fromUrl: string;
|
|
50
|
+
/** URL after focus (navigation target) */
|
|
51
|
+
toUrl: string;
|
|
52
|
+
/** Type of context change */
|
|
53
|
+
changeType: 'navigation' | 'new-window' | 'dialog';
|
|
54
|
+
}
|
|
55
|
+
export interface FocusCheckResult {
|
|
56
|
+
url: string;
|
|
57
|
+
totalFocusableElements: number;
|
|
58
|
+
elementsWithFocusStyle: number;
|
|
59
|
+
elementsWithoutFocusStyle: number;
|
|
60
|
+
/** WCAG 2.4.7 violations */
|
|
61
|
+
issues: Array<{
|
|
62
|
+
tag: string;
|
|
63
|
+
role: string | null;
|
|
64
|
+
name: string;
|
|
65
|
+
}>;
|
|
66
|
+
/** WCAG 3.2.1 violations - focus triggered context change */
|
|
67
|
+
onFocusViolations: OnFocusViolation[];
|
|
68
|
+
/** WCAG 2.4.12 violations - focus obscured by fixed/sticky elements */
|
|
69
|
+
focusObscuredIssues: FocusObscuredIssue[];
|
|
70
|
+
elementsWithObscuredFocus: number;
|
|
71
|
+
allElements: FocusRecord[];
|
|
72
|
+
/** Whether test was interrupted by navigation */
|
|
73
|
+
interrupted: boolean;
|
|
74
|
+
interruptedAt?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Path the screenshot was (or would be) written to. Empty string when
|
|
77
|
+
* `screenshot` was disabled and no screenshot file was produced.
|
|
78
|
+
*/
|
|
79
|
+
screenshotPath: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Bounding rect for overlap calculations
|
|
83
|
+
*/
|
|
84
|
+
export interface BoundingRect {
|
|
85
|
+
left: number;
|
|
86
|
+
top: number;
|
|
87
|
+
width: number;
|
|
88
|
+
height: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Information about an element obscuring the focused element
|
|
92
|
+
*/
|
|
93
|
+
export interface FocusObscuredOverlap {
|
|
94
|
+
/** The element causing the obscuring */
|
|
95
|
+
obscuredBy: {
|
|
96
|
+
tag: string;
|
|
97
|
+
role: string | null;
|
|
98
|
+
name: string;
|
|
99
|
+
selector: string;
|
|
100
|
+
};
|
|
101
|
+
/** The overlapping area */
|
|
102
|
+
overlapRect: BoundingRect;
|
|
103
|
+
/** Area of overlap in square pixels */
|
|
104
|
+
overlapArea: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* WCAG 2.4.12 violation - focus indicator hidden by fixed/sticky content
|
|
108
|
+
*/
|
|
109
|
+
export interface FocusObscuredIssue {
|
|
110
|
+
/** The focused element that is obscured */
|
|
111
|
+
element: {
|
|
112
|
+
tag: string;
|
|
113
|
+
role: string | null;
|
|
114
|
+
name: string;
|
|
115
|
+
selector: string;
|
|
116
|
+
};
|
|
117
|
+
/** Bounding rect of the focused element */
|
|
118
|
+
elementRect: BoundingRect;
|
|
119
|
+
/** List of overlapping fixed/sticky elements */
|
|
120
|
+
overlaps: FocusObscuredOverlap[];
|
|
121
|
+
/** Ratio of element area that is obscured (0-1) */
|
|
122
|
+
obscuredRatio: number;
|
|
123
|
+
}
|
|
124
|
+
export interface ReflowIssue {
|
|
125
|
+
selector: string;
|
|
126
|
+
tagName: string;
|
|
127
|
+
rect: {
|
|
128
|
+
left: number;
|
|
129
|
+
right: number;
|
|
130
|
+
width: number;
|
|
131
|
+
};
|
|
132
|
+
viewportWidth: number;
|
|
133
|
+
reason: 'overflow-right' | 'overflow-left' | 'clipped-text';
|
|
134
|
+
}
|
|
135
|
+
export interface ClippedTextElement {
|
|
136
|
+
selector: string;
|
|
137
|
+
tagName: string;
|
|
138
|
+
scrollWidth: number;
|
|
139
|
+
clientWidth: number;
|
|
140
|
+
scrollHeight: number;
|
|
141
|
+
clientHeight: number;
|
|
142
|
+
overflow: string;
|
|
143
|
+
overflowX: string;
|
|
144
|
+
}
|
|
145
|
+
export interface ReflowCheckResult {
|
|
146
|
+
url: string;
|
|
147
|
+
viewport: {
|
|
148
|
+
width: number;
|
|
149
|
+
height: number;
|
|
150
|
+
};
|
|
151
|
+
hasHorizontalScroll: boolean;
|
|
152
|
+
documentScrollWidth: number;
|
|
153
|
+
documentClientWidth: number;
|
|
154
|
+
overflowingElements: ReflowIssue[];
|
|
155
|
+
clippedTextElements: ClippedTextElement[];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Exception types for WCAG 2.5.8 Target Size (Minimum)
|
|
159
|
+
* - inline: Target is in a sentence or text block
|
|
160
|
+
* - redundant: Another target with same function meets size requirement
|
|
161
|
+
* - ua-control: Size is determined by user agent (native controls)
|
|
162
|
+
* - spacing: Target has sufficient spacing from adjacent targets
|
|
163
|
+
* - essential-review: May be essential exception but requires manual review
|
|
164
|
+
*/
|
|
165
|
+
export type TargetSizeException = 'inline' | 'redundant' | 'ua-control' | 'spacing' | 'essential-review';
|
|
166
|
+
export interface TargetSizeIssue {
|
|
167
|
+
/** CSS selector for the element */
|
|
168
|
+
selector: string;
|
|
169
|
+
/** HTML tag name */
|
|
170
|
+
tagName: string;
|
|
171
|
+
/** ARIA role if present */
|
|
172
|
+
role: string | null;
|
|
173
|
+
/** Computed accessible name */
|
|
174
|
+
accessibleName: string | null;
|
|
175
|
+
/** Element width in CSS pixels */
|
|
176
|
+
width: number;
|
|
177
|
+
/** Element height in CSS pixels */
|
|
178
|
+
height: number;
|
|
179
|
+
/** Smallest dimension (min of width/height) */
|
|
180
|
+
minDimension: number;
|
|
181
|
+
/** Pass/fail level */
|
|
182
|
+
level: 'fail-aa' | 'fail-aaa-only' | 'pass';
|
|
183
|
+
/** Exception that may apply */
|
|
184
|
+
exception: TargetSizeException | null;
|
|
185
|
+
/** Human-readable exception details */
|
|
186
|
+
exceptionDetails: string | null;
|
|
187
|
+
/** Link href for redundancy check */
|
|
188
|
+
href: string | null;
|
|
189
|
+
}
|
|
190
|
+
export interface TargetSizeSummary {
|
|
191
|
+
/** Number of targets failing AA (< 24px) */
|
|
192
|
+
failAACount: number;
|
|
193
|
+
/** Number of targets failing only AAA (24-43px) */
|
|
194
|
+
failAAAOnlyCount: number;
|
|
195
|
+
/** Number of targets passing (>= 44px) */
|
|
196
|
+
passCount: number;
|
|
197
|
+
/** Number of targets with possible exceptions */
|
|
198
|
+
exceptedCount: number;
|
|
199
|
+
}
|
|
200
|
+
export interface TargetSizeCheckResult {
|
|
201
|
+
/** Page URL */
|
|
202
|
+
url: string;
|
|
203
|
+
/** Total interactive elements checked */
|
|
204
|
+
totalTargetsChecked: number;
|
|
205
|
+
/** Elements failing AA threshold (< 24px) */
|
|
206
|
+
failAA: TargetSizeIssue[];
|
|
207
|
+
/** Elements failing only AAA threshold (24-43px) */
|
|
208
|
+
failAAAOnly: TargetSizeIssue[];
|
|
209
|
+
/** Number of elements passing (>= 44px) */
|
|
210
|
+
passedTargets: number;
|
|
211
|
+
/** Elements with possible exceptions */
|
|
212
|
+
exceptedTargets: TargetSizeIssue[];
|
|
213
|
+
/** Summary counts */
|
|
214
|
+
summary: TargetSizeSummary;
|
|
215
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotation overlay utilities for audit screenshots.
|
|
3
|
+
*
|
|
4
|
+
* Adds positioned boxes + labels over elements without modifying the content
|
|
5
|
+
* DOM. Used by the target size check to highlight pass/fail targets.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Standard annotation color schemes.
|
|
9
|
+
* Colors chosen for sufficient contrast with white text.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ANNOTATION_COLORS: {
|
|
12
|
+
/** Pass - Dark green with white text */
|
|
13
|
+
readonly pass: {
|
|
14
|
+
readonly bg: "#16a34a";
|
|
15
|
+
readonly border: "#16a34a";
|
|
16
|
+
readonly text: "#ffffff";
|
|
17
|
+
};
|
|
18
|
+
/** Warning - Dark orange with white text */
|
|
19
|
+
readonly warning: {
|
|
20
|
+
readonly bg: "#e65100";
|
|
21
|
+
readonly border: "#e65100";
|
|
22
|
+
readonly text: "#ffffff";
|
|
23
|
+
};
|
|
24
|
+
/** Fail - Dark red with white text */
|
|
25
|
+
readonly fail: {
|
|
26
|
+
readonly bg: "#dc2626";
|
|
27
|
+
readonly border: "#dc2626";
|
|
28
|
+
readonly text: "#ffffff";
|
|
29
|
+
};
|
|
30
|
+
/** Info - Dark blue with white text */
|
|
31
|
+
readonly info: {
|
|
32
|
+
readonly bg: "#0d47a1";
|
|
33
|
+
readonly border: "#0d47a1";
|
|
34
|
+
readonly text: "#ffffff";
|
|
35
|
+
};
|
|
36
|
+
/** Violation - Purple with white text */
|
|
37
|
+
readonly violation: {
|
|
38
|
+
readonly bg: "#7c3aed";
|
|
39
|
+
readonly border: "#7c3aed";
|
|
40
|
+
readonly text: "#ffffff";
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export type AnnotationColorScheme = keyof typeof ANNOTATION_COLORS;
|
|
44
|
+
/**
|
|
45
|
+
* Annotation configuration for browser context.
|
|
46
|
+
*/
|
|
47
|
+
export interface AnnotationConfig {
|
|
48
|
+
/** CSS selector to annotate */
|
|
49
|
+
selector: string;
|
|
50
|
+
/** Label text to display */
|
|
51
|
+
label: string;
|
|
52
|
+
/** Color scheme to use */
|
|
53
|
+
colorScheme: AnnotationColorScheme;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Add annotations to a page using Playwright.
|
|
57
|
+
*
|
|
58
|
+
* Note: this mutates the page DOM (it appends an overlay element). Take any
|
|
59
|
+
* "clean" screenshot before calling this.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* await addPageAnnotations(page, [
|
|
63
|
+
* { selector: '#header', label: 'PASS', colorScheme: 'pass' },
|
|
64
|
+
* { selector: '.error', label: 'FAIL', colorScheme: 'fail' },
|
|
65
|
+
* ]);
|
|
66
|
+
*/
|
|
67
|
+
export declare function addPageAnnotations(page: import('@playwright/test').Page, annotations: AnnotationConfig[]): Promise<void>;
|