Testing with Cypress
Use the AudioEye Accessibility Testing SDK for Cypress to add accessibility assertions to browser-based tests and full user flows.
Cypress is a good fit when you want to:
- test accessibility after real navigation and rendering
- validate authenticated or multi-step flows
- scan a full page or a specific element
- keep accessibility checks alongside existing Cypress tests
Before you begin
Complete the client-credential and package-manager setup in getting started before installing the Cypress package.
The AudioEye Testing SDK Cypress library is compatible with most Cypress plugins. Plugins that fundamentally change how
tests are authored may require additional integration work. For example, @badeball/cypress-cucumber-preprocessor,
which uses .feature files, is not directly documented by this guide.
Installation
Install the SDK as a development dependency:
- npm
- Yarn
- pnpm
npm install -D @audioeye/testing-sdk-cypress
yarn add -D @audioeye/testing-sdk-cypress
pnpm add -D @audioeye/testing-sdk-cypress
This package requires cypress version 13.0.0 or greater.
Setup
The Cypress SDK registers custom commands that you can call from a Cypress chain.
1. Import the SDK in your Cypress commands file
If your project uses TypeScript:
import '@audioeye/testing-sdk-cypress';
If your project uses JavaScript:
import '@audioeye/testing-sdk-cypress';
2. Register the AudioEye tasks in your Cypress config
Starting in v6, the SDK verifies your license each time it runs. A Cypress command runs in the browser, but the license
check needs Node (network access, the filesystem, and environment variables), so the SDK performs it in a Cypress
task. Register that task in your Cypress config's setupNodeEvents with registerAudioEyeTasks:
import { defineConfig } from 'cypress';
import { registerAudioEyeTasks } from '@audioeye/testing-sdk-cypress/plugin';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
registerAudioEyeTasks(on);
return config;
},
},
});
If you already have a setupNodeEvents function (for example, the webpack preprocessor in
Troubleshooting), add the registerAudioEyeTasks(on) call
inside it rather than adding a second setupNodeEvents.
Without this step, cy.checkA11y() and cy.scanA11y() fail with
The 'task' event has not been registered in the setupNodeEvents method. You must register it before using cy.task().
This is the one setup change v5 projects must make when upgrading. Make sure AUDIOEYE_TESTING_SDK_CLIENT_ID and
AUDIOEYE_TESTING_SDK_CLIENT_TOKEN are also present in the environment that runs Cypress — see
getting started.
3. Ensure your support file loads the commands file
If you already import ./commands from your support file, you do not need to change anything else.
TypeScript example:
import './commands';
JavaScript example:
import './commands';
4. Confirm Cypress can run your existing tests
Before adding accessibility assertions, make sure your normal Cypress setup is already working and your application can be visited in tests.
Your first accessibility test
The SDK exposes two commands:
cy.checkA11y(scope?, options?)— assertion-style. Fails the test when issues remain after applying filters.cy.scanA11y(scope?, options?)— chainable. Resolves to anA11yResultsinstance you can inspect with.its(),.invoke(), or.then().
Start with cy.checkA11y() to confirm the SDK is installed and working:
describe('accessibility checks', () => {
it('home page has no accessibility issues', () => {
cy.visit('http://localhost:3000');
cy.checkA11y();
});
});
If the page has issues, the test fails with a formatted list showing each rule code, its target selector, and the direct link to that rule's developer documentation.
Common usage patterns
Scan the full page
describe('full-page accessibility', () => {
it('passes a level-AA check', () => {
cy.visit('http://localhost:3000');
cy.checkA11y(null, { level: 'AA' });
});
});
Scan a specific element
describe('element-level accessibility', () => {
it('checks the login form', () => {
cy.visit('http://localhost:3000');
cy.checkA11y('[data-cy="login"]');
// or, with a previous subject:
cy.get('[data-cy="login"]').checkA11y();
});
});
Inspect issues without failing the test
Use cy.scanA11y() when you want to assert which issues fired, count them, group them by WCAG level, or chain
filters.
describe('inspecting issues', () => {
it('contains exactly the expected codes', () => {
cy.visit('http://localhost:3000');
cy.scanA11y()
.its('resultCodes')
.should('deep.equal', [
'BadTag_Emphasis_Detect',
'Heading_Sequence_Wrong',
'Img_AttributeRequirement_Missing',
'Link_ExternalWarning_Missing',
]);
cy.scanA11y('[data-cy="login"]').invoke('has', 'Link_ExternalWarning_Missing').should('be.true');
});
});
Filter results by WCAG conformance level
describe('conformance-level filtering', () => {
it('groups issues by WCAG level (AA cumulative)', () => {
cy.visit('http://localhost:3000');
cy.scanA11y(null, { level: 'AA' }).its('resultsGroupedByWcagSuccessCriteriaLevel').should('have.keys', ['A', 'AA']);
});
});
Ignore or restrict to specific rules
cy.checkA11y(null, { ignoreRules: ['Img_Name_WeakName'] });
cy.scanA11y('.checkout', { onlyRules: ['Link_Name_Missing', 'Button_Name_Missing'] });
Filter by CSS selector
Use selector filters when a scan should focus on, or ignore, a region of the page. Selectors use normal CSS ancestor matching in the application document.
cy.checkA11y(null, { excludingSelector: '[data-cy="third-party-widget"]' });
cy.scanA11y(null, { withinSelector: 'main article' }).its('isEmpty').should('be.true');
Inspection chain — .its vs .invoke
Cypress's .its('property') resolves a property; .invoke('method', ...args) calls a method. The A11yResults
instance returned by cy.scanA11y works with both:
cy.scanA11y().its('isEmpty').should('be.true');
cy.scanA11y().its('count').should('eq', 0);
cy.scanA11y().its('resultCodes').should('include', 'Heading_Sequence_Wrong');
cy.scanA11y().invoke('has', 'Heading_Sequence_Wrong').should('be.true');
cy.scanA11y().invoke('conformanceLevel', 'AA').its('count').should('eq', 0);
cy.scanA11y().invoke('withoutRule', 'Img_Name_WeakName').its('isEmpty').should('be.true');
You can also unwrap to plain JavaScript with .then():
cy.scanA11y('.modal').then((report) => {
expect(report.has('Aria_Required_Detect')).to.be.true;
expect(report.issues[0].helpUrl).to.match(/developer\.audioeye\.com/);
});
SDK API
cy.checkA11y(scope?, options?)
Assertion-style command. Runs an AudioEye scan, applies the given filters, and fails the test when any issues remain.
cy.checkA11y(); // whole page
cy.checkA11y('.navbar');
cy.checkA11y({ level: 'AA' }); // whole page + options
cy.checkA11y(null, { level: 'AA', ignoreRules: ['Img_Name_WeakName'] });
cy.get('.modal').checkA11y(); // previous-subject form
Parameters
› scope (optional) — string | JQuery<HTMLElement> | HTMLElement | null — what to scan. When omitted (or
null), scans the whole page. Pass a CSS selector or pass an element via the previous-subject form.
› options (optional) — see A11yScanOptions below.
Returns
Cypress.Chainable<A11yResults> — useful if you want to chain further inspection after the assertion.
cy.scanA11y(scope?, options?)
Chainable command. Runs an AudioEye scan and resolves to an A11yResults instance. Does not fail the test on its own —
use Cypress assertions or cy.checkA11y for that.
cy.scanA11y().its('isEmpty').should('be.true');
cy.scanA11y('.navbar', { level: 'AA' }).invoke('has', 'Link_ExternalWarning_Missing').should('be.true');
cy.get('.modal').scanA11y().its('count').should('eq', 0);
Parameters
Same as cy.checkA11y.
Returns
Cypress.Chainable<A11yResults>.
A11yScanOptions
| Field | Type | Description |
|---|---|---|
level | 'A' | 'AA' | 'AAA' | WCAG conformance level filter; cumulative by default. |
onlyRules | string[] | Restrict to issues for these rule codes. |
ignoreRules | string[] | Drop issues for these rule codes. |
withinSelector | string | Keep only issues whose target descends from this CSS ancestor. |
excludingSelector | string | Drop issues whose target descends from this CSS ancestor. |
baselineIds | string[] | Drop issues whose stable fingerprint (A11yIssue.id) appears in this list. |
component | boolean | If true, treat the scan as a component test — page-level rules are filtered out. |
A11yResults
A11yResults is the report object returned (wrapped in a Cypress Chainable) by cy.scanA11y and cy.checkA11y. All
filter methods return a new A11yResults, so chains are immutable. See the
Jest API reference for the full list of filters, queries, and the per-issue shape.
In a Cypress chain, prefer .its('property') for getters and .invoke('method', ...args) for methods. The most useful
properties to access this way are count, isEmpty, issues, resultCodes, and
resultsGroupedByWcagSuccessCriteriaLevel.
Recommended onboarding workflow
If you're adopting the Cypress SDK for the first time, this sequence usually works well:
- Install
@audioeye/testing-sdk-cypress. - Import the SDK in
cypress/support/commands.tsorcypress/support/commands.js. - Add one smoke test that calls
cy.checkA11y()aftercy.visit('/'). - Confirm the command runs successfully in local development.
- Add focused checks for key flows such as login, signup, checkout, navigation, or modal dialogs.
- Move the checks into CI once the baseline is stable.
Legacy commands (deprecated)
The cy.accessibility(...) and cy.a11y(...) commands continue to work in v6 but emit a one-time deprecation warning
the first time each is called per process. The new cy.checkA11y and cy.scanA11y commands cover the same use cases
with better autocomplete, optional subjects, and richer filtering. See the v5 → v6 upgrade guide.
// Pre-v6 form. Still works, but you should migrate.
cy.get('html').accessibility('resultCodes').should('deep.equal', ['Heading_Sequence_Wrong']);
cy.get('html').a11y('resultsGroupedByWcagSuccessCriteriaLevel', 'AA').should('have.keys', ['A', 'AA']);
The legacy commands accept a magic-string output parameter ('resultCodes' or
'resultsGroupedByWcagSuccessCriteriaLevel') that controls what they return. The new commands return an A11yResults
instance and you choose what you want from it via .its('resultCodes'),
.its('resultsGroupedByWcagSuccessCriteriaLevel'), .invoke('has', code), and so on.
Troubleshooting
cy.checkA11y() fails with "The 'task' event has not been registered"
If a test fails with
The 'task' event has not been registered in the setupNodeEvents method. You must register it before using cy.task(),
the AudioEye license task is not registered in your Cypress config. v6 runs its license check through a Cypress task, so
you must call registerAudioEyeTasks(on) inside setupNodeEvents.
Resolution
Follow Setup step 2: import registerAudioEyeTasks from
@audioeye/testing-sdk-cypress/plugin and call it inside your config's setupNodeEvents. If you already have a
setupNodeEvents, add the call there instead of creating a second one. Then confirm AUDIOEYE_TESTING_SDK_CLIENT_ID
and AUDIOEYE_TESTING_SDK_CLIENT_TOKEN are set in the environment that runs Cypress.
TypeScript errors in a JavaScript-only Cypress setup
If you use JavaScript only and see a compilation error related to TypeScript declaration files, configure Cypress to
ignore .d.ts files during preprocessing.
Error example
Oops...we found an error preparing this test file:
> cypress/support/e2e.js
The error was:
Error: Webpack Compilation Error
Module parse failed: Unexpected token (3:8)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import { EvaluateRulesOutput } from './rules';
|
> declare const A11Y_RULES_VERSION = "__A11Y_RULES_VERSION__";
| declare const SDK_VERSION = "__SDK_VERSION__";
|
Resolution
Add @cypress/webpack-preprocessor as a development dependency, then configure Cypress to ignore TypeScript declaration
files.
import { defineConfig } from 'cypress';
import webpackPreprocessor from '@cypress/webpack-preprocessor';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
const options = {
webpackOptions: {
module: {
rules: [
{
test: /\.d\.ts$/,
use: 'ignore-loader',
},
],
},
},
};
on('file:preprocessor', webpackPreprocessor(options));
return config;
},
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx}',
},
});
Need to test authenticated pages or multi-step workflows?
That is a strong use case for Cypress. Log in through your normal test setup, navigate to the page state you care about,
and then run cy.checkA11y() (or cy.scanA11y()) on either the full page or a targeted element.
Want to understand which rules run in different environments?
See About Our Rules for details on rule behavior across component, browser, and test-runner contexts.