Revision control

Copy as Markdown

Other Tools

# Axe Accessibility Checks
Thunderbird browser-chrome tests can run [axe-core](https://github.com/dequelabs/axe-core)
against content browsers and chrome windows through `AxeHelpers.sys.mjs`. Use
these helpers to add automated accessibility checks to normal functional tests.
```js
const { checkAxe, startAxeMutationObserver } = ChromeUtils.importESModule(
);
```
Axe checks are ordinary test assertions. A failure is reported in the normal test
output with the failing rule id, impact, axe help text, help URL, and target
selector. There is no separate report to collect.
## One-shot checks
Use `checkAxe` when the test reaches a stable UI state and you want to scan a
content browser once.
```js
await checkAxe(tab.browser, {
context: "#settingsPane",
message: "Settings pane has no axe violations",
specialPowers: SpecialPowers,
});
```
Use `checkAxeInWindow` for chrome windows.
```js
const { checkAxeInWindow } = ChromeUtils.importESModule(
);
await checkAxeInWindow(window, {
context: document.querySelector("#toolbar-context-menu"),
message: "Toolbar context menu has no axe violations",
});
```
The `context` option is passed to `axe.run`. Keep it as small as the UI under
test allows, such as a dialog, pane, notification, or fixture root.
## Watching DOM Mutations
Use a mutation observer when a functional test drives UI changes over time and
you want axe to check the states produced by those changes. The watcher runs
once when it starts, then runs again after observed DOM mutations while the test
continues.
For content tabs, use `startAxeMutationObserver`.
```js
const { startAxeMutationObserver } = ChromeUtils.importESModule(
);
let browser;
let tab;
add_setup(async function () {
tab = tabmail.openTab("contentTab", {
});
await BrowserTestUtils.browserLoaded(tab.browser);
browser = tab.browser;
await startAxeMutationObserver(tab.browser, {
container: "#fixtureRoot",
message: "Content tab stayed axe-clean while the test mutated the DOM",
specialPowers: SpecialPowers,
});
registerCleanupFunction(() => {
tabmail.closeTab(tab);
});
});
add_task(async function test_content_tab_ui() {
// Run the functional test steps that mutate #fixtureRoot.
EventUtils.synthesizeMouseAtCenter(
browser.contentWindow.document.querySelector("button"),
{},
browser.contentWindow
);
});
```
For chrome windows, use `startAxeMutationObserverInWindow`.
```js
const { startAxeMutationObserverInWindow } = ChromeUtils.importESModule(
);
let axeWatcher;
let dialog;
add_setup(async function () {
dialog = await openTestDialog();
axeWatcher = await startAxeMutationObserverInWindow(window, {
autoFinish: false,
container: dialog,
message: "Dialog stayed axe-clean while the test mutated it",
});
registerCleanupFunction(async () => {
await axeWatcher.finish();
await closeTestDialog(dialog);
});
});
add_task(async function test_chrome_window_ui() {
// Run the functional test steps that mutate the dialog.
EventUtils.synthesizeMouseAtCenter(
dialog.querySelector("button"),
{},
window
);
});
```
By default the watcher registers a cleanup function and finishes automatically.
This is what the content-tab example uses. In browser-chrome tests that need to
tear down chrome UI in the same cleanup path, set `autoFinish` to `false` and
finish the watcher before closing or navigating the window or dialog under
observation, as shown in the chrome-window example. `finish()` stops observing,
runs a final check, and asserts that no watcher errors or axe violations were
collected. Use `flush()` to force any pending check to run and inspect the
current report before the end of the test.
The watcher uses a throttle, not a debounce. While mutations continue, checks
are scheduled at most once per `throttleMs` interval. The default is 10 ms.
## Context and Container
`context` controls what axe scans. `container` controls what the mutation
observer watches. If `context` is omitted for a mutation watcher, the helper uses
the watched container as the axe context.
For content browsers, pass selector strings because the observer runs in the
content process. If no content-browser container or string context is provided,
the observer watches `document.body`, falling back to `documentElement`.
For chrome windows, `container` may be either an element or a selector string.
If no chrome-window container or string context is provided, the observer watches
the document root.
## Content Browser Defaults
Content tabs in browser tests are often fixtures or partial documents. They are
not expected to have full page-level landmarks, so content-browser helpers
disable these axe rules by default:
- `landmark-one-main`
- `region`
Chrome-window helpers do not apply those defaults. A test can still override any
axe rule through `axeOptions`.
```js
await checkAxe(tab.browser, {
axeOptions: {
rules: {
"landmark-one-main": { enabled: true },
},
},
specialPowers: SpecialPowers,
});
```
## Fluent and Build Skips
The helpers wait for Fluent localization before every axe run. When a document
has `document.l10n`, the helper waits for `l10n.ready`, calls
`translateRoots()`, and waits for a frame so translated strings are present in
the DOM before axe scans it.
Axe checks are skipped on debug, ASan, TSan, and code coverage builds. The
helpers return empty skipped results in those configurations so the same test can
still run.
## SpecialPowers
Content-browser helpers use `SpecialPowers.spawn` to inject and run axe in the
content process. If the helper cannot find `SpecialPowers` from the test global
or browser owner, pass it explicitly:
```js
await checkAxe(tab.browser, {
specialPowers: SpecialPowers,
});
```
Chrome-window helpers run in the current process and do not need
`SpecialPowers.spawn`.
## Vendored axe-core
The axe browser bundle is vendored under `third_party/axe-core`. The build
exposes `third_party/axe-core/axe.min.js` as
`resource://testing-common/mail/axe.min.js`, which `AxeHelpers.sys.mjs` injects
into the target document. Tests should import and use the helper module instead
of loading `axe.min.js` directly.
The vendoring metadata is in `third_party/axe-core/moz.yaml`. The custom vendor
script downloads the published npm package, verifies the npm integrity hash, and
copies the files Thunderbird keeps.
To update axe-core, run mach vendor with the desired npm package version:
```sh
../mach vendor -r 4.12.1 third_party/axe-core/moz.yaml
```
Use the new version number in place of `4.12.1`.