Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reporters #129

Merged
merged 32 commits into from
Jul 19, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
71ceab2
add support for external reporters
dwightjack Mar 12, 2021
621a28d
reporters tests
dwightjack Mar 17, 2021
524a008
add reporters documentation
dwightjack Mar 18, 2021
9991f65
fix reporters merging from CLI
dwightjack Mar 18, 2021
c5a67c5
reporters: built-in report map object and results method refactor
dwightjack Mar 19, 2021
766559c
Update lib/helpers/resolver.js
dwightjack Mar 22, 2021
de9d33e
documentation fix
dwightjack Mar 22, 2021
36b8016
refactor fallback values
dwightjack Mar 22, 2021
52db80f
Merge branch 'master' into master
dwightjack Apr 15, 2021
72ca03c
Update lib/helpers/reporter.js
dwightjack Apr 15, 2021
8e8a7d0
Update lib/pa11y-ci.js
dwightjack Apr 15, 2021
0f1ba19
move logs to a dedicated reporter
dwightjack Apr 16, 2021
8aa2a69
deduplicate and refactor reporters loader
dwightjack Apr 16, 2021
3f60421
review repoters methods
dwightjack Apr 16, 2021
91bd31e
update reporters docs
dwightjack Apr 16, 2021
7f63786
typo
dwightjack Apr 16, 2021
50b2d44
Add JSON reporter and unit tests
aarongoldenthal May 3, 2021
47204d7
Move test data to mock folder
aarongoldenthal May 5, 2021
cbc8b10
Update reporters to accept CLI/JSON reporter shorthand name
aarongoldenthal May 5, 2021
d4da4fb
Merge pull request #1 from aarongoldenthal/add-reporters
dwightjack May 5, 2021
05dfbb5
Merge branch 'master' into master
dwightjack May 10, 2021
1d7010b
add multiple reporters tests
dwightjack May 10, 2021
d7a2632
json reporter: resolve relative paths and ensure parent folders exist
dwightjack May 25, 2021
460c008
reporters integratino test
dwightjack May 25, 2021
18e2a3e
docs: json reporter filename resolve strategy
dwightjack May 25, 2021
8b2722d
rename reporters tests folder
dwightjack May 25, 2021
fa70723
Merge branch 'master' into master
dwightjack May 25, 2021
30d78ad
Update README.md
dwightjack Jun 2, 2021
5dfed4b
Update lib/reporters/json.js
dwightjack Jun 2, 2021
874fe9e
Merge branch 'master' into master
joeyciechanowicz Jul 5, 2021
e6b67b7
Fixes defaults to original values, and linting
joeyciechanowicz Jul 5, 2021
3e637a8
Uses unlinkSync for v12 support
joeyciechanowicz Jul 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
187 changes: 186 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ CI runs accessibility tests against multiple URLs and reports on any issues. Thi
- [Default configuration](#default-configuration)
- [URL configuration](#url-configuration)
- [Sitemaps](#sitemaps)
- [Reporters](#reporters)
- [Use Multiple reporters](#use-multiple-reporters)
- [Write a custom reporter](#write-a-custom-reporter)
- [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:
Expand Down Expand Up @@ -55,6 +59,7 @@ Options:
-x, --sitemap-exclude <pattern> a pattern to find in sitemaps and exclude any url that matches
-j, --json Output results as JSON
-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.
```

### Configuration
Expand Down Expand Up @@ -139,6 +144,186 @@ The above would ensure that you run Pa11y CI against local URLs instead of the l

If there are items in the sitemap that you'd like to exclude from the testing (for example PDFs) you can do so using the `--sitemap-exclude` flag.

## 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:

```
npm install pa11y-reporter-html --save
pa11y-ci --reporter=pa11y-reporter-html http://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": [
"http://pa11y.org/",
{
"url": "http://pa11y.org/contributing",
"timeout": 50000,
"screenCapture": "myDir/my-screen-capture.png"
}
]
}
```

dwightjack marked this conversation as resolved.
Show resolved Hide resolved
**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": [
"http://pa11y.org/",
{
"url": "http://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": [
"http://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

If you want to run `pa11y-ci` in a Docker container then you can use the [`buildkite/puppeteer`](https://github.com/buildkite/docker-puppeteer) image as this installs Chrome and all the required libs to run headless chrome on Linux.
Expand Down Expand Up @@ -171,7 +356,6 @@ ADD config.json /usr/config.json
ENTRYPOINT ["pa11y-ci", "-c", "/usr/config.json"]
```


## Tutorials and articles

Here are some useful articles written by Pa11y users and contributors:
Expand Down Expand Up @@ -226,6 +410,7 @@ Copyright &copy; 2016–2017, Team Pa11y
[node.js]: https://nodejs.org/
[pa11y]: https://github.com/pa11y/pa11y
[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
[twitter]: https://twitter.com/pa11yorg

Expand Down
7 changes: 7 additions & 0 deletions bin/pa11y-ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ commander
'-T, --threshold <number>',
'permit this number of errors, warnings, or notices, otherwise fail with exit code 2',
'0'
).option(
'--reporter <reporter>',
'the reporter to use. Can be a npm module or a path to a local file.',
)
.parse(process.argv);

Expand All @@ -74,6 +77,10 @@ Promise.resolve()
return config;
})
.then(config => {
// Override config reporters with CLI argument
if (commander.reporter) {
config.defaults.reporters = [commander.reporter];
}
// Actually run Pa11y CI
return pa11yCi(urls.concat(config.urls || []), config.defaults);
})
Expand Down
14 changes: 14 additions & 0 deletions lib/helpers/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

const noop = () => undefined;

module.exports = {
concurrency: 2,
log: {
error: noop,
info: noop
},
wrapWidth: 80,
reporters: ['cli'],
useIncognitoBrowserContext: false
};
17 changes: 17 additions & 0 deletions lib/helpers/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const path = require('path');
const fs = require('fs');

module.exports = function loadReporter(reporter) {
try {
return require(reporter);
} catch (_) {
const localModule = path.resolve(process.cwd(), reporter);
if (!fs.existsSync(localModule)) {
console.error(`Unable to load reporter "${reporter}"`);
return undefined;
}
return require(localModule);
}
};
32 changes: 32 additions & 0 deletions lib/helpers/resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const loadReporter = require('./loader');

const reporterShorthand = {
cli: require.resolve('../reporters/cli.js'),
json: require.resolve('../reporters/json.js')
};

module.exports = function resolveReporters(config = {}) {
if (!Array.isArray(config.reporters) || config.reporters.length === 0) {
return [];
}
return config.reporters.map(reporter => {
let reporterOptions = {};
if (Array.isArray(reporter)) {
[reporter, reporterOptions = {}] = reporter;
}
if (typeof reporter !== 'string') {
return undefined;
}
if (Object.keys(reporterShorthand).includes(reporter)) {
reporter = reporterShorthand[reporter];
}
const reporterModule = loadReporter(reporter);

if (typeof reporterModule === 'function') {
return reporterModule(reporterOptions, config);
}
return reporterModule;
}).filter(Boolean);
};