├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── app.js ├── axe-phantom.js ├── config ├── config.json └── package.json ├── cookies └── .keep ├── loadtest.js ├── module.js ├── package-lock.json ├── package.json ├── ptest1.js ├── ptest2.js ├── public ├── css │ ├── HTMLCS.css │ ├── home-print.css │ ├── home.css │ └── lib │ │ ├── bootstrap-accessibility.css │ │ ├── bootstrap-responsive.css │ │ └── bootstrap.min.css ├── img │ ├── AATT_a11y-edge_sticker.png │ ├── HTMLCS-tools.png │ ├── PayPal_logo.png │ ├── asc.gif │ ├── bg.gif │ ├── bgTexture1.gif │ ├── desc.gif │ ├── external-link-hover.png │ ├── external-link-table.png │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ ├── icon_load_roundcorner_lock1_186x42_withlock.gif │ ├── logo_347x50_PPa11y.png │ ├── summaryLoader-error.gif │ ├── summaryLoader-notice.gif │ ├── summaryLoader-warning.gif │ └── test-area-bg.png └── js │ ├── lib │ ├── __jquery.tablesorter.js │ ├── bootstrap-accessibility.min.js │ ├── bootstrap.min.js │ ├── calltable2CSV.js │ ├── jquery-1.7.1.min.js │ ├── jquery.js │ └── table2CSV.js │ └── validation.js ├── screenshots └── .keep ├── src ├── HTMLCS_Run.js ├── PAET.js ├── axe.js ├── axe │ ├── axe.js │ └── axe.min.js ├── axe_url.js ├── chrome.js ├── chrome │ └── axs_testing.js ├── chrome_url.js ├── home.js ├── htmlcs │ ├── .jshintrc │ ├── HTMLCS.js │ └── HTMLCS.min.js ├── login.js ├── runner_html.js ├── runner_json.js └── screenshot.js ├── test ├── AATT_API.html ├── Accessibility Fails_files │ ├── animated-tree.gif │ ├── bbc-blocks-dark.png │ ├── decoration.png │ ├── demo.html │ ├── demo_002.html │ ├── foo.html │ ├── jquery-1.js │ ├── main.css │ ├── main.js │ ├── normalize.css │ ├── red_panda.jpg │ ├── spacer.gif │ └── submit.png ├── Fails.html ├── bs_modal_dynamic │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ ├── donate.html │ └── jquery.js ├── css │ └── home.css ├── imgs │ ├── asc.gif │ ├── bg.gif │ ├── desc.gif │ └── screenshots │ │ └── Home_ScreenShot.png ├── index.html ├── js │ ├── main.js │ ├── sorttable.js │ └── util.js └── tooltest │ ├── allTests.html │ ├── lowcontrast.gif │ └── paypal-logo1x.svg ├── tmp ├── .keep ├── bootstrap.min.css ├── bootstrap.min.js └── jquery.js └── views ├── bookmark.html ├── help.html ├── home.html └── index.html /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | ./node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | 16 | node_modules 17 | 18 | .DS_Store 19 | .idea 20 | screenshots/* 21 | 22 | tmp/*.html 23 | cookies/*.txt 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.9.4 2 | 3 | RUN apt-get update && apt-get install -y openssh-client git && rm -rf /var/lib/apt/lists/* 4 | COPY . /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | RUN npm install 8 | RUN git submodule init 9 | RUN git submodule update 10 | 11 | EXPOSE 3000 12 | ENTRYPOINT DEBUG=AATT* http_port=3000 node app.js -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, PayPal 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the PayPal nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automated Accessibility Testing Tool (AATT) 2 | 3 | Browser-based accessibility testing tools and plugins require manually testing each page, one at a time. Tools that can crawl a website can only scan pages that do not require login credentials, and that are not behind a firewall. Instead of developing, testing, and using a separate accessibility test suite, you can now integrate accessibility testing into your existing automation test suite using AATT. 4 | 5 | AATT tests web applications regarding conformance to the Web Content Accessibility Guidelines (WCAG) 2.1 (for axe engine). 6 | 7 | AATT provides an accessibility API and custom web application for [HTML CodeSniffer](http://squizlabs.github.io/HTML_CodeSniffer/), [Axe](https://github.com/dequelabs/axe-core) and [Chrome developer tool](https://github.com/GoogleChrome/accessibility-developer-tools). Using the AATT web application, you can configure test server configurations inside the firewall, and test individual pages. 8 | 9 | AATT includes [HTML CodeSniffer](http://squizlabs.github.io/HTML_CodeSniffer/), [Axe](https://github.com/dequelabs/axe-core) and [Chrome developer tool](https://github.com/GoogleChrome/accessibility-developer-tools) with Express and PhantomJS, which runs on Node. 10 | 11 | For example, it can be used to test Java web applications using [SeLion](https://github.com/paypal/selion/) automation test frameworks. 12 | 13 | For node applications, it can be integrated into [NemoJS testing framework](https://github.com/paypal/nemo) to run accessibility testing during automated unit testing .For Nemo framework use [Nemo-Accessibility plugin] (https://github.com/paypal/nemo-accessibility) 14 | 15 | 16 | ## Set up 17 | 18 | ```sh 19 | $ git clone https://github.com/paypal/AATT.git 20 | $ cd AATT 21 | $ npm i 22 | $ DEBUG=AATT* http_port=3000 node app.js . (If you want to run in Debug mode printing logs) 23 | ``` 24 | $sudo node app.js will run in default port 80 without printing log information 25 | 26 | You can now access the running instance of AATT from http://localhost:3000 27 | 28 | ## Integration with AATT API 29 | AATT provides an API for evaluating HTML Source code from other servers. The API EndPoint is: https://your_nodejs_server/evaluate 30 | 31 | * Accepts the following OPTIONAL parameters: 32 | 1. "source" to send the HTML source of the page. Can be a whole page or partial page source. Defaults to document 33 | 2. "engine" E.g. engine=htmlcs. This is the engine which will scan the code. It accepts a single value of "axe", chrome" or "htmlcs". Defaults to axe 34 | 3. "ouput" to get the jsonified string. E.g. output=json. If this parameter is not set or left empty, it will return a string with table data that can be parsed or appended directly into your page. Defaults to json. 35 | 36 | 4. "errLevel" Error level like Error, Warning or Notices . Mapped to 1, 2 and 3 respectively. E.g. "1,2,3" . (For HTMLCS engine) 37 | 5. "level" This option applies only for the default htmlcs evaluation engine. Options can be either of the following WCAG2AA, WCAG2A, WCAG2AAA, Section508 . Defaults to "WCAG2AA" (For HTMLCS engine) 38 | 39 | 40 | * Set the Request Header Content-type as application/x-www-form-urlencoded 41 | 42 | ## Example 43 | 44 | Here is a sample ajax script which would initiate the request: 45 | 46 | ``` html 47 | var xmlhttp = new XMLHttpRequest(); 48 | xmlhttp.open("POST","http://your_nodejs_server/evaluate",true); 49 | xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 50 | xmlhttp.send("source=" + document.getElementById('source').value + "&priority=" + document.getElementById('priority').value); 51 | 52 | ``` 53 | ## How to Use with nemo-accessibility as a plugin 54 | 55 | [Nemo](https://github.com/paypal/nemo) is a node.js based automation framework for browser automation. It's plugin-architecture helps switch on/off different capabilities. The [nemo-accessibility](https://github.com/paypal/nemo-accessibility) plugin performs accessibility scanning while running browser automation using [Nemo framework](https://github.com/paypal/nemo). 56 | 57 | [Learn more about nemo](https://github.com/paypal/nemo) 58 | 59 | `nemo-accessibility` plugin uses the AATT accessibility API to evaluate HTML source. Therefore you must specify the API url under as a plugin argument like below. 60 | 61 | ```json 62 | "nemo-accessibility":{ 63 | "module":"nemo-accessibility", 64 | "arguments": ["https://your_nodejs_accessibility_server/evaluate"] 65 | } 66 | ``` 67 | 68 | ## How to Use with nightwatchJS 69 | [Nightwatch JS](http://nightwatchjs.org ) is another UI automated testing framework powered by Node.js and uses the Selenium WebDriver API. To call AATT, you need to use the [request module](https://github.com/request/request). NightwatchJs has call back functions like before and after hooks that would be called before or after executing a test case. Request to AATT API should be done in after hook passing the source code of the page to the API. Here is an [example commit](https://github.com/mpnkhan/nightwatch/commit/a377e860e0bfbd21d9e365e86fb3e6c4ec0e63f0) on how to do this with Nightwatch. 70 | 71 | ## How to use as a node module 72 | 73 | The AATT evaluate function can be used directly as a node module, without the 74 | need for using a web API. 75 | 76 | ### Installation 77 | 78 | Add the module to your project 79 | 80 | ```sh 81 | npm install --save aatt 82 | ``` 83 | 84 | ### Usage Example 85 | 86 | This takes the same options as the web `/evaluate` HTTP endpoint. 87 | 88 | ```javascript 89 | const { evaluate } = require('aatt'); 90 | 91 | evaluate({ 92 | source: "
Bar
", 93 | output: "json", 94 | engine: "htmlcs", 95 | level: "WCAG2A" 96 | }).then(result => { 97 | console.log('Results', JSON.parse(result)); 98 | }); 99 | ``` 100 | 101 | ## Copyright and License 102 | 103 | Copyright 2021, PayPal under [the BSD license](LICENSE.md). 104 | 105 | [1]: https://yourhostname/evaluate "AATT api" 106 | 107 | ## Contributors 108 | - Prem Nawaz Khan, developer || [https://github.com/mpnkhan](https://github.com/mpnkhan) || [@mpnkhan](https://twitter.com/mpnkhan) 109 | - Cathy O'Connor, design || [@cagocon](https://twitter.com/cagocon) 110 | - Srinivasu Chakravarthula, user interaction, testing || @csrinivasu 111 | 112 | ## Feedback 113 | We welcome your feedback. Please file issues and/or enhancement requests. 114 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | //R E Q U I R E S 2 | var express = require('express') 3 | var app = express(); 4 | var http = require('http'); 5 | var https = require('https'); 6 | var cons = require('consolidate') 7 | var childProcess = require('child_process'); 8 | var path =require('path'); 9 | var phantomjs = require('phantomjs-prebuilt'); 10 | var binPath = phantomjs.path 11 | var fs = require('fs'); 12 | var bodyParser = require('body-parser'); 13 | var session = require('express-session'); 14 | var debug = require('debug'); 15 | var log = debug('AATT:log'); 16 | var error = debug('AATT:error'); 17 | var nconf = require('nconf'); 18 | var { evaluate } = require('./module.js'); 19 | 20 | nconf.env().argv(); 21 | 22 | // B A S I C C O N F I G 23 | var http_port = nconf.get('http_port') || 80; // Start with Sudo for starting in port 80 or 443 24 | var https_port = nconf.get('https_port') || 443; 25 | var ssl_path= 'cert/ssl.key'; 26 | var cert_file = 'cert/abc.cer'; 27 | 28 | 29 | app.set('views', __dirname + '/views'); 30 | app.engine('html', cons.handlebars); 31 | app.set('view engine', 'html'); 32 | 33 | app.use(bodyParser.json({limit: '100mb'})); 34 | app.use(bodyParser.urlencoded({limit: '100mb', extended: true})); 35 | 36 | app.use(session({ resave: true, 37 | saveUninitialized: true, 38 | secret: 'uwotm8' })); 39 | 40 | app.use(express.static(path.join(__dirname, 'public'))); 41 | app.use(express.static(path.join(__dirname, 'src'))); 42 | app.use('/test', express.static(__dirname + '/test')); 43 | app.use('/src', express.static(__dirname + '/src')); 44 | 45 | app.use('/screenshots', express.static(__dirname + '/screenshots')); 46 | app.use('/Auditor',express.static(path.join(__dirname, 'src/HTML_CodeSniffer/Auditor'))); 47 | app.use('/Images',express.static(path.join(__dirname, 'src/HTML_CodeSniffer/Auditor/Images'))); 48 | 49 | 50 | if (fs.existsSync(ssl_path)) { 51 | var hskey = fs.readFileSync(ssl_path); 52 | var hscert = fs.readFileSync(cert_file) ; 53 | var options = { 54 | key: hskey, 55 | cert: hscert 56 | }; 57 | var httpsServer = https.createServer(options, app); 58 | httpsServer.listen(https_port); 59 | log('Express started on port ' , https_port); 60 | 61 | } else { 62 | var server = http.createServer(app); 63 | app.listen(http_port); 64 | log('Express started on port ', http_port); 65 | } 66 | 67 | app.get('/', function(req, res) { 68 | res.render('index.html',{data:''}); 69 | }); 70 | 71 | app.get('/help', function(req, res) { 72 | res.render('help.html'); 73 | }); 74 | 75 | app.get('/getURL', function(req, res) { 76 | res.render('index.html',{data:''}); 77 | }); 78 | 79 | app.post('/sniffURL', function(req, res) { 80 | var childArgs 81 | , userName = '' 82 | , d = new Date() 83 | , customDateString = d.getHours() +'_' + d.getMinutes() + '_' + d.getSeconds() +'_'+ d.getMonth() + '_' + d.getDate() + '_' + d.getFullYear() 84 | , dirName = "screenshots/" + customDateString 85 | , scrshot = req.body.scrshot 86 | , msgErr = req.body.msgErr 87 | , msgWarn = req.body.msgWarn 88 | , msgNotice = req.body.msgNotice 89 | , eLevel=[] 90 | , engine = req.body.engine 91 | , output ='string' 92 | 93 | if(typeof engine === 'undefined' || engine ==='') engine = 'htmlcs'; 94 | if(typeof output === 'undefined' || output ==='') output = 'string'; 95 | 96 | log('E N G I N E ', engine); 97 | 98 | if (typeof req.session.userName !== 'undefined') { 99 | userName = req.session.userName; 100 | log('Testing logged in session: -> ', userName) 101 | } 102 | if(engine === 'htmlcs'){ 103 | if(typeof msgErr !== 'undefined' && msgErr=='true') eLevel.push(1); 104 | if(typeof msgWarn !== 'undefined' && msgWarn=='true') eLevel.push(2); 105 | if(typeof msgNotice !== 'undefined' && msgNotice=='true') eLevel.push(3); 106 | 107 | //Default to Error 108 | if(typeof msgErr === 'undefined' && typeof msgWarn === 'undefined' && typeof msgNotice === 'undefined') eLevel.push(1); 109 | 110 | if(typeof scrshot !== 'undefined' && scrshot === 'true') fs.mkdirSync(dirName); //Create SCREEN SHOT DIRECTORY 111 | 112 | childArgs = ['--config=config/config.json', path.join(__dirname, 'src/PAET.js') 113 | , req.body.textURL 114 | , 'WCAG2AA' 115 | , userName 116 | , dirName 117 | , scrshot 118 | , eLevel 119 | ]; 120 | } 121 | if(engine === 'axe'){ 122 | childArgs = ['--config=config/config.json', path.join(__dirname, 'src/axe_url.js'), req.body.textURL, output]; 123 | } 124 | if(engine === 'chrome'){ 125 | childArgs = ['--config=config/config.json', path.join(__dirname, 'src/chrome_url.js'), req.body.textURL, output]; 126 | } 127 | childProcess.execFile(binPath, childArgs, function(err, stdout, stderr) { 128 | res.json({ userName: userName, data: stdout }); 129 | log(stdout); 130 | }); 131 | }); 132 | 133 | app.post('/sniffHTML', function(req, res) { 134 | var childArgs 135 | , userName = '' 136 | , d = new Date() 137 | , tempFilename = 'tmp/'+ new Date().getTime() + '.html' 138 | , engine = req.body.engine 139 | , output ='string' 140 | 141 | if(typeof engine === 'undefined' || engine ==='') engine = 'htmlcs'; 142 | if(typeof output === 'undefined' || output ==='') output = 'string'; 143 | 144 | var source = req.body.source; 145 | source = source.replace(/ 99 | 100 |