Testing with Jest
The AudioEye Accessibility Testing SDK Jest library lets you test rendered components for accessibility issues as part of your existing unit and integration test suite.
If you are new to the SDK, complete the client-credential and package-manager setup in getting started before installing the Jest package.
When to use Jest
Jest is a good fit when you want to:
- catch accessibility issues while developing components
- validate rendered output in unit or integration tests
- test isolated UI states without launching a full browser
- add accessibility assertions to an existing React Testing Library workflow
If you need to test full browser behavior such as routing, authentication, or multi-step flows, use Playwright or Cypress instead.
Prerequisites
Before you begin, make sure you have:
- configured your client credentials as described in getting started
- installed Jest in your project
- set up a DOM-based test environment when your tests render UI
- installed any rendering utilities your app already uses, such as
@testing-library/react
The Jest SDK evaluates component markup in a test environment. Because Jest does not run in a real browser page, some rules that depend on full browser context, computed styles, layout, or runtime page behavior are not available here.
For more details, see About Our Rules.
Installation
Install the Jest SDK as a development dependency:
- npm
- Yarn
- pnpm
npm install -D @audioeye/testing-sdk-jest
yarn add -D @audioeye/testing-sdk-jest
pnpm add -D @audioeye/testing-sdk-jest
Test environment setup
Most component tests should run in a DOM-capable environment such as jsdom.
A minimal Jest configuration looks like this:
module.exports = {
testEnvironment: 'jsdom',
};
If you are using Jest 28 or later, make sure your project has configured the test environment correctly for that version of Jest. See the Jest documentation for details:
Register the matcher
Once at the top of your test file (or in a Jest setup file applied to all tests), extend expect with the
toBeAccessible matcher:
import { toBeAccessible } from '@audioeye/testing-sdk-jest';
expect.extend({ toBeAccessible });
If you have a project-wide Jest setup file (e.g. jest.setup.ts referenced from setupFilesAfterEach in your Jest
config), this is a good place to register the matcher once for the whole suite.
Your first accessibility test
The basic workflow is:
- render a component
- assert it is accessible with
expect(...).toBeAccessible()
Here is a simple React example using React Testing Library:
import { toBeAccessible } from '@audioeye/testing-sdk-jest';
import { render } from '@testing-library/react';
import React from 'react';
expect.extend({ toBeAccessible });
function Image({ altText }: { altText: string }) {
return <img src="/logo.png" alt={altText} />;
}
describe('Image', () => {
it('passes when alt text is descriptive', () => {
expect(render(<Image altText="The AudioEye company logo" />)).toBeAccessible();
});
it('catches weak alt text', () => {
expect(render(<Image altText="image" />)).not.toBeAccessible();
});
});
When the assertion fails, the matcher prints every issue it found, with the rule code, target selector, and a direct link to the rule's developer documentation.
Supported input types
For convenience, the matcher and scan accept several common input types. This makes it easier to use with the testing
tools you already have:
stringRenderResult(from@testing-library/react)HTMLElementDocumentFragmentJQuery<HTMLElement>
Common usage patterns
Filter to a specific WCAG conformance level
expect(render(<Image altText="image" />)).toBeAccessible({ level: 'AA' });
level accepts 'A', 'AA', or 'AAA'. The filter is cumulative — AA includes both A and AA results.
Ignore specific rules
expect(container).toBeAccessible({ ignoreRules: ['Img_Name_WeakName'] });
Restrict to specific rules
expect(container).toBeAccessible({ onlyRules: ['Img_Name_Missing', 'Img_Name_WeakName'] });
Filter by CSS selector
Use withinSelector to keep only issues inside a section, or excludingSelector to suppress issues inside a known
container. Selectors use normal CSS ancestor matching against the rendered test document.
expect(container).toBeAccessible({ excludingSelector: '[data-testid="third-party-widget"]' });
const report = scan(container, { withinSelector: 'main article' });
expect(report.isEmpty).toBe(true);
Inspect the report instead of asserting
When you want to look at issues — count them, group them, or assert about which rule fired — call scan directly. It
returns an A11yResults instance you can chain assertions against.
import { scan } from '@audioeye/testing-sdk-jest';
import { render } from '@testing-library/react';
import { Image } from './image';
describe('Image', () => {
it('produces exactly one weak-name issue', () => {
const report = scan(render(<Image altText="image" />));
expect(report.count).toBe(1);
expect(report.has('Img_Name_WeakName')).toBe(true);
expect(report.issues[0].helpUrl).toMatch(/developer\.audioeye\.com/);
});
});
scan returns an A11yResults instance synchronously, so all of its filters and getters work directly inside expect:
expect(scan(container).conformanceLevel('AA').isEmpty).toBe(true);
expect(scan(container).withoutRule('Img_Name_WeakName').count).toBe(0);
expect(scan(container).resultCodes).toEqual([]);
Use an HTMLElement
const { container } = render(<Image altText="image" />);
expect(container).toBeAccessible();
Use a DocumentFragment
const { asFragment } = render(<Image altText="image" />);
expect(asFragment()).toBeAccessible();
Use raw HTML
expect('<img src="/logo.png" alt="image" />').toBeAccessible();
SDK API
toBeAccessible(options?) — Jest matcher
Asserts that the rendered output has no accessibility issues after applying the given filters.
Parameters
› options.level ('A' | 'AA' | 'AAA', optional) — WCAG conformance level filter; cumulative by default.
› options.onlyRules (string[], optional) — Restrict to these rule codes.
› options.ignoreRules (string[], optional) — Drop these rule codes.
› options.withinSelector (string, optional) — Keep only issues whose target descends from this CSS selector.
› options.excludingSelector (string, optional) — Drop issues whose target descends from this CSS selector.
› options.baselineIds (string[], optional) — Drop issues whose stable fingerprint (A11yIssue.id) appears in
this list.
scan(source, options?) — functional API
Run AudioEye's rules against a rendered source and return an A11yResults instance for inspection.
const report = scan(render(<Image altText="image" />));
Parameters
› source (string | RenderResult | HTMLElement | DocumentFragment | JQuery<HTMLElement>) — what to evaluate.
› options (same shape as toBeAccessible options, optional) — filters applied to the report before returning.
Returns
An instance of A11yResults.
A11yResults API
A11yResults is the chainable report object returned by scan (and used internally by toBeAccessible). All filter
methods return a new instance, so chains are immutable.
Filters
| Method | Returns | Description |
|---|---|---|
conformanceLevel(level, opts?) | A11yResults | Filter to issues at the given WCAG level. Cumulative; pass { exact: true } for exact-level only. |
withRule(...codes) | A11yResults | Restrict to issues for the given rule codes. |
withoutRule(...codes) | A11yResults | Drop issues for the given rule codes. |
withinSelector(selector) | A11yResults | Restrict to issues inside the given CSS ancestor selector. |
excludingSelector(selector) | A11yResults | Drop issues inside the given CSS ancestor selector. |
excludingIds(ids) | A11yResults | Drop issues whose stable fingerprint matches an entry in ids. |
Queries
| Member | Type | Description |
|---|---|---|
count | number | Total issue count after filtering. |
isEmpty | boolean | true when no issues remain after filtering. |
has(ruleCode) | boolean | true when at least one issue matches the rule code. |
get(ruleCode) | A11yIssue[] | Issues for the given rule code. |
issues | A11yIssue[] | All issues remaining after filtering. |
resultCodes | string[] | Sorted, deduplicated array of rule codes. |
resultsGroupedByWcagSuccessCriteriaLevel | Record<string, A11yIssue[]> | Issues grouped by WCAG level ('A', 'AA', 'AAA', or ''). |
Issue shape
Each entry in issues has:
| Field | Type | Description |
|---|---|---|
id | string | Stable SARIF fingerprint, useful for baseline ignore lists. |
ruleCode | string | The rule that fired (e.g. 'Img_Name_WeakName'). |
ruleMetadata | RuleMetaOutput | WCAG criterion, level, name, fix-guidance, etc. |
helpUrl | string | Direct link to the rule's developer documentation. |
cssSelector | string | CSS selector path for the violating element. |
xpath | string | XPath for the violating element. |
source | string | outerHTML of the violating element. |
boundingBox | { x; y; width; height } | undefined | Layout box of the element (real-browser scans only). |
frame | string[] | undefined | Frame chain for violations inside iframes. |
relatedNodes | Array<{ cssSelector; xpath?; html }> | undefined | Optional context nodes the rule provides. |
Legacy API (deprecated)
The accessibility.evaluate (and a11y.evaluate) form continues to work in v6 but emits a one-time deprecation warning
the first time it is called per process. New code should use the matcher or scan. See the
v5 → v6 upgrade guide.
import { accessibility } from '@audioeye/testing-sdk-jest';
import { render } from '@testing-library/react';
const results = accessibility.evaluate(render(<Image altText="image" />));
expect(results.resultCodes).toEqual(['Img_Name_WeakName']);
The SDK provides matching accessibility and a11y singleton exports. Both behave identically and both are deprecated.
Troubleshooting
No package found during install
If installation fails, verify that your client credentials and package-manager configuration are set up correctly in getting started.
Tests fail because document or DOM APIs are missing
Confirm that Jest is using a DOM test environment such as jsdom.
Expected browser-dependent rules are not running
Jest does not provide the same browser context as Playwright, Cypress, or the CLI. If you need browser-context rules, use a browser-based integration instead. See About Our Rules.
expect(...).toBeAccessible is not a function
Make sure you registered the matcher with expect.extend({ toBeAccessible }). The simplest place is a project-wide Jest
setup file (referenced from setupFilesAfterEach in jest.config.js).
Next steps
After your first Jest test is working, you can:
- add
expect(node).toBeAccessible()to existing component tests - use
scan(...)when you want to assert which issues are present, not just whether the page is clean - move browser-dependent scenarios to Playwright or Cypress
- review Troubleshooting if installation or execution fails