diff --git a/.gitignore b/.gitignore index 22e6580..2e451ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -.idea \ No newline at end of file +.idea +*.lock \ No newline at end of file diff --git a/README.md b/README.md index e724846..b9138cf 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The TestCafe module that allows you to use the [aXe](https://github.com/dequelab ## Installation ```bash -npm install axe-testcafe +npm install axe-core axe-testcafe --save-dev ``` ## How to use @@ -12,17 +12,18 @@ npm install axe-testcafe You can write a TestCafe test with automated accessibility checks like this. ```js -import axeCheck from 'axe-testcafe'; +import { axeCheck, createReport } from 'axe-testcafe'; fixture `TestCafe tests with Axe` .page `http://example.com`; test('Automated accessibility testing', async t => { - await axeCheck(t); + const { error, violations } = await axeCheck(t); + await t.expect(violations.length === 0).ok(createReport(violations)); }); ``` -If any accessibility issues are found, you will see a detailed report. +If any accessibility issues are found, you will see a detailed report generated by the `createReport` function. ![Accessibility errors](https://github.com/helen-dikareva/axe-testcafe/blob/master/errors.png) @@ -32,8 +33,8 @@ The `axe-testcafe` module allows you to define the `context` and `options` [axe. ```js test('Automated accessibility testing', async () => { - var axeContext = { exclude: [['select']] }; - var axeOptions = { rules: { 'html-has-lang': { enabled: false } } }; - - await axeCheck(t, axeContext, axeOptions); + const axeContext = { exclude: [['select']] }; + const axeOptions = { rules: { 'html-has-lang': { enabled: false } } }; + const { error, violations } = await axeCheck(t, axeContext, axeOptions); + await t.expect(violations.length === 0).ok(createReport(violations)); }); diff --git a/errors.png b/errors.png index ca784b8..ca68326 100644 Binary files a/errors.png and b/errors.png differ diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..08d6064 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,18 @@ +declare module 'axe-testcafe' { + import { ElementContext, RunOnly, AxeResults, Result } from 'axe-core'; + import 'testcafe'; + + export function axeCheck( + t: TestController, + context?: ElementContext, + options?: { + runOnly?: RunOnly; + rules?: Object; + iframes?: Boolean; + elementRef?: Boolean; + selectors?: Boolean; + } + ): Promise; + + export function createReport(violations: Result[]): string; +} \ No newline at end of file diff --git a/index.js b/index.js index 5eb924c..b964ae5 100644 --- a/index.js +++ b/index.js @@ -1,74 +1,59 @@ -var fs = require('fs'); -var path = require('path'); -var testcafe = require('testcafe'); +const fs = require('fs'); +const path = require('path'); +const { ClientFunction } = require('testcafe'); +const { red, green, reset } = require('chalk'); -var ClientFunction = testcafe.ClientFunction; +const AXE_DIR_PATH = path.dirname(require.resolve('axe-core')); +const AXE_SCRIPT = fs.readFileSync(path.join(AXE_DIR_PATH, 'axe.min.js'), 'utf8'); -var AXE_DIR_PATH = path.dirname(require.resolve('axe-core')); -var AXE_SCRIPT = fs.readFileSync(path.join(AXE_DIR_PATH, 'axe.min.js')).toString(); +const hasAxe = ClientFunction(() => !!(window.axe && window.axe.run)); -function AxeError (message) { - Error.call(this, message); +const injectAxe = ClientFunction(() => eval(AXE_SCRIPT), { dependencies: { AXE_SCRIPT } }); - this.name = 'AxeError'; - this.message = message; - - if (typeof Error.captureStackTrace === 'function') - Error.captureStackTrace(this, AxeError); - else - this.stack = (new Error(message)).stack; -} - -AxeError.prototype = Object.create(Error.prototype); - -var hasAxe = ClientFunction(function () { - return !!(window.axe && window.axe.run); +const runAxe = ClientFunction((context, options = {}) => { + return new Promise((resolve) => { + axe.run(context || document, options, (error, { violations }) => { + resolve({ error, violations }); + }); + }); }); -var injectAxe = ClientFunction(function () { - eval(AXE_SCRIPT); -}, { dependencies: { AXE_SCRIPT: AXE_SCRIPT } }); +const createReport = violations => { + if (!violations.length) { + return green('0 violations found'); + } -var runAxe = ClientFunction(function (context, options) { - return new Promise(function (resolve) { - axe.run(context || document, options || {}, function (err, results) { - if (err) - return resolve(err.message); + const report = violations.reduce((acc, { nodes, help }, i) => { - var errors = ''; + acc += red(`${i+1}) ${help}\n`); + + acc += reset(nodes.reduce((e, { target }) => { + const targetNodes = target.map((t) => `"${t}"`).join(', '); + e += `\t${targetNodes}\n`; + return e; + },'')); - if (results.violations.length !== 0) { - results.violations.forEach(function (violation) { - errors += violation.help + '\n\tnodes:\n'; + return acc; - violation.nodes.forEach(function (node) { - var targetNodes = node.target.map(function (target) { - return '"' + target + '"'; - }).join(', '); + }, red(`${violations.length} violations found:\n`)); - errors += '\t\t' + targetNodes + '\n'; - }); - }); - } + return reset(report); + +}; - return resolve(errors); - }); - }) -}); +const axeCheck = async (t, context, options) => { + const hasScript = await hasAxe.with({ boundTestRun: t })(); + if (!hasScript) + await injectAxe.with({ boundTestRun: t })(); -module.exports = function axeCheck (t, context, options) { - return hasAxe.with({ boundTestRun: t })() - .then(function (result) { - if (!result) - return injectAxe.with({ boundTestRun: t })(); + try { + return await runAxe.with({ boundTestRun: t })(context, options); + } catch (e) { + return { error: e }; + } +}; - return Promise.resolve(); - }) - .then(function () { - return runAxe.with({ boundTestRun: t })(context, options); - }) - .then(function (error) { - if (error) - throw new AxeError('\n' + error); - }); +module.exports = { + axeCheck, + createReport }; \ No newline at end of file diff --git a/package.json b/package.json index d50f1af..f30f8e8 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,36 @@ -{ - "name": "axe-testcafe", - "version": "1.1.0", - "description": "The TestCafe module that allows you to use the aXe accessibility engine in TestCafe tests", - "main": "index.js", - "repository": { - "type": "git", - "url": "https://github.com/helen-dikareva/axe-testcafe" - }, - "keywords": [ - "axe", - "testcafe", - "test", - "accessibility" - ], - "author": "Helen Dikareva (elena.dikareva@devexpress.com)", - "license": "MIT", - "bugs": { - "url": "https://github.com/helen-dikareva/axe-testcafe/issues" - }, - "homepage": "https://github.com/helen-dikareva/axe-testcafe", - "files": [ - "index.js" - ], - "dependencies": { - "axe-core": "^2.2.3 || ^3.0.0" - }, - "peerDependencies": { - "testcafe": "*" - } -} +{ + "name": "axe-testcafe", + "version": "2.0.0", + "description": "The TestCafe module that allows you to use the aXe accessibility engine in TestCafe tests", + "main": "index.js", + "typings": "index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/helen-dikareva/axe-testcafe" + }, + "engines": { + "node": ">=8.9.0" + }, + "keywords": [ + "axe", + "testcafe", + "test", + "accessibility" + ], + "author": "Helen Dikareva (elena.dikareva@devexpress.com)", + "license": "MIT", + "bugs": { + "url": "https://github.com/helen-dikareva/axe-testcafe/issues" + }, + "homepage": "https://github.com/helen-dikareva/axe-testcafe", + "files": [ + "index.js" + ], + "peerDependencies": { + "axe-core": ">=2.2.3 <4", + "testcafe": "*" + }, + "dependencies": { + "chalk": "^2.4.1" + } +}