Skip to content
This repository has been archived by the owner on Dec 8, 2022. It is now read-only.

Commit

Permalink
e2e Accessibility (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
Blackbaud-SteveBrush authored Aug 17, 2017
1 parent da33cdc commit 15a4bea
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 5 deletions.
96 changes: 96 additions & 0 deletions config/axe/axe.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*jshint node: true*/
'use strict';

// Defaults derived from: https://github.com/dequelabs/axe-core
const defaults = {
rules: {
'area-alt': { 'enabled': true },
'audio-caption': { 'enabled': true },
'button-name': { 'enabled': true },
'document-title': { 'enabled': true },
'empty-heading': { 'enabled': true },
'frame-title': { 'enabled': true },
'frame-title-unique': { 'enabled': true },
'image-alt': { 'enabled': true },
'image-redundant-alt': { 'enabled': true },
'input-image-alt': { 'enabled': true },
'link-name': { 'enabled': true },
'object-alt': { 'enabled': true },
'server-side-image-map': { 'enabled': true },
'video-caption': { 'enabled': true },
'video-description': { 'enabled': true },

'definition-list': { 'enabled': true },
'dlitem': { 'enabled': true },
'heading-order': { 'enabled': true },
'href-no-hash': { 'enabled': true },
'layout-table': { 'enabled': true },
'list': { 'enabled': true },
'listitem': { 'enabled': true },
'p-as-heading': { 'enabled': true },

'scope-attr-valid': { 'enabled': true },
'table-duplicate-name': { 'enabled': true },
'table-fake-caption': { 'enabled': true },
'td-has-header': { 'enabled': true },
'td-headers-attr': { 'enabled': true },
'th-has-data-cells': { 'enabled': true },

'duplicate-id': { 'enabled': true },
'html-has-lang': { 'enabled': true },
'html-lang-valid': { 'enabled': true },
'meta-refresh': { 'enabled': true },
'valid-lang': { 'enabled': true },

'checkboxgroup': { 'enabled': true },
'label': { 'enabled': true },
'radiogroup': { 'enabled': true },

'accesskeys': { 'enabled': true },
'bypass': { 'enabled': true },
'tabindex': { 'enabled': true },

'aria-allowed-attr': { 'enabled': true },
'aria-required-attr': { 'enabled': true },
'aria-required-children': { 'enabled': true },
'aria-required-parent': { 'enabled': true },
'aria-roles': { 'enabled': true },
'aria-valid-attr': { 'enabled': true },
'aria-valid-attr-value': { 'enabled': true },

'blink': { 'enabled': true },
'color-contrast': { 'enabled': true },
'link-in-text-block': { 'enabled': true },
'marquee': { 'enabled': true },
'meta-viewport': { 'enabled': true },
'meta-viewport-large': { 'enabled': true }
}
};

module.exports = {
getConfig: () => {
const skyPagesConfigUtil = require('../sky-pages/sky-pages.config');
const skyPagesConfig = skyPagesConfigUtil.getSkyPagesConfig();

let config = {};

// Merge rules from skyux config.
if (skyPagesConfig.skyux.a11y && skyPagesConfig.skyux.a11y.rules) {
config.rules = Object.assign({}, defaults.rules, skyPagesConfig.skyux.a11y.rules);
}

// The consuming SPA wishes to disable all rules.
if (skyPagesConfig.skyux.a11y === false) {
config.rules = Object.assign({}, defaults.rules);
Object.keys(config.rules).forEach((key) => {
config.rules[key].enabled = false;
});
}

if (!config.rules) {
return defaults;
}

return config;
}
};
58 changes: 58 additions & 0 deletions lib/a11y-analyzer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*jshint node: true */
'use strict';

const axeBuilder = require('axe-webdriverjs');
const logger = require('../utils/logger');
const axeConfig = require('../config/axe/axe.config');
const { browser } = require('protractor');

function SkyA11y() {}

SkyA11y.run = function () {
return browser
.getCurrentUrl()
.then(url => new Promise((resolve) => {
const config = axeConfig.getConfig();

logger.info(`Starting accessibility checks for ${url}...`);

axeBuilder(browser.driver)
.options(config)
.analyze((results) => {
const numViolations = results.violations.length;
const subject = (numViolations === 1) ? 'violation' : 'violations';

logger.info(`Accessibility checks finished with ${numViolations} ${subject}.\n`);

if (numViolations > 0) {
logViolations(results);
}

resolve(numViolations);
});
}));
};

function logViolations(results) {
results.violations.forEach((violation) => {
const wcagTags = violation.tags
.filter(tag => tag.match(/wcag\d{3}|^best*/gi))
.join(', ');

const html = violation.nodes
.reduce(
(accumulator, node) => `${accumulator}\n${node.html}\n`,
' Elements:\n'
);

const error = [
`aXe - [Rule: \'${violation.id}\'] ${violation.help} - WCAG: ${wcagTags}`,
` Get help at: ${violation.helpUrl}\n`,
`${html}\n\n`
].join('\n');

logger.error(error);
});
}

module.exports = SkyA11y;
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@types/node": "7.0.18",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "3.1.3",
"axe-webdriverjs": "1.1.3",
"codelyzer": "3.0.1",
"core-js": "2.4.1",
"enhanced-resolve": "3.3.0",
Expand Down Expand Up @@ -82,17 +83,18 @@
"rxjs": "5.4.2",
"sass-loader": "6.0.5",
"selenium-standalone": "6.4.1",
"selenium-webdriver": "3.5.0",
"source-map-inline-loader": "blackbaud-bobbyearl/source-map-inline-loader",
"source-map-loader": "0.2.1",
"style-loader": "0.17.0",
"ts-helpers": "1.1.2",
"ts-node": "3.0.4",
"tslint": "5.2.0",
"typescript": "2.3.2",
"web-animations-js": "2.2.5",
"webpack": "2.5.1",
"webpack-dev-server": "2.4.5",
"webpack-merge": "4.1.0",
"web-animations-js": "2.2.5",
"winston": "2.3.1",
"zone.js": "0.8.10"
},
Expand Down
5 changes: 5 additions & 0 deletions runtime/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export interface RuntimeConfig {
useTemplateUrl: boolean;
}

export interface SkyuxConfigA11y {
rules?: any;
}

export interface SkyuxConfigApp {
externals?: Object;
port?: string;
Expand All @@ -36,6 +40,7 @@ export interface SkyuxConfigHost {
}

export interface SkyuxConfig {
a11y?: SkyuxConfigA11y|boolean;
app?: SkyuxConfigApp;
appSettings?: any;
auth?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions runtime/testing/e2e/a11y.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export declare class SkyA11y {
static run(): Promise<any>;
}
2 changes: 2 additions & 0 deletions runtime/testing/e2e/a11y.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const analyzer = require('../../../lib/a11y-analyzer');
module.exports = { SkyA11y: analyzer };
1 change: 1 addition & 0 deletions runtime/testing/e2e/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './host-browser';
export * from './a11y';
73 changes: 73 additions & 0 deletions test/config-axe.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*jshint jasmine: true, node: true */
'use strict';

const mock = require('mock-require');

describe('config axe', () => {
afterEach(() => {
mock.stopAll();
});

it('should return a config object', () => {
mock('../config/sky-pages/sky-pages.config', {
getSkyPagesConfig: () => {
return {
skyux: {}
};
}
});
const lib = mock.reRequire('../config/axe/axe.config');
const config = lib.getConfig();
expect(config).toBeDefined();
expect(config.rules.label.enabled).toEqual(true);
});

it('should merge config from a consuming SPA', () => {
mock('../config/sky-pages/sky-pages.config', {
getSkyPagesConfig: () => {
return {
skyux: {
a11y: {
rules: {
label: { enabled: false }
}
}
}
};
}
});
const lib = mock.reRequire('../config/axe/axe.config');
const config = lib.getConfig();
expect(config.rules.label.enabled).toEqual(false);
});

it('should return defaults if rules are not defined', () => {
mock('../config/sky-pages/sky-pages.config', {
getSkyPagesConfig: () => {
return {
skyux: {
a11y: {}
}
};
}
});
const lib = mock.reRequire('../config/axe/axe.config');
const config = lib.getConfig();
expect(config.rules.label.enabled).toEqual(true);
});

it('should disabled all rules if accessibility is set to false', () => {
mock('../config/sky-pages/sky-pages.config', {
getSkyPagesConfig: () => {
return {
skyux: {
a11y: false
}
};
}
});
const lib = mock.reRequire('../config/axe/axe.config');
const config = lib.getConfig();
expect(config.rules.label.enabled).toEqual(false);
});
});
9 changes: 5 additions & 4 deletions test/config-protractor.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ describe('config protractor test', () => {
let config;

beforeEach(() => {
lib = require('../config/protractor/protractor.conf.js');
lib = mock.reRequire('../config/protractor/protractor.conf.js');
config = lib.config;
});

afterEach(() => {
mock.stopAll();
});

it('should return a config object', () => {
expect(lib.config).toBeDefined();
});
Expand All @@ -28,9 +32,6 @@ describe('config protractor test', () => {
expect(config.beforeLaunch).toBeDefined();
config.beforeLaunch();
expect(called).toBe(true);

mock.stop('ts-node');

});

it('should provide a method for onPrepare', () => {
Expand Down
Loading

0 comments on commit 15a4bea

Please sign in to comment.