├── .gitignore ├── README.md ├── package-lock.json ├── package.json └── spec ├── custom-rule-test.js ├── custom-rule.json ├── remote-url-test.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | dist 3 | bower_components 4 | node_modules 5 | spec/support/* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # axe-webdriverjs demo 2 | 3 | How to set up aXe with Selenium WebdriverJS and Jasmine for automated testing. 4 | 5 | ## Requirements 6 | * Node.js 7 | * npm 8 | * Chrome browser 9 | 10 | ## Installation 11 | 12 | If you don't already have it, [download Chrome driver](https://sites.google.com/a/chromium.org/chromedriver/downloads) and put it in a location on your system $PATH. 13 | 14 | >For more info on setup and testing other browsers, review the [selenium-webriver documentation](https://www.npmjs.com/package/selenium-webdriver). 15 | 16 | Install dependencies (including Jasmine): 17 | ``` 18 | npm install 19 | ``` 20 | 21 | ### Demo Project 22 | 23 | The demo tests in `spec/test.js` require eBay's Accessibility Pattern examples to be running locally at `http://localhost:4000`. You can follow along with the project by cloning the repository and setting it up on your local machine: https://github.com/ianmcburnie/mindpatterns/ 24 | 25 | ## Usage: Run tests 26 | 27 | ``` 28 | npm test 29 | ``` -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axe-webdriverjs-demo", 3 | "version": "1.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "adm-zip": { 8 | "version": "0.4.4", 9 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.4.tgz", 10 | "integrity": "sha1-ph7VrmkFw66lizplfSUDMJEFJzY=" 11 | }, 12 | "axe-core": { 13 | "version": "2.4.2", 14 | "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-2.4.2.tgz", 15 | "integrity": "sha512-RyAyvCYPJ5y/Ov2mY67kuZG8SfxIAOAI0KuJ9nRXX/vDCL/RTcArGgtn5i2+uZjL23GC6eqMaJ6GjUEDEe11tw==" 16 | }, 17 | "axe-webdriverjs": { 18 | "version": "1.1.5", 19 | "resolved": "https://registry.npmjs.org/axe-webdriverjs/-/axe-webdriverjs-1.1.5.tgz", 20 | "integrity": "sha512-Nsb/6gyIGYFmQbo3NP14MZghNQff5m+v8D4ex4baad6DvtFzg6T843WZvbdb5F9oHCUWnXy5cgw4LhEpj4ffww==", 21 | "requires": { 22 | "axe-core": "2.4.2" 23 | } 24 | }, 25 | "balanced-match": { 26 | "version": "1.0.0", 27 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 28 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 29 | }, 30 | "brace-expansion": { 31 | "version": "1.1.8", 32 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 33 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 34 | "requires": { 35 | "balanced-match": "1.0.0", 36 | "concat-map": "0.0.1" 37 | } 38 | }, 39 | "concat-map": { 40 | "version": "0.0.1", 41 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 42 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 43 | }, 44 | "exit": { 45 | "version": "0.1.2", 46 | "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", 47 | "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" 48 | }, 49 | "fs.realpath": { 50 | "version": "1.0.0", 51 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 52 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 53 | }, 54 | "glob": { 55 | "version": "7.1.2", 56 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 57 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 58 | "requires": { 59 | "fs.realpath": "1.0.0", 60 | "inflight": "1.0.6", 61 | "inherits": "2.0.3", 62 | "minimatch": "3.0.4", 63 | "once": "1.4.0", 64 | "path-is-absolute": "1.0.1" 65 | } 66 | }, 67 | "inflight": { 68 | "version": "1.0.6", 69 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 70 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 71 | "requires": { 72 | "once": "1.4.0", 73 | "wrappy": "1.0.2" 74 | } 75 | }, 76 | "inherits": { 77 | "version": "2.0.3", 78 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 79 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 80 | }, 81 | "jasmine": { 82 | "version": "2.8.0", 83 | "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", 84 | "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=", 85 | "requires": { 86 | "exit": "0.1.2", 87 | "glob": "7.1.2", 88 | "jasmine-core": "2.8.0" 89 | } 90 | }, 91 | "jasmine-core": { 92 | "version": "2.8.0", 93 | "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", 94 | "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=" 95 | }, 96 | "minimatch": { 97 | "version": "3.0.4", 98 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 99 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 100 | "requires": { 101 | "brace-expansion": "1.1.8" 102 | } 103 | }, 104 | "once": { 105 | "version": "1.4.0", 106 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 107 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 108 | "requires": { 109 | "wrappy": "1.0.2" 110 | } 111 | }, 112 | "options": { 113 | "version": "0.0.6", 114 | "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", 115 | "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" 116 | }, 117 | "path-is-absolute": { 118 | "version": "1.0.1", 119 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 120 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 121 | }, 122 | "rimraf": { 123 | "version": "2.6.2", 124 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 125 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 126 | "requires": { 127 | "glob": "7.1.2" 128 | } 129 | }, 130 | "sax": { 131 | "version": "0.6.1", 132 | "resolved": "https://registry.npmjs.org/sax/-/sax-0.6.1.tgz", 133 | "integrity": "sha1-VjsZx8HeiS4Jv8Ty/DDjwn8JUrk=" 134 | }, 135 | "selenium-webdriver": { 136 | "version": "2.53.3", 137 | "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.53.3.tgz", 138 | "integrity": "sha1-0p/1qVff8aG0ncRXdW5OS/vc4IU=", 139 | "requires": { 140 | "adm-zip": "0.4.4", 141 | "rimraf": "2.6.2", 142 | "tmp": "0.0.24", 143 | "ws": "1.1.4", 144 | "xml2js": "0.4.4" 145 | } 146 | }, 147 | "tmp": { 148 | "version": "0.0.24", 149 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", 150 | "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=" 151 | }, 152 | "ultron": { 153 | "version": "1.0.2", 154 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", 155 | "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" 156 | }, 157 | "wrappy": { 158 | "version": "1.0.2", 159 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 160 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 161 | }, 162 | "ws": { 163 | "version": "1.1.4", 164 | "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.4.tgz", 165 | "integrity": "sha1-V/QNA2gy5fUFVmKjl8Tedu1mv2E=", 166 | "requires": { 167 | "options": "0.0.6", 168 | "ultron": "1.0.2" 169 | } 170 | }, 171 | "xml2js": { 172 | "version": "0.4.4", 173 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.4.tgz", 174 | "integrity": "sha1-MREBAAMAiuGSQOuhdJe1fHKcVV0=", 175 | "requires": { 176 | "sax": "0.6.1", 177 | "xmlbuilder": "9.0.4" 178 | } 179 | }, 180 | "xmlbuilder": { 181 | "version": "9.0.4", 182 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", 183 | "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=" 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axe-webdriverjs-demo", 3 | "version": "1.0.1", 4 | "description": "How to set up aXe with WebdriverJS for automated testing.", 5 | "scripts": { 6 | "postinstall": "jasmine init", 7 | "test": "jasmine spec/custom-rule-test.js" 8 | }, 9 | "author": { 10 | "name": "Marcy Sutton", 11 | "organization": "Deque Systems", 12 | "url": "http://deque.com" 13 | }, 14 | "license": "ISC", 15 | "dependencies": { 16 | "axe-webdriverjs": "^1.1.5", 17 | "jasmine": "^2.8.0", 18 | "selenium-webdriver": "^2.53.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/custom-rule-test.js: -------------------------------------------------------------------------------- 1 | var selenium = require('selenium-webdriver'), 2 | AxeBuilder = require('axe-webdriverjs'); 3 | var util = require('util'); 4 | 5 | var driver, browser; 6 | 7 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; 8 | 9 | var customRuleJson = require('./custom-rule.json'); 10 | 11 | describe('Selenium-aXe Tutorial', function() { 12 | 13 | // Open the MarsCommuter website in the browser before each test is run 14 | beforeEach(function(done) { 15 | driver = new selenium.Builder() 16 | .forBrowser('chrome'); 17 | 18 | browser = driver.build(); 19 | 20 | browser.manage().timeouts().setScriptTimeout(60000); 21 | 22 | browser.get('https://dequeuniversity.com/demo/mars/').then(function () { 23 | browser.executeAsyncScript(function(callback) { 24 | var script = document.createElement('script'); 25 | script.innerHTML = 'document.documentElement.classList.add("deque-axe-is-ready");'; 26 | document.documentElement.appendChild(script); 27 | callback(); 28 | }) 29 | .then(function () { 30 | return browser.wait(selenium.until.elementsLocated(selenium.By.css('.deque-axe-is-ready'))); 31 | }) 32 | .then(function () { 33 | done(); 34 | }); 35 | }); 36 | }); 37 | 38 | // Close the website after each test is run (so that it is opened fresh each time) 39 | afterEach(function(done) { 40 | browser.quit().then(function () { 41 | done(); 42 | }); 43 | }); 44 | 45 | it('should fetch the home page and analyze it', function (done) { 46 | browser.findElement(selenium.By.tagName('body')) 47 | .then(function () { 48 | AxeBuilder(browser) 49 | .configure(customRuleJson) 50 | .analyze(function(results) { 51 | console.log('Accessibility Violations: ', results.violations.length); 52 | if (results.violations.length > 0 || results.incomplete.length > 0) { 53 | // console.log(util.inspect(results.violations, { showHidden: true, depth: 8 })); 54 | // console.log(util.inspect(results.incomplete, { showHidden: true, depth: 5 })); 55 | } 56 | console.log(results); 57 | // expect(results.violations.length).toBe(0); 58 | // expect(results.incomplete.length).toBe(0); 59 | done(); 60 | }) 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /spec/custom-rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "checks": [ 3 | { 4 | "id": "dylang", 5 | "options": [ 6 | "dylan" 7 | ], 8 | "evaluate": "function (node, options) {\n var lang = (node.getAttribute(\"lang\") || \"\").trim().toLowerCase();\n var xmlLang = (node.getAttribute(\"xml:lang\") || \"\").trim().toLowerCase();\n var invalid = [];\n (options || []).forEach(function(cc) {\n cc = cc.toLowerCase();\n if (lang && (lang === cc || lang.indexOf(cc.toLowerCase() + \"-\") === 0)) {\n lang = null;\n }\n if (xmlLang && (xmlLang === cc || xmlLang.indexOf(cc.toLowerCase() + \"-\") === 0)) {\n xmlLang = null;\n }\n });\n if (xmlLang) {\n invalid.push('xml:lang=\"' + xmlLang + '\"');\n }\n if (lang) {\n invalid.push('lang=\"' + lang + '\"');\n }\n if (invalid.length) {\n this.data(invalid);\n return true;\n }\n return false;\n }", 9 | "messages": { 10 | "pass": "Good language", 11 | "fail": "You must use the DYLAN language" 12 | } 13 | }], 14 | "rules": [ 15 | { 16 | "id": "dylang", 17 | "metadata": { 18 | "description": "Ensures lang attributes have the value of 'dylan'", 19 | "help": "lang attribute must have the value of 'dylan'" 20 | }, 21 | "selector": "html", 22 | "any": [], 23 | "all": [], 24 | "none": [ 25 | "dylang" 26 | ], 27 | "tags": [ 28 | "wcag2aa" 29 | ] 30 | }], 31 | "data": { 32 | "rules": { 33 | "dylang": { 34 | "description": "Ensures lang attributes have the value of 'dylan'", 35 | "help": "lang attribute must have the value of 'dylan'" 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /spec/remote-url-test.js: -------------------------------------------------------------------------------- 1 | var selenium = require('selenium-webdriver'), 2 | AxeBuilder = require('axe-webdriverjs'); 3 | 4 | var driver, browser; 5 | 6 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; 7 | 8 | var util = require('util'); 9 | 10 | describe('Selenium-aXe Tutorial', function() { 11 | 12 | // Open the Deque website in the browser before each test is run 13 | beforeEach(function(done) { 14 | driver = new selenium.Builder() 15 | .forBrowser('chrome'); 16 | 17 | browser = driver.build(); 18 | 19 | browser.manage().timeouts().setScriptTimeout(60000); 20 | 21 | browser.get('http://www.deque.com/').then(function () { 22 | browser.executeAsyncScript(function(callback) { 23 | var script = document.createElement('script'); 24 | script.innerHTML = 'document.documentElement.classList.add("deque-axe-is-ready");'; 25 | document.documentElement.appendChild(script); 26 | callback(); 27 | }) 28 | .then(function () { 29 | return browser.wait(selenium.until.elementsLocated(selenium.By.css('.deque-axe-is-ready'))); 30 | }) 31 | .then(function(){ 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | // Close the website after each test is run (so that it is opened fresh each time) 38 | afterEach(function(done) { 39 | browser.quit().then(function () { 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should just fetch the home page and analyze it', function (done) { 45 | browser.findElement(selenium.By.css('body.home')).then(function (element) { 46 | return element.getAttribute('class').then(function (className) { 47 | expect(className.indexOf('home')).toBe(0); 48 | }); 49 | }).then(function () { 50 | AxeBuilder(browser) 51 | .analyze(function(results) { 52 | console.log('Accessibility Violations: ', results.violations.length); 53 | if (results.violations.length > 0) { 54 | console.log(util.inspect(results.violations, { showHidden: true, depth: 8 })); 55 | } 56 | console.log('Needs review: ', results.incomplete.length); 57 | if (results.incomplete.length > 0) { 58 | console.log(util.inspect(results.incomplete, { showHidden: true, depth: 8 })); 59 | } 60 | expect(results.violations.length).toBe(0); 61 | done(); 62 | }) 63 | }); 64 | }); 65 | }); -------------------------------------------------------------------------------- /spec/test.js: -------------------------------------------------------------------------------- 1 | var selenium = require('selenium-webdriver'), 2 | AxeBuilder = require('axe-webdriverjs'), 3 | Key = selenium.Key; 4 | 5 | var util = require('util'); 6 | 7 | var driver; 8 | 9 | describe('Radio button demo', function() { 10 | 11 | beforeEach(function(done) { 12 | driver = new selenium.Builder() 13 | .forBrowser('chrome') 14 | .build(); 15 | 16 | driver.get('http://localhost:4000/radio/') 17 | .then(function () { 18 | done(); 19 | }); 20 | }); 21 | 22 | // Close website after each test is run (so it is opened fresh each time) 23 | afterEach(function(done) { 24 | driver.quit().then(function () { 25 | done(); 26 | }); 27 | }); 28 | 29 | it('should change state with the keyboard', function() { 30 | var selector = 'span[role="radio"][aria-labelledby="radiogroup-0-label-0"]'; 31 | 32 | driver.findElement(selenium.By.css(selector)) 33 | .then(function (element) { 34 | element.sendKeys(Key.SPACE); 35 | return element; 36 | }) 37 | .then(function (element) { 38 | return element.getAttribute('aria-checked') 39 | }) 40 | .then(function (attr) { 41 | expect(attr).toEqual('true'); 42 | }); 43 | }); 44 | 45 | it('should fetch the radio button demo and analyze it', function (done) { 46 | driver.findElement(selenium.By.css('.radio_')) 47 | .then(function () { 48 | AxeBuilder(driver) 49 | .analyze(function(results) { 50 | console.log('Accessibility Violations: ', results.violations.length); 51 | if (results.violations.length > 0) { 52 | console.log(util.inspect(results.violations, true, null)); 53 | } 54 | expect(results.violations.length).toBe(0); 55 | done(); 56 | }) 57 | }); 58 | }); 59 | }); --------------------------------------------------------------------------------