nightwatch-axe-verbose

Verbose axe-core accessibility testing plugin for Nightwatch.js. Per-element failure reporting ensures downstream violations are never hidden. Adopted as a default Nightwatch plugin in v2.3.6.
Adopted as a default Nightwatch plugin in v2.3.6
Nightwatch pluginaxe-core poweredPer-element reportingWCAG compliance
accessibility.test.js
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.

Adopted as a default Nightwatch plugin in v2.3.6
If you are running Nightwatch 2.3.6 or later, these commands ship with Nightwatch by default — no separate install needed.

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:

Terminal
√ 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

If you are using Nightwatch 2.3.6 or later, skip installation — the plugin is bundled by default.

For earlier versions:

Terminal
npm install nightwatch-axe-verbose --save-dev

Register it as a plugin in nightwatch.conf.js:

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:

accessibility.test.js
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:

accessibility.test.js
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:

accessibility.spec.ts
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:

legacy-a11y.spec.ts
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:

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

nightwatch.conf.js
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.

PrecedenceSource
HighestSelector passed directly to axeRun()
Options passed directly to axeRun()
LowestGlobal axeSettings in nightwatch.conf.js

Frequently Asked Questions

Changelog

2.5.0 — axe-core v4.11.1

Updated to axe-core v4.11.1.

2.4.0 — assert.isAccessible Assertion

Added assert.isAccessible() — a native Nightwatch assertion variant of axeRun() that surfaces violations correctly in HTML test reports and supports an optional expectedViolationCount parameter.

2.3.3 — axe-core 4.10.3

Updated to axe-core 4.10.3. Added iframe test coverage.

2.3.0 — Non-String Context Support

axeRun() now accepts non-string values as context, enabling object-based axe context configurations. Community contribution.

2.2.0 — Global Configuration

Added support for reading selector context and run options from the Nightwatch globals collection via axeSettings, so defaults do not need to be repeated in every test.

2.1.0 — Async Support

Refactored to support calling axe commands from async test functions. Community contribution.

2.0.0 — Nightwatch Plugin

Restructured as a Nightwatch 2.x plugin. Plugin install via nightwatch.conf.js is now the recommended setup. Adopted as a default plugin in Nightwatch 2.3.6+.

1.0.0 — Initial Release

Initial release with axeInject() and axeRun() custom commands for Nightwatch.

Ready to use nightwatch-axe-verbose?

axe-core powered accessibility testing with per-element failure reporting for Nightwatch.js.

npm install nightwatch-axe-verbose