Skip to content

Commit

Permalink
Merge pull request #29643 from storybookjs/valentin/unified-a11y-testing
Browse files Browse the repository at this point in the history
A11y: Create a11y test provider and revamp a11y addon
  • Loading branch information
valentinpalkovic authored Dec 9, 2024
2 parents f545849 + b20b03b commit d6a06fb
Show file tree
Hide file tree
Showing 110 changed files with 3,799 additions and 975 deletions.
26 changes: 26 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<h1>Migration</h1>

- [From version 8.4.x to 8.5.x](#from-version-84x-to-85x)
- [Addon-a11y: Component test integration](#addon-a11y-component-test-integration)
- [Addon-a11y: Deprecated `parameters.a11y.manual`](#addon-a11y-deprecated-parametersa11ymanual)
- [Indexing behavior of @storybook/experimental-addon-test is changed](#indexing-behavior-of-storybookexperimental-addon-test-is-changed)
- [From version 8.2.x to 8.3.x](#from-version-82x-to-83x)
- [Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types](#removed-experimental_sidebar_bottom-and-deprecated-experimental_sidebar_top-addon-types)
Expand Down Expand Up @@ -423,6 +425,30 @@

## From version 8.4.x to 8.5.x

### Addon-a11y: Component test integration

In Storybook 8.4, we introduced a new addon called [addon test](https://storybook.js.org/docs/writing-tests/test-addon). Powered by Vitest under the hood, this addon lets you watch, run, and debug your component tests directly in Storybook.

In Storybook 8.5, we revamped the Accessibility addon (`@storybook/addon-a11y`) to integrate it with the component tests feature. This means you can now extend your component tests to include accessibility tests. If you upgrade to Storybook 8.5 via `npx storybook@latest upgrade`, the Accessibility addon will be automatically configured to work with the component tests. However, if you're upgrading manually and you have the [addon test](https://storybook.js.org/docs/writing-tests/test-addon) installed, adjust your configuration as follows:

```diff
// .storybook/vitest.config.ts
...
+import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';

const annotations = setProjectAnnotations([
previewAnnotations,
+ a11yAddonAnnotations,
]);

// Run Storybook's beforeAll hook
beforeAll(annotations.beforeAll);
```

### Addon-a11y: Deprecated `parameters.a11y.manual`

We have deprecated `parameters.a11y.manual` in 8.5. Please use `globals.a11y.manual` instead.

### Indexing behavior of @storybook/experimental-addon-test is changed

The Storybook test addon used to index stories based on the `test.include` field in the Vitest config file. This caused indexing issues with Storybook, because stories could have been indexed by Storybook and not Vitest, and vice versa. Starting in Storybook 8.5.0-alpha.18, we changed the indexing behavior so that it always uses the globs defined in the `stories` field in `.storybook/main.js` for a more consistent experience. It is now discouraged to use `test.include`, please remove it.
Expand Down
3 changes: 3 additions & 0 deletions code/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ export const parameters = {
opacity: 0.4,
},
},
a11y: {
warnings: ['minor', 'moderate', 'serious', 'critical'],
},
};

export const tags = ['test', 'vitest'];
5 changes: 3 additions & 2 deletions code/.storybook/storybook.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { userEvent as storybookEvent, expect as storybookExpect } from '@storybo
// eslint-disable-next-line import/namespace
import * as testAnnotations from '@storybook/experimental-addon-test/preview';

import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';

import * as coreAnnotations from '../addons/toolbars/template/stories/preview';
import * as componentAnnotations from '../core/template/stories/preview';
// register global components used in many stories
Expand All @@ -15,9 +17,8 @@ import * as projectAnnotations from './preview';
vi.spyOn(console, 'warn').mockImplementation((...args) => console.log(...args));

const annotations = setProjectAnnotations([
// @ts-expect-error check type errors later
a11yAddonAnnotations,
projectAnnotations,
// @ts-expect-error check type errors later
componentAnnotations,
coreAnnotations,
testAnnotations,
Expand Down
207 changes: 7 additions & 200 deletions code/addons/a11y/README.md
Original file line number Diff line number Diff line change
@@ -1,206 +1,13 @@
# storybook-addon-a11y
# Storybook Accessibility Addon

This Storybook addon can be helpful to make your UI components more accessible.
The @storybook/addon-a11y package provides accessibility testing for Storybook stories. It uses axe-core to run the tests.

[Framework Support](https://storybook.js.org/docs/configure/integration/frameworks-feature-support)
## Getting Started

![Screenshot](https://raw.githubusercontent.com/storybookjs/storybook/next/code/addons/a11y/docs/screenshot.png)
### Add the addon to an existing Storybook

## Getting started

First, install the addon.

```sh
$ yarn add @storybook/addon-a11y --dev
```

Add this line to your `main.js` file (create this file inside your Storybook config directory if needed).

```js
export default {
addons: ['@storybook/addon-a11y'],
};
```

And here's a sample story file to test the addon:

```js
import React from 'react';

export default {
title: 'button',
};

export const Accessible = () => <button>Accessible button</button>;

export const Inaccessible = () => (
<button style={{ backgroundColor: 'red', color: 'darkRed' }}>Inaccessible button</button>
);
```

## Handling failing rules

When Axe reports accessibility violations in stories, there are multiple ways to handle these failures depending on your needs.

### Story-level overrides

At the Story level, override rules using `parameters.a11y.config.rules`.

```js
export const InputWithoutAutofill = () => <input type="text" autocomplete="nope" />;

InputWithoutAutofill.parameters = {
a11y: {
// Avoid doing this, as it will fully disable all accessibility checks for this story.
disable: true,

// Instead, override rules 👇
// axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
config: {
rules: [
{
// You can exclude some elements from failing a specific rule:
id: 'autocomplete-valid',
selector: '*:not([autocomplete="nope"])',
},
{
// You can also signify that a violation will need to be fixed in the future
// by overriding the result of a rule to return "Needs Review"
// rather than "Violation" if the rule fails:
id: 'landmark-complementary-is-top-level',
reviewOnFail: true,
},
],
},
},
};
```

Alternatively, you can disable specific rules in a Story:

```js
export const Inaccessible = () => (
<button style={{ backgroundColor: 'red', color: 'darkRed' }}>Inaccessible button</button>
);
Inaccessible.parameters = {
a11y: {
config: {
rules: [{ id: 'color-contrast', enabled: false }],
},
},
};
```bash
npx storybook add @storybook/addon-a11y
```

Tip: clearly explain in a comment why a rule was overridden, it’ll help you and your team trace back why certain violations aren’t being reported or need to be addressed. For example:

```js
MyStory.parameters = {
a11y: {
config: {
rules: [
{
// Allow `autocomplete="nope"` on form elements,
// a workaround to disable autofill in Chrome.
// @link https://bugs.chromium.org/p/chromium/issues/detail?id=468153
id: 'autocomplete-valid',
selector: '*:not([autocomplete="nope"])',
},
{
// @fixme Color contrast of subdued text fails, as raised in issue #123.
id: 'color-contrast',
reviewOnFail: true,
},
],
},
},
};
```

### Global overrides

When you want to ignore an accessibility rule or change its settings across all stories, set `parameters.a11y.config.rules` in your Storybook’s `preview.ts` file. This can be particularly useful for ignoring false positives commonly reported by Axe.

```ts
// .storybook/preview.ts

export const parameters = {
a11y: {
config: {
rules: [
{
// This tells Axe to run the 'autocomplete-valid' rule on selectors
// that match '*:not([autocomplete="nope"])' (all elements except [autocomplete="nope"]).
// This is the safest way of ignoring a violation across all stories,
// as Axe will only ignore very specific elements and keep reporting
// violations on other elements of this rule.
id: 'autocomplete-valid',
selector: '*:not([autocomplete="nope"])',
},
{
// To disable a rule across all stories, set `enabled` to `false`.
// Use with caution: all violations of this rule will be ignored!
id: 'autocomplete-valid',
enabled: false,
},
],
},
},
};
```

### Disabling checks

If you wish to entirely disable `a11y` checks for a subset of stories, you can control this with story parameters:

```js
export const MyNonCheckedStory = () => <SomeComponent />;
MyNonCheckedStory.parameters = {
// Avoid doing this, as it fully disables all accessibility checks for this story,
// and consider the techniques described above.
a11y: { disable: true },
};
```

## Parameters

For more customizability use parameters to configure [aXe options](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure).
You can override these options [at story level too](https://storybook.js.org/docs/react/configure/features-and-behavior#per-story-options).

```js
import React from 'react';
import { addDecorator, addParameters, storiesOf } from '@storybook/react';

export default {
title: 'button',
parameters: {
a11y: {
// optional selector which element to inspect
element: '#storybook-root',
// axe-core configurationOptions (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#parameters-1)
config: {},
// axe-core optionsParameter (https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter)
options: {},
// optional flag to prevent the automatic check
manual: true,
},
},
};

export const accessible = () => <button>Accessible button</button>;

export const inaccessible = () => (
<button style={{ backgroundColor: 'red', color: 'darkRed' }}>Inaccessible button</button>
);
```

## Automate accessibility tests with test runner

The test runner does not apply any rules that you have set on your stories by default. You can configure the runner to correctly apply the rules by [following the guide on the Storybook docs](https://storybook.js.org/docs/writing-tests/accessibility-testing#automate-accessibility-tests-with-test-runner).

## Roadmap

- Make UI accessible
- Show in story where violations are.
- Add more example tests
- Add tests
- Make CI integration possible
[More on getting started with the accessibility addon](https://storybook.js.org/docs/writing-tests/accessibility-testing#accessibility-checks-with-a11y-addon)
6 changes: 5 additions & 1 deletion code/addons/a11y/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@
},
"dependencies": {
"@storybook/addon-highlight": "workspace:*",
"axe-core": "^4.2.0"
"@storybook/test": "workspace:*",
"axe-core": "^4.2.0",
"vitest-axe": "^0.1.0"
},
"devDependencies": {
"@storybook/global": "^5.0.0",
"@storybook/icons": "^1.2.12",
"@testing-library/react": "^14.0.0",
"picocolors": "^1.1.0",
"pretty-format": "^29.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-resize-detector": "^7.1.2",
Expand Down
1 change: 0 additions & 1 deletion code/addons/a11y/src/a11yRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe('a11yRunner', () => {
await import('./a11yRunner');

expect(mockedAddons.getChannel).toHaveBeenCalled();
expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.REQUEST, expect.any(Function));
expect(mockChannel.on).toHaveBeenCalledWith(EVENTS.MANUAL, expect.any(Function));
});
});
Loading

0 comments on commit d6a06fb

Please sign in to comment.