Upgrading from v5 to v6
v6 introduces a richer, more idiomatic API for each framework — matchers, scoped commands, fluent filters — alongside SARIF output and baselines for the CLI. The old v5 APIs continue to work in v6 but emit deprecation warnings the first time they are used in a process. They will be removed in v7.
This guide explains what changed, how to migrate one framework at a time, and what stayed the same.
Before you upgrade: the license check
This is the one change in v6 that can break an otherwise-working v5 setup, and it is not opt-in. Handle it first.
Starting in v6, every scan runs a license check against the AudioEye API. Both your Client ID and Client Token must be present in the environment when the SDK runs — not only when you install packages, which is all v5 required.
AUDIOEYE_TESTING_SDK_CLIENT_ID=<your client id>
AUDIOEYE_TESTING_SDK_CLIENT_TOKEN=<your client token>
How it behaves:
- Grace window. A successful check caches a signed grace token locally that stays valid for up to 7 days, so scans keep working — including offline — within that window. The SDK refreshes the token online about every 24 hours, but falls back to the cached copy whenever AudioEye can't be reached, so a temporary outage doesn't break your scans.
- Fails closed. If the credentials are missing, rejected, or unverifiable with no valid cached grace token, the scan
stops with a non-zero exit code instead of silently passing. The CLI prints the reason as
Error running scan: <message>. - Network. The machine running scans needs outbound access to the AudioEye API at least once per 7-day window. Fully air-gapped runners are not supported.
- CI caching (optional). Set
AUDIOEYE_TESTING_SDK_CACHE_DIRto a persisted path to carry a warm grace-token cache across jobs within its 7-day lifetime.
What to do before rolling out v6:
- Confirm
AUDIOEYE_TESTING_SDK_CLIENT_IDandAUDIOEYE_TESTING_SDK_CLIENT_TOKENare set in every environment that runs scans — local shells, CI jobs, and any container or test runner. v5 may have only needed credentials at install time; v6 needs them at run time too. - If your CI installs the SDK from Cloudsmith using only the token, make sure both variables (id and token) are also exposed to the steps that actually run the scan.
Full setup details, including portal screenshots and per-package-manager configuration, are in How licensing works.
TL;DR
| You used to write… | In v6, prefer… |
|---|---|
accessibility.evaluate(c).resultCodes (Jest) | expect(c).toBeAccessible() matcher, or scan(c) |
cy.get('html').accessibility('resultCodes') | cy.checkA11y() (assertion) or cy.scanA11y() (chainable) |
cy.get(x).accessibility('resultsGroupedByWcagSuccessCriteriaLevel', 'AA') | cy.scanA11y(x, { level: 'AA' }).its('resultsGroupedByWcagSuccessCriteriaLevel') |
await accessibility.evaluate(loc) (Playwright) | await expect(loc).toBeAccessible(), or await a11y.scan(loc) |
aetest scan --format json | unchanged — --format sarif is also available now |
Deprecation timeline
- v6: old APIs keep working. Each emits a one-time
console.warnper process so you can find them in CI logs. JSDoc@deprecatedtags will strike them through in your editor. - v7: the deprecated APIs are removed.
There is no fixed date for v7. Plan to migrate during your next normal SDK upgrade window.
What's new everywhere
These are not breaking changes — they're pure additions to A11yResults. They work whether you use the new entry points
or stick with the deprecated ones.
New filter methods on A11yResults
withRule(...codes)— keep only issues for the given rule codes.withoutRule(...codes)— drop issues for the given rule codes.withinSelector(selector)— keep only issues whose target descends from the given CSS ancestor selector.excludingSelector(selector)— drop issues whose target is inside that ancestor.excludingIds(ids)— drop issues whose stable fingerprint matches an entry inids. Use this with a SARIF baseline file.conformanceLevel(level, { exact: true })— opt-in exact-level filtering. Cumulative semantics remain the default (AAincludes A and AA).
All return a new A11yResults instance — chains stay immutable.
Selector filters use normal CSS ancestor matching when the framework has access to a live document or browser page
(Jest, Cypress, and Playwright). For example, excludingSelector: '[data-testid="third-party-widget"]' drops issues
inside that element and keeps the rest of the scan.
New convenience getters and methods
count— total issue count after filtering.isEmpty—truewhen no issues remain after filtering.issues— array of issues with their full enriched shape.has(ruleCode)—truewhen at least one issue with that rule code remains.get(ruleCode)— issues for that rule code only.
New per-issue fields
Every issue now carries:
id— a stable SARIF fingerprint (partialFingerprints.issueFingerprint) based on the rule, target path, and target markup. Use it to maintain a baseline.helpUrl— direct link to the rule's developer documentation, e.g.https://developer.audioeye.com/rules/Img_Name_WeakName.xpath— XPath for the target element, complementingcssSelector.boundingBox— element layout box{ x, y, width, height }(real-browser scans only).frame— frame selector chain when the violation is inside an iframe.relatedNodes— additional context elements when a rule surfaces them.
New scan-level context on the report
A11yReport.context carries scanId, timestamp, engine.{sdk, rules}, rulesEvaluated, plus an optional
viewport. Runtime errors during evaluation surface as a structured errors[] array (in addition to the legacy
concatenated summaryResults text).
Bug fix worth knowing about
conformanceLevel('A') and conformanceLevel('AA') now correctly include rules that map to multiple WCAG criteria. v5
stored a comma-joined level code such as 'A,AA' and compared it lexicographically against the filter, which quietly
dropped these rules. v6 collapses each rule's WCAG levels to its single highest-priority value (A > AA > AAA), so
a rule covering criteria at A and AA is bucketed at A and a rule at AA and AAA is bucketed at AA. After upgrading you
may see additional issues appear inside level-A or level-AA buckets — they were always failing, just bucketed wrong.
wcagSuccessCriteriaLevelCode is now a single level
In v5, RuleMetaOutput.wcagSuccessCriteriaLevelCode could be a comma-joined string such as 'A, AA' for rules that
mapped to multiple criteria. In v6 it is always one of 'A', 'AA', 'AAA', or '' (no WCAG criteria) — the
highest-priority level the rule applies at. This affects the describe CLI output, every issue's ruleMetadata, and
the keys returned by resultsGroupedByWcagSuccessCriteriaLevel. If you previously did level.split(',') on the field,
drop the split.
Per-framework migration
Jest
- Matcher (recommended)
- Functional scan
// v5
import { accessibility } from '@audioeye/testing-sdk-jest';
it('image has accessible name', () => {
const results = accessibility.evaluate(render(<Image altText="image" />));
expect(results.resultCodes).toEqual([]);
});
// v6
import { toBeAccessible } from '@audioeye/testing-sdk-jest';
expect.extend({ toBeAccessible });
it('image has accessible name', () => {
expect(render(<Image altText="image" />)).toBeAccessible();
});
// v5
import { accessibility } from '@audioeye/testing-sdk-jest';
const results = accessibility.evaluate(render(<Image altText="image" />));
expect(results.resultCodes).toEqual(['Img_Name_WeakName']);
// v6
import { scan } from '@audioeye/testing-sdk-jest';
const report = scan(render(<Image altText="image" />));
expect(report.resultCodes).toEqual(['Img_Name_WeakName']);
expect(report.has('Img_Name_WeakName')).toBe(true);
scan accepts the exact same input shapes as accessibility.evaluate (string, RenderResult, HTMLElement,
DocumentFragment, JQuery<HTMLElement>).
The a11y and accessibility singleton exports continue to work for now. Their evaluate method warns once per
process and forwards to scan.
Cypress
// v5
cy.get('html').accessibility('resultCodes').should('be.an', 'array');
cy.get('[data-cy="login"]').accessibility('resultCodes').should('deep.equal', ['Link_ExternalWarning_Missing']);
cy.get('html').accessibility('resultsGroupedByWcagSuccessCriteriaLevel', 'AA').should('deep.equal', {
/* ... */
});
// v6
cy.checkA11y(); // assertion-style; fails on issues
cy.checkA11y('[data-cy="login"]');
cy.scanA11y().its('resultCodes').should('have.length.greaterThan', 0);
cy.scanA11y('[data-cy="login"]').invoke('has', 'Link_ExternalWarning_Missing').should('be.true');
cy.scanA11y(null, { level: 'AA' }).its('resultsGroupedByWcagSuccessCriteriaLevel').should('deep.equal', {
/* ... */
});
The new commands work with or without a previous subject:
cy.checkA11y('.modal')andcy.get('.modal').checkA11y()are equivalent.cy.scanA11y()andcy.scanA11y('.modal', options)are both valid.
The cy.accessibility and cy.a11y commands continue to work and emit a one-time deprecation warning the first time
they're called.
About the magic-string output parameter
The legacy commands took a magic string ('resultCodes' or 'resultsGroupedByWcagSuccessCriteriaLevel') as their first
argument to switch what they returned. 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. This makes the IDE autocomplete actually useful and lets the same chain mix in .invoke('conformanceLevel', 'AA')
etc.
Playwright
// v5
import { test, expect } from '@audioeye/testing-sdk-playwright';
test('home', async ({ page, accessibility }) => {
await page.goto('/');
const results = await accessibility.evaluate();
expect(results.conformanceLevel('AA').resultCodes).toEqual([]);
});
// v6 — matcher (recommended)
import { test, expect } from '@audioeye/testing-sdk-playwright';
test('home', async ({ page }) => {
await page.goto('/');
await expect(page).toBeAccessible({ level: 'AA' });
await expect(page.locator('.navbar')).toBeAccessible();
});
// v6 — functional scan, when you need to inspect issues
test('home', async ({ page, a11y }) => {
await page.goto('/');
const report = await a11y.scan(page.locator('.navbar'), { level: 'AA' });
expect(report.has('Link_ExternalWarning_Missing')).toBe(true);
expect(report.issues[0].helpUrl).toMatch(/developer\.audioeye\.com/);
});
The matcher attaches a11y-report.json to test.info() automatically when it fails, so the issue list shows up in your
Playwright HTML report without extra wiring.
scan takes either a Page or a Locator. The default scope when called as await a11y.scan() is the entire page
(matching the matcher's default). The legacy evaluate(locator?) defaulted to page.locator('html:root'); evaluate
continues to work and forwards to scan after warning once.
CLI
The CLI surface is purely additive in v6:
- New
--format sarif(alongsidehtml/json/csv). The output is SARIF v2.1.0 and includes per-issuepartialFingerprints.issueFingerprint, suitable for upload to GitHub code-scanning. - New
--baseline <path>flag. Points at a SARIF file; any issue whose fingerprint appears in the baseline is suppressed from the report. If every issue is suppressed, the exit code drops to 0.
Generate a baseline once and check against it on subsequent runs:
aetest scan https://example.com --format sarif --output ./.aetest-baseline.sarif
aetest scan https://example.com --baseline ./.aetest-baseline.sarif
Existing flags are unchanged. Existing html / json / csv outputs gain the new per-issue fields (id, helpUrl,
xpath, boundingBox, frame, relatedNodes) automatically.
What's still the same
- Rule codes are unchanged.
Img_Name_WeakName,Html_SkipLink_Missing, etc. keep their existing names. Snapshot tests, JIRA tickets, and dashboards keyed on rule codes continue to work. A11yResultsgetters (resultCodes,resultsGroupedByWcagSuccessCriteriaLevel) still exist with the same shape; note thatresultsGroupedByWcagSuccessCriteriaLevel's keys are now always single-level ('A','AA','AAA', or'') — see thewcagSuccessCriteriaLevelCodechange above.conformanceLevel(level)still takes'A','AA', or'AAA'. Cumulative semantics are unchanged (just no longer buggy for multi-criteria rules).- All existing CLI flags (
--component,--print-test-list,--debug,--output,--stdout,--timeout,--viewport-dimensions,--mobile,--css-selectors) work exactly as before. - The core type names (
TestingSdkRuleResultType,TestingSdkAllResultType,EvaluateRulesInputType) remain exported. - The
findIssuesfunction from@audioeye/testing-sdk-coreis still supported for advanced users.
Quick checklist
- Set
AUDIOEYE_TESTING_SDK_CLIENT_IDandAUDIOEYE_TESTING_SDK_CLIENT_TOKENin every environment that runs scans (see the license check). - Update each
@audioeye/testing-sdk-*package to v6 via your package manager. - Run your existing test suite. It should pass; deprecation warnings will surface old call sites in your CI logs.
- Migrate Jest tests to the matcher or
scan. - Migrate Cypress tests to
cy.checkA11y/cy.scanA11y. - Migrate Playwright tests to the matcher or
scan. - (Optional) Adopt SARIF and
--baselineif you run the CLI in CI. - Once all deprecation warnings are gone, you're ready for v7 whenever it ships.
If you hit anything surprising while migrating, see Troubleshooting — and file an issue with a minimal reproduction.