Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

pa11y-ci

Package Overview
Dependencies
Maintainers
7
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pa11y-ci - npm Package Compare versions

Comparing version 2.4.2 to 3.0.0

lib/helpers/defaults.js

73

bin/pa11y-ci.js

@@ -54,2 +54,5 @@ #!/usr/bin/env node

'0'
).option(
'--reporter <reporter>',
'the reporter to use. Can be a npm module or a path to a local file.'
)

@@ -78,2 +81,6 @@ .parse(process.argv);

.then(config => {
// Override config reporters with CLI argument
if (commander.reporter) {
config.defaults.reporters = [commander.reporter];
}
// Actually run Pa11y CI

@@ -147,3 +154,3 @@ return pa11yCi(urls.concat(config.urls || []), config.defaults);

configPath = configPath || '.pa11yci';
if (configPath[0] !== '/') {
if (!path.isAbsolute(configPath)) {
configPath = path.join(process.cwd(), configPath);

@@ -210,4 +217,4 @@ }

program.sitemapFind ?
new RegExp(program.sitemapFind, 'gi') :
null
new RegExp(program.sitemapFind, 'gi') :
null
);

@@ -217,4 +224,4 @@ const sitemapReplace = program.sitemapReplace || '';

program.sitemapExclude ?
new RegExp(program.sitemapExclude, 'gi') :
null
new RegExp(program.sitemapExclude, 'gi') :
null
);

@@ -224,35 +231,35 @@

return Promise.resolve()
.then(() => fetch(sitemapUrl))
.then(response => response.text())
.then(body => {
const $ = cheerio.load(body, {xmlMode: true});
.then(() => fetch(sitemapUrl))
.then(response => response.text())
.then(body => {
const $ = cheerio.load(body, {xmlMode: true});
const isSitemapIndex = $('sitemapindex').length > 0;
if (isSitemapIndex) {
return Promise.all($('sitemap > loc').toArray().map(element => {
return getUrlsFromSitemap($(element).text(), config);
})).then(configs => {
return configs.pop();
const isSitemapIndex = $('sitemapindex').length > 0;
if (isSitemapIndex) {
return Promise.all($('sitemap > loc').toArray().map(element => {
return getUrlsFromSitemap($(element).text(), config);
})).then(configs => {
return configs.pop();
});
}
$('url > loc').toArray().forEach(element => {
let url = $(element).text();
if (sitemapExclude && url.match(sitemapExclude)) {
return;
}
if (sitemapFind) {
url = url.replace(sitemapFind, sitemapReplace);
}
config.urls.push(url);
});
}
$('url > loc').toArray().forEach(element => {
let url = $(element).text();
if (sitemapExclude && url.match(sitemapExclude)) {
return;
return config;
})
.catch(error => {
if (error.stack && error.stack.includes('node-fetch')) {
throw new Error(`The sitemap "${sitemapUrl}" could not be loaded`);
}
if (sitemapFind) {
url = url.replace(sitemapFind, sitemapReplace);
}
config.urls.push(url);
throw new Error(`The sitemap "${sitemapUrl}" could not be parsed`);
});
return config;
})
.catch(error => {
if (error.stack && error.stack.includes('node-fetch')) {
throw new Error(`The sitemap "${sitemapUrl}" could not be loaded`);
}
throw new Error(`The sitemap "${sitemapUrl}" could not be parsed`);
});
}

@@ -259,0 +266,0 @@

@@ -8,14 +8,10 @@ //

const chalk = require('chalk');
const defaults = require('lodash/defaultsDeep');
const omit = require('lodash/omit');
const pa11y = require('pa11y');
const queue = require('async/queue');
const wordwrap = require('wordwrap');
const puppeteer = require('puppeteer');
const resolveReporters = require('./helpers/resolver');
const defaultCfg = require('./helpers/defaults');
// Just an empty function to use as default
// configuration and arguments
/* istanbul ignore next */
// eslint-disable-next-line no-empty-function
const noop = () => {};

@@ -26,14 +22,19 @@ // Here's the exports. `pa11yCi` is defined further down the

function cycleReporters(reporters, method, ...args) {
if (!reporters.length) {
return Promise.resolve();
}
return Promise.all(reporters.map(reporter => {
if (typeof reporter[method] === 'function') {
return reporter[method](...args);
}
return false;
}));
}
// The default configuration object. This is extended with
// whatever configurations the user passes in from the
// command line
module.exports.defaults = {
concurrency: 2,
log: {
error: noop,
info: noop
},
wrapWidth: 80,
useIncognitoBrowserContext: false
};
module.exports.defaults = defaultCfg;

@@ -44,2 +45,3 @@ // This function does all the setup and actually runs Pa11y

function pa11yCi(urls, options) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async resolve => {

@@ -67,8 +69,13 @@ // Create a test browser to assign to tests

// Resolve reporters
const reporters = resolveReporters(options);
// We delete options.log because we don't want it to
// get passed into Pa11y – we don't want super verbose
// logs from it
const log = options.log;
delete options.log;
// we also remove reporters because it's not part of pa11y options
options = omit(options, ['log', 'reporters']);
await cycleReporters(reporters, 'beforeAll', urls);
// Create a Pa11y test function and an async queue

@@ -79,3 +86,2 @@ const taskQueue = queue(testRunner, options.concurrency);

// Push the URLs on to the queue
log.info(chalk.cyan.underline(`Running Pa11y on ${urls.length} URLs:`));
taskQueue.push(urls);

@@ -92,22 +98,11 @@

function processResults(results, reportConfig, url) {
function processResults(results, reportConfig) {
const withinThreshold = reportConfig.threshold ?
results.issues.length <= reportConfig.threshold :
false;
let message = ` ${chalk.cyan('>')} ${url} - `;
results.issues.length <= reportConfig.threshold :
false;
if (results.issues.length && !withinThreshold) {
message += chalk.red(`${results.issues.length} errors`);
log.error(message);
report.results[url] = results.issues;
report.results[results.pageUrl] = results.issues;
report.errors += results.issues.length;
} else {
message += chalk.green(`${results.issues.length} errors`);
if (withinThreshold) {
message += chalk.green(
` (within threshold of ${reportConfig.threshold})`
);
}
log.info(message);
report.results[url] = [];
report.results[results.pageUrl] = [];
report.passes += 1;

@@ -128,2 +123,5 @@ }

}
await cycleReporters(reporters, 'begin', url);
config.browser = config.useIncognitoBrowserContext ?

@@ -137,5 +135,6 @@ await testBrowser.createIncognitoBrowserContext() :

const results = await pa11y(url, config);
processResults(results, config, url);
await cycleReporters(reporters, 'results', results, config);
processResults(results, config);
} catch (error) {
log.error(` ${chalk.cyan('>')} ${url} - ${chalk.red('Failed to run')}`);
await cycleReporters(reporters, 'error', error, url, config);
report.results[url] = [error];

@@ -153,40 +152,6 @@ } finally {

function testRunComplete() {
const passRatio = `${report.passes}/${report.total} URLs passed`;
testBrowser.close();
if (report.passes === report.total) {
log.info(chalk.green(`\n✔ ${passRatio}`));
} else {
// Now we loop over the errors and output them with
// word wrapping
const wrap = wordwrap(3, options.wrapWidth);
Object.keys(report.results).forEach(url => {
if (report.results[url].length) {
log.error(chalk.underline(`\nErrors in ${url}:`));
report.results[url].forEach(result => {
const redBullet = chalk.red('•');
if (result instanceof Error) {
log.error(`\n ${redBullet} Error: ${wrap(result.message).trim()}`);
} else {
const context = result.context ?
result.context.replace(/\s+/g, ' ') :
'[no context]';
log.error([
'',
` ${redBullet} ${wrap(result.message).trim()}`,
'',
chalk.grey(wrap(`(${result.selector})`)),
'',
chalk.grey(wrap(context))
].join('\n'));
}
});
}
});
log.error(chalk.red(`\n✘ ${passRatio}`));
}
// Resolve the promise with the report
resolve(report);
cycleReporters(reporters, 'afterAll', report, options).then(() => resolve(report));
}

@@ -193,0 +158,0 @@

{
"name": "pa11y-ci",
"version": "2.4.2",
"version": "3.0.0",
"description": "Pa11y CI is a CI-centric accessibility test runner, built using Pa11y",

@@ -8,3 +8,3 @@ "keywords": [],

"contributors": [
"Rowan Manning (http://rowanmanning.com/)"
"Rowan Manning (https://rowanmanning.com/)"
],

@@ -19,26 +19,25 @@ "repository": {

"engines": {
"node": ">=8"
"node": ">=12"
},
"dependencies": {
"async": "~2.6.3",
"chalk": "~1.1.3",
"cheerio": "~1.0.0-rc.3",
"commander": "~2.20.3",
"cheerio": "~1.0.0-rc.10",
"commander": "~6.2.1",
"globby": "~6.1.0",
"lodash": "~4.17.20",
"node-fetch": "~2.6.0",
"pa11y": "~5.3.1",
"kleur": "~4.1.4",
"lodash": "~4.17.21",
"node-fetch": "~2.6.1",
"pa11y": "~6.1.0",
"protocolify": "~3.0.0",
"puppeteer": "~1.19.0",
"puppeteer": "~9.1.1",
"wordwrap": "~1.0.0"
},
"devDependencies": {
"@rowanmanning/make": "^2.1.0",
"eslint": "^3.19.0",
"mocha": "^7.2.0",
"mockery": "^2.0.0",
"eslint": "^7.27.0",
"mocha": "^8.4.0",
"mockery": "^2.1.0",
"nyc": "^15.1.0",
"pa11y-lint-config": "^1.2.0",
"proclaim": "^3.4.4",
"sinon": "^2.3.2"
"pa11y-lint-config": "^2.0.0",
"proclaim": "^3.6.0",
"sinon": "^11.1.0"
},

@@ -50,3 +49,8 @@ "main": "./lib/pa11y-ci.js",

"scripts": {
"test": "make ci"
"lint": "eslint .",
"verify-coverage": "nyc check-coverage --lines 90 --functions 90 --branches 90",
"test-unit": "mocha --file test/unit/setup.test.js 'test/unit/**/*.test.js' --recursive",
"test-coverage": "nyc --reporter=text --reporter=html mocha --file test/unit/setup.test.js 'test/unit/**/*.test.js' --recursive",
"test-integration": "mocha --file test/integration/setup.test.js 'test/integration/**/*.test.js' test/integration/teardown.test.js --recursive --timeout 10000 --slow 5000",
"test": "npm run test-coverage && npm run verify-coverage && npm run test-integration"
},

@@ -53,0 +57,0 @@ "files": [

@@ -1,7 +0,6 @@

# Pa11y CI
Pa11y CI is a CI-centric accessibility test runner, built using [Pa11y].
Pa11y CI is an accessibility test runner built using [Pa11y] focused on running on Continuous Integration environments.
CI runs accessibility tests against multiple URLs and reports on any issues. This is best used during automated testing of your application and can act as a gatekeeper to stop a11y issues from making it to live.
Pa11y CI runs accessibility tests against multiple URLs and reports on any issues. This is best used during automated testing of your application and can act as a gatekeeper to stop a11y issues from making it to live.

@@ -16,20 +15,25 @@ [![NPM version][shield-npm]][info-npm]

## Table Of Contents
## Table of contents
- [Requirements](#requirements)
- [Usage](#usage)
- [Configuration](#configuration)
- [Default configuration](#default-configuration)
- [URL configuration](#url-configuration)
- [Sitemaps](#sitemaps)
- [Docker](#docker)
- [Tutorials and articles](#tutorials-and-articles)
- [Contributing](#contributing)
- [Support and Migration](#support-and-migration)
- [Licence](#licence)
* [Table of contents](#table-of-contents)
* [Requirements](#requirements)
* [Usage](#usage)
* [Configuration](#configuration)
* [Default configuration](#default-configuration)
* [URL configuration](#url-configuration)
* [Sitemaps](#sitemaps)
* [Reporters](#reporters)
* [Use Multiple reporters](#use-multiple-reporters)
* [Reporter options](#reporter-options)
* [Write a custom reporter](#write-a-custom-reporter)
* [Configurable reporters](#configurable-reporters)
* [Docker](#docker)
* [Tutorials and articles](#tutorials-and-articles)
* [Contributing](#contributing)
* [Support and Migration](#support-and-migration)
* [Licence](#licence)
## Requirements
This command line tool requires [Node.js] 8+. You can install through npm:
This command line tool requires [Node.js] 12+. You can install through npm:

@@ -40,3 +44,2 @@ ```sh

## Usage

@@ -46,3 +49,3 @@

```
```sh
Usage: pa11y-ci [options] [<paths>]

@@ -61,2 +64,3 @@

-T, --threshold <number> permit this number of errors, warnings, or notices, otherwise fail with exit code 2
--reporter <reporter> The reporter to use. Can be "cli", "json", an npm module, or a path to a local file.
```

@@ -73,4 +77,4 @@

"urls": [
"http://pa11y.org/",
"http://pa11y.org/contributing"
"https://pa11y.org/",
"https://pa11y.org/contributing"
]

@@ -96,4 +100,4 @@ }

"urls": [
"http://pa11y.org/",
"http://pa11y.org/contributing"
"https://pa11y.org/",
"https://pa11y.org/contributing"
]

@@ -105,4 +109,4 @@ }

- `concurrency`: The number of tests that should be run in parallel. Defaults to `2`.
- `useIncognitoBrowserContext`: Run test with an isolated incognito browser context, stops cookies being shared and modified between tests. Defaults to `false`.
* `concurrency`: The number of tests that should be run in parallel. Defaults to `1`.
* `useIncognitoBrowserContext`: Run test with an isolated incognito browser context, stops cookies being shared and modified between tests. Defaults to `true`.

@@ -119,5 +123,5 @@ ### URL configuration

"urls": [
"http://pa11y.org/",
"https://pa11y.org/",
{
"url": "http://pa11y.org/contributing",
"url": "https://pa11y.org/contributing",
"timeout": 50000,

@@ -135,3 +139,3 @@ "screenCapture": "myDir/my-screen-capture.png"

```sh
pa11y-ci --sitemap http://pa11y.org/sitemap.xml
pa11y-ci --sitemap https://pa11y.org/sitemap.xml
```

@@ -144,3 +148,3 @@

```sh
pa11y-ci --sitemap http://pa11y.org/sitemap.xml --sitemap-find pa11y.org --sitemap-replace localhost
pa11y-ci --sitemap https://pa11y.org/sitemap.xml --sitemap-find pa11y.org --sitemap-replace localhost
```

@@ -152,2 +156,183 @@

## Reporters
Pa11y CI includes both a CLI reporter that outputs pa11y results to the console and a JSON reporter that outputs JSON-formatted results (to the console or a file). If no reporter is specified, the CLI reporter is selected by default. You can use the `--reporter` option to define a single reporter. The option value can be:
* `cli` for the included CLI reporter or `json` for the included JSON reporter
* the path of a locally installed npm module (ie: `pa11y-reporter-html`)
* the path to a local node module relative to the current working directory (ie: `./reporters/my-reporter.js`)
* an absolute path to a node module (ie: `/root/user/me/reporters/my-reporter.js`)
Example:
```sh
npm install pa11y-reporter-html --save
pa11y-ci --reporter=pa11y-reporter-html https://pa11y.org/
```
**Note**: If custom reporter(s) are specified, the default CLI reporter will be overridden.
### Use Multiple reporters
You can use multiple reporters by setting them on the `defaults.reporters` array in your config. The shorthand `cli` and `json` can be included to select the included reporters.
```json
{
"defaults": {
"reporters": [
"cli", // <-- this is the default reporter
"pa11y-reporter-html",
"./my-local-reporter.js"
]
},
"urls": [
"https://pa11y.org/",
{
"url": "https://pa11y.org/contributing",
"timeout": 50000,
"screenCapture": "myDir/my-screen-capture.png"
}
]
}
```
**Note**: If the CLI `--reporter` option is specified, it will override any reporters specified in the config file.
### Reporter options
Reporters can be configured, when supported, by settings the reporter as an array with its options as the second item:
```json
{
"defaults": {
"reporters": [
"pa11y-reporter-html",
["./my-local-reporter.js", { "option1": true }] // <-- note that this is an array
]
},
"urls": [
"https://pa11y.org/",
{
"url": "https://pa11y.org/contributing",
"timeout": 50000,
"screenCapture": "myDir/my-screen-capture.png"
}
]
}
```
The included CLI reporter does not support any options.
The included JSON reporter outputs the results to the console by default. It can also accept a `fileName` with a relative or absolute file name where the JSON results will be written. Relative file name will be resolved from the current working directory.
```json
{
"defaults": {
"reporters": [
["json", { "fileName": "./results.json" }] // <-- note that this is an array
]
},
"urls": [
"https://pa11y.org/"
]
}
```
### Write a custom reporter
Pa11y CI reporters use an interface similar to [pa11y reporters] and support the following optional methods:
* `beforeAll(urls)`: called at the beginning of the process. `urls` is the URLs array defined in your config
* `afterAll(report)` called at the very end of the process with the following arguments:
* `report`: pa11y-ci report object
* `config`: pa11y-ci configuration object
* `begin(url)`: called before processing each URL. `url` is the URL being processed
* `results(results, config)` called after pa11y test run with the following arguments:
* `results`: pa11y results object [URL configuration object](#url-configuration)
* `config`: the current [URL configuration object](#url-configuration)
* `error(error, url, config)`: called when a test run fails with the following arguments:
* `error`: pa11y error message
* `url`: the URL being processed
* `config`: the current [URL configuration object](#url-configuration)
Here is an example of a custom reporter writing pa11y-ci report and errors to files:
```js
const fs = require('fs');
const { createHash } = require('crypto');
// create a unique filename from URL
function fileName(url: any, prefix = '') {
const hash = createHash('md5').update(url).digest('hex');
return `${prefix}${hash}.json`;
}
exports.afterAll = function (report) {
return fs.promises.writeFile('report.json', JSON.stringify(report), 'utf8');
}
// write error details to an individual log for each URL
exports.error = function (error, url) {
const data = JSON.stringify({url, error});
return fs.promises.writeFile(fileName(url, 'error-'), data, 'utf8');
}
```
#### Configurable reporters
A configurable reporter is a special kind of pa11y-ci reporter exporting a single factory function as its default export.
When initialized, the function receives the user configured options (if any) and pa11y-ci configuration object as argument.
For example, here is a reporter writing all results to a single configurable file:
```js
// ./my-reporter.js
const fs = require('fs');
module.exports = function (options) {
// initialize an empty report data
const customReport = {
results: {},
errors: [],
violations: 0,
}
const fileName = options.fileName
return {
// add test results to the report
results(results) {
customReport.results[results.pageUrl] = results;
customReport.violations += results.issues.length;
},
// also store errors
error(error, url) {
customReport.errors.push({ error, url });
},
// write to a file
afterAll() {
const data = JSON.stringify(customReport);
return fs.promises.writeFile(fileName, data, 'utf8');
}
}
};
```
```json
// configuration file
{
"defaults": {
"reporters": [
["./my-reporter.js", { "fileName": "./my-report.json" }]
]
},
"urls": [
...
]
}
```
### Docker

@@ -158,2 +343,3 @@

You will need a `config.json` that sets the `--no-sandbox` Chromium launch arguments:
```json

@@ -169,4 +355,4 @@ {

"urls": [
"http://pa11y.org/",
"http://pa11y.org/contributing"
"https://pa11y.org/",
"https://pa11y.org/contributing"
]

@@ -187,3 +373,2 @@ }

## Tutorials and articles

@@ -193,5 +378,4 @@

- [Automated accessibility testing with Travis and Pa11y CI](http://andrewmee.com/posts/automated-accessibility-testing-node-travis-ci-pa11y/)
* [Automated accessibility testing with Travis and Pa11y CI](https://andrewmee.com/posts/automated-accessibility-testing-node-travis-ci-pa11y/)
## Contributing

@@ -206,3 +390,4 @@

```sh
make ci
npm run lint
npm test
```

@@ -213,10 +398,9 @@

```sh
make verify # Verify all of the code (JSHint/JSCS)
make test # Run all tests
make test-unit # Run the unit tests
make test-unit-coverage # Run the unit tests with coverage
make test-integration # Run the integration tests
npm run lint # Verify all of the code (ESLint)
npm test # Run all tests
npm run test-unit # Run the unit tests
npm run coverage # Run the unit tests with coverage
npm run test-integration # Run the integration tests
```
## Support and Migration

@@ -230,14 +414,13 @@

| :-------------- | :------------ | :----------------- | :--------------- | :--------------- |
| :heart: | 2 | N/A | 8+ | N/A |
| :hourglass: | 1 | 1.3 | 4+ | 2018-04-18 |
| :heart: | 3 | N/A | 12+ | N/A |
| :hourglass: | 2 | 2.4.2 | 8+ | 2022-05-26 |
| :skull: | 1 | 1.3 | 4+ | 2018-04-18 |
If you're opening issues related to these, please mention the version that the issue relates to.
## Licence
Licensed under the [Lesser General Public License (LGPL-3.0)](LICENSE).<br/>
Copyright &copy; 2016–2017, Team Pa11y
Copyright &copy; 2016–2021, Team Pa11y
[issues]: https://github.com/pa11y/pa11y-ci/issues

@@ -247,2 +430,3 @@ [node.js]: https://nodejs.org/

[pa11y configurations]: https://github.com/pa11y/pa11y#configuration
[pa11y reporters]: https://github.com/pa11y/pa11y#reporters
[sidekick-proposal]: https://github.com/pa11y/sidekick/blob/master/PROPOSAL.md

@@ -249,0 +433,0 @@ [twitter]: https://twitter.com/pa11yorg

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc