@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.ja.md +128 -0
  3. package/README.md +130 -0
  4. package/dist/constants.d.ts +88 -0
  5. package/dist/constants.js +184 -0
  6. package/dist/index.d.ts +13 -0
  7. package/dist/index.js +13 -0
  8. package/dist/playwright/index.d.ts +19 -0
  9. package/dist/playwright/index.js +19 -0
  10. package/dist/playwright/runAxeAudit.d.ts +28 -0
  11. package/dist/playwright/runAxeAudit.js +89 -0
  12. package/dist/playwright/runFocusIndicatorCheck.d.ts +30 -0
  13. package/dist/playwright/runFocusIndicatorCheck.js +740 -0
  14. package/dist/playwright/runReflowCheck.d.ts +36 -0
  15. package/dist/playwright/runReflowCheck.js +68 -0
  16. package/dist/playwright/runTargetSizeCheck.d.ts +35 -0
  17. package/dist/playwright/runTargetSizeCheck.js +389 -0
  18. package/dist/schemas/index.d.ts +34 -0
  19. package/dist/schemas/index.js +245 -0
  20. package/dist/test-entries/axe-audit.d.ts +13 -0
  21. package/dist/test-entries/axe-audit.js +20 -0
  22. package/dist/test-entries/focus-indicator-check.d.ts +9 -0
  23. package/dist/test-entries/focus-indicator-check.js +13 -0
  24. package/dist/test-entries/reflow-check.d.ts +9 -0
  25. package/dist/test-entries/reflow-check.js +21 -0
  26. package/dist/test-entries/target-size-check.d.ts +8 -0
  27. package/dist/test-entries/target-size-check.js +15 -0
  28. package/dist/types.d.ts +215 -0
  29. package/dist/types.js +4 -0
  30. package/dist/utils/annotations.d.ts +67 -0
  31. package/dist/utils/annotations.js +110 -0
  32. package/dist/utils/layout.d.ts +23 -0
  33. package/dist/utils/layout.js +144 -0
  34. package/dist/utils/test-harness.d.ts +76 -0
  35. package/dist/utils/test-harness.js +119 -0
  36. 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
+ });
@@ -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,4 @@
1
+ /**
2
+ * Type definitions for the WCAG audit checks shipped in @masup9/a11y-audit.
3
+ */
4
+ export {};
@@ -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>;