Skip to main content
Version: v6

Writing Custom Accessibility Tests

The AudioEye Accessibility Testing SDK allows you to write custom tests using the @audioeye/testing-sdk-core package. This package provides the core functionality used for all of our testing frameworks. If specific framework coverage is not provided, you can use this package to write your own tests.

Prerequisites

Before you install the core package, complete the package-manager setup in getting started. Depending on your project, that means configuring either:

  • .npmrc for npm or pnpm
  • .yarnrc.yml for Yarn

Installation

Install the core package as a development dependency:

npm install -D @audioeye/testing-sdk-core

If you need to set up a virtual browser environment, also install:

npm install -D @audioeye/testing-sdk-virtual-dom

If you need to convert test results to different formats:

npm install -D @audioeye/testing-sdk-results

Usage

Use the findIssues function to run AudioEye's rules over an in-memory DOM. It returns a TestingSdkAllResultType, which you can either inspect directly or wrap in A11yResults for a fluent filter API.

Prefer the framework packages when possible

Most teams should reach for @audioeye/testing-sdk-jest, @audioeye/testing-sdk-cypress, or @audioeye/testing-sdk-playwright first. They wrap findIssues and the related helpers in a much smaller, more idiomatic API. Use @audioeye/testing-sdk-core directly only when none of the framework packages fit your runner.

Importing the function

import { findIssues } from '@audioeye/testing-sdk-core';

findIssues type signature

import type { RenderResult } from '@testing-library/react';

type EvaluateRulesInputType = string | RenderResult | HTMLElement | DocumentFragment | JQuery<HTMLElement>;

type RunOptions = {
browserMode: 'virtual' | 'real';
runMode: 'external' | 'embedded';
resultsToFilter?: Set<string>;
component?: boolean;
printTestList?: boolean;
debug?: boolean;
format?: 'html' | 'json' | 'csv' | 'sarif';
viewportDimensions?: {
width: number;
height: number;
};
mobile?: boolean;
output?: string;
stdout?: boolean;
timeout?: number;
cssSelectors?: boolean;
localWindow?: Window;
metadataToExclude?: (keyof RuleMetaOutput)[];
};

type TestingSdkAllResultType = {
ruleResults: TestingSdkRuleResultType[];
exitCode: number;
summaryResults: string;
formattedResults: string;

// New in v6:
errors?: A11yScanError[]; // structured runtime errors
context?: A11yScanContext; // engine + run metadata
};

type TestingSdkRuleResultType = {
ruleCode: string;
ruleMetadata: RuleMetaOutput | undefined;
result: 'fail';
source: string; // outerHTML of the violating element
cssSelector?: string;

// New in v6:
id?: string; // stable SARIF fingerprint
helpUrl?: string; // https://developer.audioeye.com/rules/{ruleCode}
xpath?: string;
boundingBox?: { x: number; y: number; width: number; height: number };
frame?: string[]; // frame chain for iframe contents
message?: string;
relatedNodes?: { cssSelector: string; xpath?: string; html: string }[];
};

type RuleMetaOutput = {
code: string;
description: string;
fixAtSource: boolean;
fullName: string;
sourceFixGuidance?: string;
// One of 'A', 'AA', 'AAA', or '' (no WCAG criteria). When a rule maps to
// multiple WCAG criteria, this is the highest-priority level (A > AA > AAA),
// e.g. a rule covering A and AA criteria reports 'A'.
wcagSuccessCriteriaLevelCode: string;
wcagSuccessCriteriaName: string;
wcagSuccessCriteriaNumber: string;
};

// New in v6: scan-level context attached to every report.
type A11yScanContext = {
scanId: string; // UUID per scan run
timestamp: string; // ISO 8601
url?: string;
viewport?: { width: number; height: number };
runDuration?: number; // milliseconds
rulesEvaluated?: number;
engine: { sdk: string; rules: string };
};

type A11yScanError = {
ruleCode: string;
cssSelector?: string;
message: string;
stack?: string;
};

declare const findIssues: (partialRunOptions?: Partial<RunOptions>) => TestingSdkAllResultType;

declare const setupDocument: (
input: EvaluateRulesInputType,
{
isRealBrowser,
localWindow,
}?: {
isRealBrowser?: boolean | null | undefined;
localWindow?: Window | null | undefined;
},
) => () => void;

declare function isHTMLHtmlElement(input: EvaluateRulesInputType): boolean;

declare const addFormattedResults: (
results: TestingSdkAllResultType,
{ runMode, format }: RunOptions,
url?: URL,
) => TestingSdkAllResultType;

Example: format the results

If you want to write the report to disk in a particular format, layer addFormattedResults on top of findIssues. This is the path the CLI takes internally.

import { addFormattedResults, findIssues, isHTMLHtmlElement } from '@audioeye/testing-sdk-core';
import { setupDocument } from '@audioeye/testing-sdk-virtual-dom';

// If you do not have a real browser environment, you can use setupDocument function to create a virtual DOM.
const cleanup = setupDocument(document.body);
let a11yIssues = findIssues({ component: !isHTMLHtmlElement(document.body) });

// Clean up the virtual DOM
cleanup();

// Add SARIF to the results (or 'html' / 'json' / 'csv').
a11yIssues = addFormattedResults(a11yIssues, {
browserMode: 'virtual',
runMode: 'external',
format: 'sarif',
});

// Handle the results
const { ruleResults, exitCode, summaryResults, formattedResults, context, errors } = a11yIssues;

Example: filter and assert with A11yResults

If you want to consume the results programmatically — assert which rules fired, group by WCAG level, drop a baseline — wrap them in A11yResults:

import { A11yResults, findIssues } from '@audioeye/testing-sdk-core';
import { setupDocument } from '@audioeye/testing-sdk-virtual-dom';

const cleanup = setupDocument(document.body);
const report = new A11yResults(findIssues({ component: false }));
cleanup();

if (!report.conformanceLevel('AA').isEmpty) {
console.error(`Found ${report.count} accessibility issue(s):`);
for (const issue of report.issues) {
console.error(` ${issue.ruleCode}${issue.cssSelector}${issue.helpUrl}`);
}
process.exitCode = 1;
}

See the A11yResults reference on the Jest page for the full list of filter methods, query helpers, and per-issue field shapes (it's the same type across all framework packages).