Skip to content

Instantly share code, notes, and snippets.

@jtomaszewski
Forked from donaldpipowitch/axe.js
Last active March 11, 2019 22:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jtomaszewski/c3e033c1672908b0b2de703a0a1fe340 to your computer and use it in GitHub Desktop.
Save jtomaszewski/c3e033c1672908b0b2de703a0a1fe340 to your computer and use it in GitHub Desktop.
aXe based a11y checks in your CI for Storybook
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
// eslint-disable-next-line import/no-extraneous-dependencies
const puppeteer = require('puppeteer');
const { green, red, cyan, grey, bold } = require('chalk');
const url = 'http://localhost:9001/iframe.html';
function runAxe() {
return new Promise((resolve, reject) =>
// eslint-disable-next-line no-undef
axe.run('#root', { runOnly: ['wcag2a'] }, (error, result) =>
error ? reject(error) : resolve(result),
),
);
}
/* eslint-disable no-underscore-dangle,no-undef */
function getExamples() {
const storyStore = __STORYBOOK_CLIENT_API__._storyStore;
const result = [];
for (const story of __STORYBOOK_CLIENT_API__.getStorybook()) {
const { kind } = story;
for (const example of story.stories) {
const { name } = example;
let enabled = true;
if (storyStore.getStoryAndParameters) {
const { parameters } = storyStore.getStoryAndParameters(
story.kind,
name,
);
// To disable axe tests for the given story,
// Use `.addParameters({ axe: { enabled: false } })`
if (
parameters &&
parameters.axe &&
parameters.axe.enabled !== undefined &&
!parameters.axe.enabled
) {
enabled = false;
}
}
if (enabled) {
result.push({
kind,
name,
});
}
}
}
return result;
}
/* eslint-enable no-underscore-dangle,no-undef */
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.goto(url);
const stories = await page.evaluate(getExamples);
const errors = [];
for (const { kind, name } of stories) {
const storyUrl = `${url}?${`selectedKind=${encodeURIComponent(
kind,
)}`}&${`selectedStory=${encodeURIComponent(name)}`}`;
console.log(`Running ${kind}/${name} ...`);
await page.goto(storyUrl);
const { violations } = await page.evaluate(runAxe);
if (violations.length) {
errors.push({ kind, name, violations });
}
}
await browser.close();
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`);
console.error(
`Open ${cyan(
'http://localhost:9001',
)} 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!`);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment