Skip to content

Instantly share code, notes, and snippets.

@donaldpipowitch
Created November 26, 2018 11:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save donaldpipowitch/898854e2f71f64d0d91e199537b3ee1a to your computer and use it in GitHub Desktop.
Save donaldpipowitch/898854e2f71f64d0d91e199537b3ee1a to your computer and use it in GitHub Desktop.
aXe based a11y checks in your CI for Storybook
const puppeteer = require('puppeteer');
const chalk = require('chalk');
const { green, red, cyan, grey, bold } = chalk;
// we assume storybook can visited at http://localhost:9001
const url = 'http://localhost:9001/iframe.html';
const runAxe = () =>
new Promise((resolve, reject) =>
axe.run('#root', { runOnly: ['wcag2a'] }, (error, result) =>
error ? reject(error) : resolve(result)
)
);
puppeteer.launch().then(async (browser) => {
// setup
const page = await browser.newPage();
await page.goto(url);
const stories = await page.evaluate(
({ innerText }) => JSON.parse(innerText),
await page.$('#sitemap')
);
// collect errors
const errors = [];
for (const { kind, name } of stories) {
const selectedKind = `selectedKind=${encodeURIComponent(kind)}`;
const selectedStory = `selectedStory=${encodeURIComponent(name)}`;
await page.goto(`${url}?${selectedKind}&${selectedStory}`);
const { violations } = await page.evaluate(runAxe);
if (violations.length) {
errors.push({ kind, name, violations });
}
}
await browser.close();
// result
if (errors.length) {
errors.forEach(({ kind, name, violations }) => {
console.error(bold(`Error(s) in ${red(`${kind}/${name}`)}:`));
violations.forEach(({ description, nodes }) => {
console.error(`${description} (${grey(`${nodes.length} times`)})`);
});
console.error('');
});
const sum = errors.reduce((acc, error) => acc + error.violations.length, 0);
console.error(`${red('✖')} Found ${red(sum)} error(s)!\n`);
const page = cyan('http://localhost:9001');
console.error(
`Open ${page} and visit the mentioned stories to find out more about where the problems happen and how they can be fixed.\n`
);
process.exitCode = 1;
} else {
console.log(`${green('✔')} Everything is WCAG2A compatible!`);
}
});
// ususally .storybook/config.js:
import { getStorybook } from '@storybook/react';
// ...your usual config is here...
// create "sitemap" which can be queried by other tools
const myStories = [];
getStorybook().forEach(({ kind, stories }) =>
stories.forEach(({ name }) => myStories.push({ kind, name }))
);
const data = document.createElement('script');
data.type = 'application/json';
data.id = 'sitemap';
data.innerText = JSON.stringify(myStories);
document.head.append(data);
@jtomaszewski
Copy link

BTW, I found a way to get all the examples without injecting any code into the storybook config ( https://github.com/happo/happo-plugin-storybook/blob/master/register.es6.js#L18-L41 ).

This and also an option to filter out some of your stories with storybook parameter, I placed in my fork: https://gist.github.com/jtomaszewski/c3e033c1672908b0b2de703a0a1fe340 . Feel free to use it.

@Hypnosphi
Copy link

spawn a webpack server

A better option is to use http-server to serve a static build

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment