Skip to main content
Version: v6

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_DIR to a persisted path to carry a warm grace-token cache across jobs within its 7-day lifetime.

What to do before rolling out v6:

  1. Confirm AUDIOEYE_TESTING_SDK_CLIENT_ID and AUDIOEYE_TESTING_SDK_CLIENT_TOKEN are 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.
  2. 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

v5 accessibility API calls and their v6 replacements.
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 jsonunchanged — --format sarif is also available now

Deprecation timeline

  • v6: old APIs keep working. Each emits a one-time console.warn per process so you can find them in CI logs. JSDoc @deprecated tags 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 in ids. Use this with a SARIF baseline file.
  • conformanceLevel(level, { exact: true }) — opt-in exact-level filtering. Cumulative semantics remain the default (AA includes 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.
  • isEmptytrue when no issues remain after filtering.
  • issues — array of issues with their full enriched shape.
  • has(ruleCode)true when 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, complementing cssSelector.
  • 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

// 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();
});

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') and cy.get('.modal').checkA11y() are equivalent.
  • cy.scanA11y() and cy.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 (alongside html / json / csv). The output is SARIF v2.1.0 and includes per-issue partialFingerprints.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.
  • A11yResults getters (resultCodes, resultsGroupedByWcagSuccessCriteriaLevel) still exist with the same shape; note that resultsGroupedByWcagSuccessCriteriaLevel's keys are now always single-level ('A', 'AA', 'AAA', or '') — see the wcagSuccessCriteriaLevelCode change 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 findIssues function from @audioeye/testing-sdk-core is still supported for advanced users.

Quick checklist

  1. Set AUDIOEYE_TESTING_SDK_CLIENT_ID and AUDIOEYE_TESTING_SDK_CLIENT_TOKEN in every environment that runs scans (see the license check).
  2. Update each @audioeye/testing-sdk-* package to v6 via your package manager.
  3. Run your existing test suite. It should pass; deprecation warnings will surface old call sites in your CI logs.
  4. Migrate Jest tests to the matcher or scan.
  5. Migrate Cypress tests to cy.checkA11y / cy.scanA11y.
  6. Migrate Playwright tests to the matcher or scan.
  7. (Optional) Adopt SARIF and --baseline if you run the CLI in CI.
  8. 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.