0
# Accessibility Testing
1
2
Automated accessibility audits using Axe Core integration to verify component accessibility compliance with WCAG guidelines.
3
4
## Capabilities
5
6
### Axe Test Function
7
8
Creates a test function that runs accessibility audits on Storybook stories using @axe-core/puppeteer integration.
9
10
```typescript { .api }
11
/**
12
* Creates a test function for automated accessibility testing
13
* @param customConfig - Optional configuration to override defaults
14
* @returns Test function that performs accessibility audits
15
*/
16
function axeTest(customConfig?: Partial<AxeConfig>): TestFunction;
17
18
interface AxeConfig extends CommonConfig {
19
/** Hook executed before accessibility test runs */
20
beforeAxeTest: (page: Page, options: Options) => Promise<void>;
21
}
22
```
23
24
**Usage Examples:**
25
26
```typescript
27
import initStoryshots from '@storybook/addon-storyshots';
28
import { axeTest } from '@storybook/addon-storyshots-puppeteer';
29
30
// Basic accessibility testing
31
initStoryshots({
32
suite: 'A11y checks',
33
test: axeTest()
34
});
35
36
// With pre-test setup
37
initStoryshots({
38
suite: 'Accessibility audits',
39
test: axeTest({
40
storybookUrl: 'http://localhost:6006',
41
beforeAxeTest: async (page) => {
42
// Wait for dynamic content
43
await page.waitForSelector('[data-loaded="true"]');
44
// Set focus state for testing
45
await page.focus('input[type="text"]');
46
}
47
})
48
});
49
```
50
51
### Story Parameter Integration
52
53
Accessibility test configuration uses the same `a11y` parameters as @storybook/addon-a11y for consistency.
54
55
```typescript { .api }
56
interface A11yParameters {
57
/** Element selector to test (default: '#storybook-root') */
58
element?: string;
59
/** Elements to exclude from testing */
60
exclude?: string | string[];
61
/** Axe rules to disable */
62
disabledRules?: string[];
63
/** Axe run options */
64
options?: AxeRunOptions;
65
/** Axe configuration */
66
config?: AxeConfig;
67
}
68
69
interface AxeRunOptions {
70
/** Include only specific rules */
71
runOnly?: string[] | { type: 'rule' | 'tag'; values: string[] };
72
/** Rule configuration overrides */
73
rules?: { [ruleId: string]: { enabled: boolean } };
74
/** Tags to include */
75
tags?: string[];
76
/** Result types to return */
77
resultTypes?: ('violations' | 'incomplete' | 'passes' | 'inapplicable')[];
78
}
79
```
80
81
**Usage Examples:**
82
83
```typescript
84
// Story with accessibility configuration
85
export const AccessibleForm = () => <ContactForm />;
86
87
AccessibleForm.parameters = {
88
a11y: {
89
element: '#contact-form',
90
exclude: ['.skip-a11y-test'],
91
disabledRules: ['color-contrast'], // Temporary exclusion
92
options: {
93
tags: ['wcag2a', 'wcag2aa'],
94
runOnly: {
95
type: 'tag',
96
values: ['wcag2a', 'wcag2aa']
97
}
98
}
99
}
100
};
101
102
// Testing specific accessibility rules
103
export const ColorContrastTest = () => <ThemeProvider><Button /></ThemeProvider>;
104
105
ColorContrastTest.parameters = {
106
a11y: {
107
options: {
108
runOnly: ['color-contrast']
109
}
110
}
111
};
112
```
113
114
### Axe Core Integration
115
116
Direct integration with @axe-core/puppeteer provides comprehensive accessibility testing capabilities.
117
118
```typescript { .api }
119
/**
120
* Axe Puppeteer instance methods used internally
121
*/
122
interface AxePuppeteer {
123
/** Include elements in accessibility scan */
124
include(selector: string): AxePuppeteer;
125
/** Exclude elements from accessibility scan */
126
exclude(selector: string | string[]): AxePuppeteer;
127
/** Set axe-core options */
128
options(options: AxeRunOptions): AxePuppeteer;
129
/** Disable specific rules */
130
disableRules(rules: string | string[]): AxePuppeteer;
131
/** Configure axe-core */
132
configure(config: AxeConfig): AxePuppeteer;
133
/** Run accessibility analysis */
134
analyze(): Promise<AxeResults>;
135
}
136
137
interface AxeResults {
138
/** Accessibility violations found */
139
violations: AxeViolation[];
140
/** Incomplete tests */
141
incomplete: AxeIncomplete[];
142
/** Passing tests */
143
passes: AxePass[];
144
/** Inapplicable tests */
145
inapplicable: AxeInapplicable[];
146
}
147
148
interface AxeViolation {
149
/** Rule identifier */
150
id: string;
151
/** Impact level */
152
impact: 'minor' | 'moderate' | 'serious' | 'critical';
153
/** Human-readable description */
154
description: string;
155
/** Help text and documentation */
156
help: string;
157
/** Help URL for more information */
158
helpUrl: string;
159
/** Elements that failed the rule */
160
nodes: AxeNodeResult[];
161
}
162
```
163
164
### Test Execution Flow
165
166
The axe test follows a specific execution pattern for consistent accessibility auditing.
167
168
```typescript { .api }
169
/**
170
* Internal test execution steps
171
*/
172
interface AxeTestFlow {
173
/** 1. Execute beforeAxeTest hook */
174
beforeAxeTest: (page: Page, options: Options) => Promise<void>;
175
/** 2. Create AxePuppeteer instance */
176
createAxeInstance: (page: Page) => AxePuppeteer;
177
/** 3. Configure accessibility test */
178
configureAxe: (axe: AxePuppeteer, parameters: A11yParameters) => AxePuppeteer;
179
/** 4. Run accessibility analysis */
180
analyzeAccessibility: () => Promise<AxeResults>;
181
/** 5. Assert no violations */
182
assertNoViolations: (violations: AxeViolation[]) => void;
183
}
184
```
185
186
**Test Flow Example:**
187
188
```typescript
189
// Internal test execution (for reference)
190
async function testBody(page, testOptions) {
191
const {
192
element = '#storybook-root',
193
exclude,
194
disabledRules,
195
options,
196
config,
197
} = testOptions.context.parameters.a11y || {};
198
199
// 1. Pre-test setup
200
await beforeAxeTest(page, options);
201
202
// 2. Create and configure Axe instance
203
const axe = new AxePuppeteer(page);
204
axe.include(element);
205
206
if (exclude) axe.exclude(exclude);
207
if (options) axe.options(options);
208
if (disabledRules) axe.disableRules(disabledRules);
209
if (config) axe.configure(config);
210
211
// 3. Run analysis and assert
212
const { violations } = await axe.analyze();
213
expect(violations).toHaveLength(0);
214
}
215
```
216
217
### Before Test Hook
218
219
Configure page state before accessibility testing runs.
220
221
```typescript { .api }
222
/**
223
* Hook for pre-test setup
224
* @param page - Puppeteer page instance
225
* @param options - Test context and URL information
226
* @returns Promise for async setup operations
227
*/
228
type BeforeAxeTest = (page: Page, options: Options) => Promise<void>;
229
```
230
231
**Usage Examples:**
232
233
```typescript
234
// Wait for dynamic content to load
235
const beforeAxeTest = async (page, { context }) => {
236
if (context.story.includes('loading')) {
237
await page.waitForSelector('[data-loaded="true"]', { timeout: 5000 });
238
}
239
240
// Allow animations to complete
241
await page.waitForTimeout(600);
242
243
// Set up specific interaction states
244
if (context.story.includes('focus')) {
245
await page.focus('input[type="email"]');
246
}
247
};
248
249
// Complex interaction setup
250
const beforeAxeTest = async (page) => {
251
// Open modal or dropdown
252
await page.click('[aria-haspopup="true"]');
253
await page.waitForSelector('[role="dialog"]');
254
255
// Wait for any transitions
256
await page.waitForFunction(
257
() => window.getComputedStyle(document.querySelector('[role="dialog"]')).opacity === '1'
258
);
259
};
260
261
initStoryshots({
262
suite: 'Dynamic accessibility',
263
test: axeTest({ beforeAxeTest })
264
});
265
```
266
267
### Default Configuration
268
269
Default accessibility testing configuration targets the main Storybook root element.
270
271
```typescript { .api }
272
const defaultAxeConfig: AxeConfig = {
273
...defaultCommonConfig,
274
beforeAxeTest: () => Promise.resolve(),
275
};
276
277
// Default story parameters when none specified
278
const defaultA11yParameters: A11yParameters = {
279
element: '#storybook-root',
280
exclude: undefined,
281
disabledRules: undefined,
282
options: undefined,
283
config: undefined
284
};
285
```