nightwatch-axe-verbose
module.exports = {
'ensure site is accessible': function (browser) {
browser
.url('https://example.com')
.axeInject()
.axeRun('body', {
rules: { 'color-contrast': { enabled: false } }
})
.end()
}
}
What is nightwatch-axe-verbose?
nightwatch-axe-verbose is a Nightwatch.js plugin that adds axe-core powered accessibility testing to your existing Nightwatch test suite. It surfaces WCAG violations at the individual element level — so if three elements fail the same rule, you get three separate failures, not one aggregated count that hides the real scope of the problem.
It solves a common frustration with basic axe integrations: when multiple elements fail the same rule, a single reported failure gives you no sense of how widespread the problem is. This plugin counts each failing element individually, so your test output reflects the true number of violations and downstream failures can't be masked by early aggregation.
The plugin ships three commands — axeInject(), axeRun(), and assert.isAccessible() — that chain naturally into your existing Nightwatch test flow without requiring test structure changes. See the official Nightwatch accessibility testing guide for full documentation.
Per-Element Failure Reporting
The key difference from a basic axe integration is how failures are reported. A standard setup counts each rule violation once, regardless of how many elements are affected. nightwatch-axe-verbose counts each failing element separately.
This matters when you're tracking accessibility debt or trying to understand the real scope of a violation. If color-contrast fails on 12 buttons, you want 12 failures — not one failure that says "color-contrast failed."
Passes are also reported per rule with element counts, giving you a clear picture of what was actually scanned:
√ Passed [ok]: aXe rule: aria-hidden-body (1 elements checked)
√ Passed [ok]: aXe rule: color-contrast (62 elements checked)
√ Passed [ok]: aXe rule: duplicate-id-aria (1 elements checked)
× Failed [fail]: (aXe rule: button-name - Buttons must have discernible text
In element: .departure-date > .ui-datepicker-trigger:nth-child(4))
× Failed [fail]: (aXe rule: color-contrast - Elements must have sufficient color contrast
In element: a[href="mars2\.html\?a\=be_bold"] > h3)
Installation
For earlier versions:
npm install nightwatch-axe-verbose --save-dev
Register it as a plugin in nightwatch.conf.js:
{
plugins: ['nightwatch-axe-verbose']
}
TypeScript
The package is written in JavaScript but works in TypeScript projects without any additional setup. Nightwatch's own TypeScript support covers the custom commands added by this plugin — axeInject(), axeRun(), and assert.isAccessible() are all fully chainable in .ts test files.
API
axeInject()
Injects the axe-core library into the current page. Call this before axeRun() on each page you want to scan:
browser
.url('https://example.com')
.axeInject()
.axeRun('body')
axeRun(selector, options)
Runs axe-core against the specified selector. Scoping to a selector like 'body' scans all child elements. You can narrow the scope to a specific component or region:
browser
.url('https://dequeuniversity.com/demo/mars/')
.axeInject()
.axeRun('body', {
rules: { 'color-contrast': { enabled: false } }
})
.end()
If no selector is provided, it defaults to html and scans the entire page with all default axe rules.
assert.isAccessible(selector, options, expectedViolationCount)
The assertion variant integrates with Nightwatch's assertion library for better stack traces in HTML test reports. expectedViolationCount defaults to 0 but can be set higher to allow a known number of violations — useful when you're tracking progress on a legacy codebase:
it('Can check for tabindex rules', (browser) => {
browser
.url('https://dequeuniversity.com/demo/mars/')
.axeInject()
.assert.isAccessible('body', {
runOnly: ['tabindex'],
})
})
Setting expectedViolationCount to a value greater than 0 is a practical way to introduce accessibility testing into a codebase that already has known violations. The test passes as long as the violation count stays at or below the threshold — so you can commit to not making things worse while working through the backlog:
it('Does not introduce new accessibility violations', (browser) => {
browser
.url('https://example.com/legacy-page')
.axeInject()
.assert.isAccessible('body', {}, 3) // 3 known violations, fail if it grows
})
Failure output includes the rule ID, severity, description, a link to the Deque rule documentation, and the specific element that failed:
aXe rule [serious]: tabindex - Elements should not have tabindex greater than zero
[https://dequeuniversity.com/rules/axe/4.10/tabindex?application=axeAPI]
In element: #from0
aXe rule [serious]: tabindex - Elements should not have tabindex greater than zero
In element: #to0
Global Configuration
If you have axe settings that apply across your entire suite — disabling a rule globally, setting a default scan context — you can configure them once in nightwatch.conf.js under axeSettings rather than repeating them in every test:
test_settings: {
default: {
globals: {
axeSettings: {
context: 'html',
options: {
rules: {
'color-contrast': { enabled: false },
},
},
}
}
}
}
Test-level settings merge with globals — if both supply a rules key, the test-level value takes precedence for that key. Settings not supplied by the test fall through from the global config.
| Precedence | Source |
|---|---|
| Highest | Selector passed directly to axeRun() |
| ↓ | Options passed directly to axeRun() |
| Lowest | Global axeSettings in nightwatch.conf.js |