Playwright JS SDK

The Evinced Playwright JS SDK integrates with new or existing Playwright tests to automatically detect accessibility issues. By adding a few lines of code to your Playwright project, you can begin analyzing all the web pages and DOM changes to provide a dynamic view of how your site can become more accessible. As a result of the test, a rich and comprehensive report is generated to easily track issues to resolution.

Interested in seeing this in action? Contact us to get started!

Prerequisites

  • Playwright version 1.25 or higher

Get started

Installation

To install Playwright JS SDK you will need either a file provided by Evinced Support or access to a remote repository that provides it. If you have neither, contact us to get started.

Installation with a locally provided file

With a local copy of the Playwright JS SDK gzipped tar package (.tgz extension), install it in your project using NPM or Node package manager of your choice:

1# Using NPM
2npm install -D <path to js-playwright-sdk-<version>.tgz file>

Installation from a remote repository

Evinced Customers have the option of accessing Playwright JS SDK from a remote repository - the Evinced JFrog Artifactory - to keep their SDK version up-to-date and to share the SDK internally at their organization.

When access is enabled, Playwright JS SDK is available at
https://evinced.jfrog.io/artifactory/restricted-npm/%40evinced/js-playwright-sdk.

Installation using NPM:

1npm install @evinced/js-playwright-sdk

AI Skills

The @evinced/js-playwright-sdk package ships with built-in AI agent skills that guide your AI assistant through integrating the Evinced SDK — from initial setup and writing tests to configuring rules and generating reports.

To activate AI Skills, add the following to your project's AGENTS.md file (create it at the root of your project if it does not exist):

1## Context
2
3Before working on any accessibility testing task, always read:
4
5`node_modules/@evinced/js-playwright-sdk/evinced-ai/entry.mdc`
6
7The entry.mdc file contains:
8- Integration rules for the Evinced Playwright JS SDK
9- Skills for setup, test writing, configuration, reporting, and logging
10- Links to detailed documentation for each task type
11
12## When to use
13
14When the user asks about accessibility testing, WCAG compliance, ARIA attributes, Evinced SDK usage, or accessibility reports or scans, read the entry point file listed under Context.
15
16## Capabilities
17
18- Set up the Evinced SDK in Playwright projects
19- Write accessibility tests using `evAnalyze`, `evStart`, `evStop`
20- Configure accessibility rules and scopes
21- Generate HTML, JSON, SARIF, or CSV reports
22- Configure the SDK — proxy, screenshots, iframes, and `evConfig.json`
23- Enable and tune SDK logging and log levels
24- Control SDK toggles — kill switch, analytics opt-out, and mock engine
25- Integrate accessibility checks into CI/CD pipelines

Note for monorepos: with pnpm, or yarn/npm workspaces, the package may be hoisted to the workspace root. If the path above does not exist, look for evinced-ai/entry.mdc under the workspace root's node_modules instead.

This works with any AI assistant that reads project context files (Cursor, Claude, Copilot, Windsurf, Gemini, and others).

Try asking your AI assistant:

  • "Set up the Evinced SDK in my project"
  • "Add accessibility checks to my existing Playwright test"
  • "Scope the scan to only the navigation bar"

Authentication

To launch Playwright JS SDK, you need to have a Service ID and an API Key.

Where to find your Evinced SDK credentials

These credentials are available via the Evinced Product Hub in the “Automation for Web” or “Automation for Mobile” product areas. Click the “Get SDK” button to see the Service Account ID and API Key at the bottom of the page.

Authenticate for Offline Testing

There are two methods to provide the token: online mode and offline mode. Online mode contacts the Evinced Licensing Server. Offline mode assumes that an Evinced employee has supplied a JSON Web Token (JWT). If an offline token is required, please reach out to your account team or support@evinced.com.

Please set credentials in environment variables and reference the environment variables in code.

1# Online mode
2export EVINCED_SERVICE_ID=<serviceId>
3export EVINCED_API_KEY=<apiKey>
4
5# Offline mode, when a JWT has been provided by Evinced
6export EVINCED_SERVICE_ID=<serviceId>
7export EVINCED_AUTH_TOKEN=<token>

Setting credentials, an example:

Authenticate Playwright JS SDK in Playwright’s globalSetup as shown below for the typical case using CommonJS or TypeScript with CommonJS.

1// global.setup.js
2const { setCredentials } = require("@evinced/js-playwright-sdk");
3
4async function globalSetup(config) {
5 try {
6 await setCredentials({
7 serviceId: process.env.EVINCED_SERVICE_ID,
8 secret: process.env.EVINCED_API_KEY,
9 });
10 } catch (error) {
11 throw new Error("Evinced SDK authorization failure.");
12 }
13}
14module.exports = globalSetup;

Set the full path to the module in the Playwright configuration:

1// playwright.config.js or playwright.config.ts
2globalSetup: require.resolve("./global.setup.js");
Authenticate Using ECMAScript Modules

If package.json declares "type": "module" or the file extension is ".mjs", use ECMAScript module syntax:

1// playwright.config.mjs
2// use pathToFileURL to resolve full path:
3 globalSetup: pathToFileURL('./global.setup.js').pathname,
1// global.settings.mjs
2import evSdk from "@evinced/js-playwright-sdk";
3const { setCredentials } = evSdk;
4
5async function globalSetup(config) {
6 try {
7 await setCredentials({
8 serviceId: process.env.EVINCED_SERVICE_ID,
9 secret: process.env.EVINCED_API_KEY,
10 });
11 } catch (error) {
12 throw new Error("Evinced SDK authorization failure.");
13 }
14}
15export default globalSetup;
Authenticate for Offline Testing

If Evinced has provided an JSON Web Token (JWT) for offline testing, invoke setOfflineCredentials to authenticate rather than setCredentials.

1// global.setup.js
2const { setOfflineCredentials } = require("@evinced/js-playwright-sdk");
3
4async function globalSetup(config) {
5 try {
6 await setOfflineCredentials({
7 serviceId: process.env.EVINCED_SERVICE_ID,
8 token: process.env.EVINCED_AUTH_TOKEN,
9 });
10 } catch (error) {
11 throw new Error("Evinced SDK authorization failure.");
12 }
13}

Your First Test

SDK Initialization

To use Playwright JS SDK, you first need to authenticate. Please refer to Authentication for details.

Place evConfig.yaml, evConfig.yml, or structured evConfig.json at the project root. Authenticate in Playwright's globalSetup before running tests (see the authentication section).

Recommended (evConfig.yaml):

1switchOn: true
2scan:
3 iframes: true
4 rootSelector: '#main'
5report:
6 format: html
7 outputDir: ./evincedReports
8 fileName: aggregatedReport
9 aggregate: true
10platform:
11 upload:
12 enabled: false

Recommended (structured evConfig.json):

1{
2 "switchOn": true,
3 "scan": {
4 "iframes": true,
5 "rootSelector": "#main"
6 },
7 "report": {
8 "format": "html",
9 "outputDir": "./evincedReports",
10 "fileName": "aggregatedReport",
11 "aggregate": true
12 },
13 "platform": {
14 "upload": {
15 "enabled": false
16 }
17 }
18}

Deprecated (legacy flat keys in evConfig.json):

1{
2 "switchOn": true,
3 "includeIframes": true,
4 "rootSelector": "#main",
5 "enableScreenshots": false,
6 "reporterOptions": {
7 "reportFormat": "html",
8 "fileName": "aggregatedReport.html",
9 "outputDir": "./evincedReports"
10 },
11 "uploadToPlatformOptions": {
12 "enableUploadToPlatform": false
13 }
14}

Add Evinced Accessibility Checks (Single Run Mode)

This is a simple example of how to add an Evinced accessibility scan to a test. Please note the inline comments that give detail on each test step.

This test scans https://demo.evinced.com, then generates an Evinced report. The test passes if the report is generated.

1// Use import in ECMAScript module
2// (.mjs extension or type: module in package.json)
3import { test, expect } from "@playwright/test";
4import { existsSync } from "node:fs";
5import { EvincedSDK } from "@evinced/js-playwright-sdk";
6
7test.describe("Evinced evAnalyze", () => {
8 test("Single test run using evAnalyze", async ({ page }) => {
9 const evReport = "./test-results/evAnalyze.html";
10 const evincedService = new EvincedSDK(page);
11 await page.goto("https://demo.evinced.com/");
12 const issues = await evincedService.evAnalyze();
13 await evincedService.evSaveFile(issues, "html", evReport);
14 expect(existsSync(evReport)).toBeTruthy();
15 });
16});
17

Add Evinced Accessibility Checks (Continuous Mode)

This is an example of how to add a continuous Evinced accessibility scan to a test. Using the evStart() and evStop() methods, the Evinced engine will continually scan in the background capturing all DOM changes and page navigation as the test is executed. This will capture all accessibility issues as clicking on drop-downs or similar interactions reveals more of the page. The advantage of continuous mode is that no interaction with the actual test code is needed.

In this test, an Evinced report is generated for https://demo.evinced.com/ after some scripted interaction. The test passes if the report is generated.

1// Use require in CommonJS module (default)
2const { test, expect } = require('@playwright/test')
3const { existsSync } = require('node:fs')
4const { EvincedSDK } = require('@evinced/js-playwright-sdk')
5
6// Use import in ECMAScript module
7import { test, expect } from '@playwright/test'
8import { existsSync } from 'node:fs'
9import evExport from '@evinced/js-playwright-sdk'
10const { EvincedSDK } = evExport
11
12// Test is the same with either module system
13test.describe('Evinced Demo Page', () => {
14 test('Continuous Test', async ({ page }) => {
15 const evReport = './test-results/continuous.html'
16 const evincedService = new EvincedSDK(page)
17 await evincedService.evStart()
18
19 await page.goto('https://demo.evinced.com/')
20
21 const BASE_FORM_SELECTOR =
22 '#gatsby-focus-wrapper > main > div.wrapper-banner > div.filter-container'
23 const SELECT_HOME_DROPDOWN = `${BASE_FORM_SELECTOR} > div:nth-child(1) > div > div.dropdown.line`
24 const SELECT_WHERE_DROPDOWN = `${BASE_FORM_SELECTOR} > div:nth-child(2) > div > div.dropdown.line`
25 const TINY_HOME_OPTION = `${BASE_FORM_SELECTOR} > div:nth-child(1) > div > ul > li:nth-child(2)`
26 const EAST_COST_OPTION = `${BASE_FORM_SELECTOR} > div:nth-child(2) > div > ul > li:nth-child(3)`
27
28 await page.locator(SELECT_HOME_DROPDOWN).click()
29 await page.locator(TINY_HOME_OPTION).click()
30 await page.locator(SELECT_WHERE_DROPDOWN).click()
31 await page.locator(EAST_COST_OPTION).click()
32
33 const issues = await evincedService.evStop()
34 await evincedService.evSaveFile(issues, 'html', evReport)
35 expect(existsSync(evReport)).toBeTruthy()
36 })
37})

API


EvincedSDK

Prepares the Evinced object for use in the project.

The specific SDK initialization is not needed. Just import EvincedSDK in your test files.

1// in CommonJS module context
2const { EvincedSDK } = require('@evinced/js-playwright-sdk');
3
4// In ECMAScript module context
5import sdk from '@evinced/js-playwright-sdk'
6const { EvincedSDK } = sdk

Refer to Configuration to see examples of initializing with options.


evAnalyze(options)

Scans the current page and returns a list of accessibility issues. This is the recommended method for static page analysis.

Note: This method is not supported if evStart() is already running.

1const evincedService = new EvincedSDK(page);
2await page.goto("https://demo.evinced.com/");
3const issues = await evincedService.evAnalyze();

Returns Promise<Issue[]>.

The returned report object contains a list of accessibility issues.

For more information regarding reports as well as the report object itself, please refer to our detailed Web Reports page.


evStart(options)

Continually watches for DOM mutations and page navigation, recording accessibility issues until the evStop() method is called. This method is recommended for dynamic page flows.

1const evincedService = new EvincedSDK(page);
2await evincedService.evStart();
3await page.goto('https://demo.evinced.com/');
4const issues = await evincedService.evStop();

Returns Promise<void>.


evStop(options)

Stops the issue-gathering process started by evStart().

1const evincedService = new EvincedSDK(page);
2await evincedService.evStart();
3await page.goto('https://demo.evinced.com/');
4const issues = await evincedService.evStop();

Returns Promise<Issue[]>.

The returned report object includes all accessibility issues detected between the evStart() and evStop() method calls.

For more information regarding reports as well as the report object itself, please refer to our detailed Web Reports page.


evSaveFile(issues, format, destination)

Saves issues in a file with the specified format and location. Supported formats are json, html, sarif, and csv. Find detailed information in the Web Reports page.

1const evincedService = new EvincedSDK(page);
2await page.goto("https://demo.evinced.com/");
3const issues = await evincedService.evAnalyze();
4evincedService.evSaveFile(issues, 'html', 'test-results/evinced-report.html');
5evincedService.evSaveFile(issues, 'csv', 'test-results/evinced-report.csv');
6evincedService.evSaveFile(issues, 'json', 'test-results/evinced-report.json');
7evincedService.evSaveFile(issues, 'sarif', 'test-results/evinced-report.sarif.json');

Returns Promise<void>.

Aggregated Report

The aggregated report feature allows you to have a general aggregated report for the whole run (not only for one test or suite). This report will contain all the issues found by the tests where evStart() and evStop() commands were called. It is still possible to use the evSaveFile() command in any place of your code along with this Aggregated Report feature.

For using the aggregated report feature, update your config file (evConfig.yaml, evConfig.yml, or structured evConfig.json at the project root).

Single Format Configuration

Recommended (evConfig.yaml):

1report:
2 format: html # Sets a desired format for the report. Available options: html, json, csv, sarif.
3 fileName: aggregatedReportForRun # Base name for the report file (extension added from format).
4 outputDir: ./evincedReports # Directory for the final aggregated report.
5 tmpDir: ./evincedReports/tmp # Per-spec tmp files (optional; OS temp dir by default).
6 aggregate: true # Generate aggregated report at end of run (default: true).

Recommended (structured evConfig.json):

1{
2 "report": {
3 "format": "html",
4 "fileName": "aggregatedReportForRun",
5 "outputDir": "./evincedReports",
6 "tmpDir": "./evincedReports/tmp",
7 "aggregate": true
8 }
9}

Property comments match the YAML example above (format, fileName, outputDir, tmpDir, aggregate).

Deprecated (legacy flat reporterOptions in evConfig.json):

1{
2 "reporterOptions": {
3 "reportFormat": "html", // Sets a desired format for the report. Available options are: html, json, csv, and sarif. Mandatory.
4 "fileName": "aggregatedReportForRun.html", // Specifies a name of the final report. Mandatory.
5 "outputDir": "./evincedReports", // Specifies a path to the final aggregated report file. Mandatory.
6 "tmpDir": "./evincedReports/tmp" // A directory for storing Evinced tmp files. 'os.tmpdir()' by default. Optional.
7 }
8}

Multiple Formats Configuration

Recommended (evConfig.yaml):

1report:
2 format: [html, json, csv, sarif] # Array generates one file per format.
3 fileName: aggregatedReport # Base name; extensions added per format.
4 outputDir: ./evincedReports
5 tmpDir: ./evincedReports/tmp
6 aggregate: true

Recommended (structured evConfig.json):

1{
2 "report": {
3 "format": ["html", "json", "csv", "sarif"],
4 "fileName": "aggregatedReport",
5 "outputDir": "./evincedReports",
6 "tmpDir": "./evincedReports/tmp",
7 "aggregate": true
8 }
9}

This configuration will generate:

  • aggregatedReport.html
  • aggregatedReport.json
  • aggregatedReport.csv
  • aggregatedReport.sarif

Deprecated (legacy flat reporterOptions in evConfig.json):

1{
2 "reporterOptions": {
3 "reportFormat": ["html", "json", "csv", "sarif"], // Array of formats to generate. Available options are: html, json, csv, and sarif. Mandatory.
4 "fileName": "aggregatedReport", // Base name for reports. Extensions will be auto-generated (e.g., aggregatedReport.html, aggregatedReport.json). Mandatory.
5 "outputDir": "./evincedReports", // Specifies a path to the final aggregated report files. Mandatory.
6 "tmpDir": "./evincedReports/tmp" // A directory for storing Evinced tmp files. 'os.tmpdir()' by default. Optional.
7 }
8}

Custom File Names for Multiple Formats

Recommended (evConfig.yaml):

1report:
2 format: [html, json] # Must match fileName array length when using custom names.
3 fileName: [myReport.html, myReport.json]
4 outputDir: ./evincedReports
5 tmpDir: ./evincedReports/tmp
6 aggregate: true

Recommended (structured evConfig.json):

1{
2 "report": {
3 "format": ["html", "json"],
4 "fileName": ["myReport.html", "myReport.json"],
5 "outputDir": "./evincedReports",
6 "tmpDir": "./evincedReports/tmp",
7 "aggregate": true
8 }
9}

Deprecated (legacy flat reporterOptions in evConfig.json):

1{
2 "reporterOptions": {
3 "reportFormat": ["html", "json"],
4 "fileName": ["myReport.html", "myReport.json"], // Array must match reportFormat array length. Mandatory.
5 "outputDir": "./evincedReports",
6 "tmpDir": "./evincedReports/tmp" // A directory for storing Evinced tmp files. 'os.tmpdir()' by default. Optional.
7 }
8}

Then add specify a path to the Evinced reporter in your playwright.config file. It might be found in your node-modules directory, for example:

1'./node_modules/@evinced/js-playwright-sdk/dist/reporter/evincedReporter.js'

Important! The Evinced reporter file path might be different in your project, so please check it before specifying the path in the config file.

To do so add Evinced reporter to a list of reporters, it may look like this:

1reporter: process.env.CI ? 'html' : [['line'], ['./node_modules/@evinced/js-playwright-sdk/dist/reporter/evincedReporter.js']]

Important!

  • For the modern schema, set report.format, report.fileName, and report.outputDir. Use report.aggregate: true (default) to generate an aggregated report at the end of the run; set report.aggregate: false to skip aggregated report generation (no report files are written and an empty report.outputDir is not left behind). For legacy reporterOptions, reportFormat, fileName, and outputDir are mandatory.
  • report.cleanup defaults to true — deletes per-spec tmp files after the run (legacy: reporterOptions.deleteTmpFiles). Set report.cleanup: false to keep tmp files for debugging.
  • format / reportFormat can be a string (single format) or an array (multiple formats). Available formats: html, json, csv, and sarif.
  • fileName can be a string (base name) or an array of strings (one name per format).
  • tmpDir is optional; the environment temp directory is used by default.

And that's it! Now you can run your tests and get the aggregated report(s) in the specified directory.

Adding Aggregated Report when using the Cucumber test reporter

In some cases when using a Cucumber reporter, you may need to add the Evinced reporter to your `BeforeAll` and `AfterAll` hooks in the following way:

1// Import the Evinced reporter,
2// it may look like this in your project (in case of using commonjs):
3const EvincedReporter = require('@evinced/js-playwright-sdk/reporter');

Add the Evinced reporter to your BeforeAll hook:

1BeforeAll(async function () {
2 // your code here
3 new EvincedReporter().onBegin();
4});

Add the Evinced reporter to your AfterAll hook:

1AfterAll(() => {
2 // your code here
3 new EvincedReporter().onEnd();
4});

Configuration

The same configuration options can be used when initializing the Evinced object using evConfig.json and when calling the evStart() and evAnalyze() methods but with a bit different consequences. Providing options when initializing defines a global configuration for all calls of evAnalyze() and evStart(), while providing options to either of those methods affect only the test in which they are called. Some SDKs also let you define configuration outside your code, in a config file that is picked up automatically at startup.

Options provided in either evAnalyze() or evStart() override those set in Evinced engine initialization.

Engines Configuration

Evinced uses two separate engines when scanning for accessibility issues, one is the aXe engine and the other is the Evinced engine. By default, Evinced disables the aXe Needs Review and Best Practices issues based on customer request and due to the fact they are mostly false positives. Please note this setting when comparing issue counts directly. See an example of how to enable Needs Review and Best practices issues in the Toggles section.

Configuration Object

To define global configuration for Playwright JS SDK, place evConfig.yaml, evConfig.yml, or structured evConfig.json at the project root. JSON and YAML formats remain fully supported; only legacy top-level key names are deprecated. Per-command options on EvincedSDK methods use the same nested structure.

1type EvConfig = {
2 switchOn?: boolean;
3 scan?: {
4 rootSelector?: string;
5 iframes?: boolean;
6 iframeDomains?: string[];
7 screenshots?: { enabled?: boolean };
8 withPasses?: boolean;
9 };
10 analysis?: {
11 AXE_CONFIG?: Record<string, unknown>;
12 TOGGLES?: Record<string, boolean>;
13 SKIP_VALIDATIONS?: SkipValidation[];
14 ISSUE_CONTENT_PER_TYPE?: Record<string, { knowledgeBaseLink: string }>;
15 RECORDING_SERVICE?: Record<string, unknown>;
16 [key: string]: unknown;
17 };
18 report?: {
19 format?: 'json' | 'html' | 'csv' | 'sarif' | ('json' | 'html' | 'csv' | 'sarif')[];
20 outputDir?: string;
21 fileName?: string | string[];
22 aggregate?: boolean;
23 cleanup?: boolean;
24 tmpDir?: string;
25 };
26 platform?: {
27 upload?: { enabled?: boolean; autoUpload?: boolean };
28 };
29 logging?: {
30 preset?: 'errors' | 'standard' | 'diagnostic';
31 channels?: { engine?: boolean; sdk?: boolean };
32 };
33 network?: {
34 idle?: { enabled?: boolean; timeout?: number; maxWait?: number };
35 proxy?: Record<string, unknown>;
36 };
37};

Recommended (evConfig.yaml):

1switchOn: true
2scan:
3 iframes: true
4 screenshots:
5 enabled: false

Recommended (structured evConfig.json):

1{
2 "switchOn": true,
3 "scan": {
4 "iframes": true,
5 "screenshots": {
6 "enabled": false
7 }
8 }
9}

Deprecated (legacy flat keys in evConfig.json):

1{
2 "switchOn": true,
3 "includeIframes": true,
4 "enableScreenshots": false
5}

Root Selector

Sets a CSS selector to limit the Evinced Engine to scan only the selected element and its children. Must be a valid CSS selector. If not set, the Evinced Engine will scan the entire document.

Default: no value

Recommended (evConfig.yaml):

1scan:
2 rootSelector: '#main'

Recommended (structured evConfig.json):

1{
2 "scan": {
3 "rootSelector": "#main"
4 }
5}

Recommended (per command):

1const evincedService = new EvincedSDK(page);
2await evincedService.evStart({
3 scan: { rootSelector: '.some-selector' }
4});
5await page.goto('https://demo.evinced.com/');
6const issues = await evincedService.evStop();

Deprecated (legacy flat rootSelector key in evConfig — JSON, YAML, and YML remain supported):

1{
2 "rootSelector": "#main"
3}

Deprecated (per command — legacy flat rootSelector):

1await evincedService.evStart({
2 rootSelector: '.some-selector'
3});

User-Defined Labels

Attaches your own metadata to every issue element, so a report can be grouped by your structure — team, feature area, component — instead of by DOM position. Labels appear on each issue under elements[].userLabels, grouped by the kind of rule that produced them:

1"userLabels": {
2 "matchingSelectors": ["#cart |+ .buy-button"],
3 "attributes": {
4 "data-testid": ["checkout-submit"]
5 },
6 "callback": {
7 "team": ["checkout"],
8 "stableId": ["WRONG_SEMANTIC_ROLE:BuyButton"]
9 }
10}

Three rule types are available, and they can be combined:

  • selector — label an element by a CSS selector it matches or descends from.
  • attribute — label an element with the value of an attribute on it or its nearest ancestor.
  • function — this is the option to reach for when you need a stable identity for an issue that survives DOM changes — for example a component name read from a data- attribute, or an id built from an element's position in your own component tree rather than in the document.

Default: no labels.

All three rule types can be written in a config file. A function rule has to name the callback — with scriptPath or ref — because a file cannot carry a live fn.

1scan:
2 userDefinedLabels:
3 - type: selector
4 selector: '#cart |+ .buy-button'
5 - type: attribute
6 attribute: data-testid
7 scope: closest
8 - type: function
9 scriptPath: ./labels/teamLabels.js

Choosing a form. The forms above differ in one way that matters: in all of them except the last, you hand the SDK the callback itself and the SDK owns installing it — into the top document and into every frame it scans, re-installing into frames that navigate or appear later, so nothing further is required of you. Supply only a ref when the application itself puts the function on the page; covering every frame is then your job, as described below. Where your SDK offers more than one way to hand over the callback, keep a short one where it is used and move a longer one into its own file. Except in Cypress the callback reaches the page as source, so it cannot use helpers, imports, or variables from the file it is written in — define what it needs inside it.

Rules for the callback

A function rule runs your callback in the page under test, once per issue element:

1callback(element, typeId) -> Record<string, string[]>

element is the DOM element the issue was reported on, and typeId is the issue type (the same value as type.id in a JSON report). Whatever the callback returns is merged into that element's userLabels.callback.

Important: The callback itself is never part of the configuration the engine receives. Configuration is plain data — it has to be copied into the page, and then copied again into every frame the engine scans — so it can only carry a name, not a live function.

The function itself must exist on the global object (window) of each page or frame the engine scans, under that name. The engine looks up the name and calls whatever it finds there. This name is the rule's ref — you can choose it yourself, or let the SDK generate one.

Before using this feature, decide whether the function will already be on the page yourself, or whether the SDK should install it for you — see Who installs the callback below.

  • It must be synchronous. The engine uses the return value immediately, so a promise is never awaited. An async function is rejected before it is installed — the rule is skipped and the SDK logs why — because that case can be spotted up front. A plain function that happens to return a promise can only be caught once it runs, so it is reported per element (see below).
  • Each callback needs its own ref. Two function rules resolving to the same ref are rejected, and the second is dropped with a warning. This is not a harmless duplicate: both rules would resolve to the same function on the page and produce identical labels, which is almost always a copy-paste slip rather than an intent.
  • It must return Record<string, string[]> — an object whose values are arrays of strings. Any other shape is reported as an error.
  • It must not throw, though nothing breaks if it does — see below.
  • In every SDK except Cypress it must be self-contained. These SDKs drive the browser from outside it, so the callback is serialised to source and injected into the page: helpers, imports, and variables from the surrounding file are not in scope when it runs. Inline everything it needs. Cypress runs inside the browser, so its callback may use values from the spec.
  • Label keys and values are capped (20 keys and 50 values per key per callback; 200 characters each). Anything over the cap is dropped rather than truncated, because these are identity strings and a truncated id would silently collide with another.

Example

A callback that gives each issue an owner and an id tied to your component tree rather than to the element's position in the document:

1function (element, typeId) {
2 // Helpers live inside the callback on purpose: outside Cypress it reaches the page as
3 // source, so nothing from the file it was written in is in scope when it runs.
4 function slug(value) {
5 return String(value == null ? '' : value)
6 .toLowerCase()
7 .replace(/[^a-z0-9]+/g, '-')
8 .replace(/^-+|-+$/g, '')
9 .slice(0, 40);
10 }
11
12 var card = element.closest('[data-component]');
13 if (!card) {
14 // Return an empty object, not nothing: `undefined` is not a `Record<string, string[]>`,
15 // so falling off the end here would put a `__callbackError` on every element that
16 // misses the lookup.
17 return {};
18 }
19
20 var labels = {};
21 var team = card.getAttribute('data-team');
22 if (team) {
23 labels.team = [team];
24 }
25 // Pair the type with the component so two different issues on one component stay apart.
26 labels.stableId = [typeId + ':' + slug(card.getAttribute('data-component'))];
27 return labels;
28}

Given <div data-component="Buy Button" data-team="checkout"> around the reported element, that returns:

1{
2 "team": ["checkout"],
3 "stableId": ["WRONG_SEMANTIC_ROLE:buy-button"]
4}

which arrives in the report under the callback bucket of that element:

1"userLabels": {
2 "callback": {
3 "team": ["checkout"],
4 "stableId": ["WRONG_SEMANTIC_ROLE:buy-button"]
5 }
6}

The keys are yours — team and stableId here — and every value is an array of strings, even when there is only one. Re-run the scan after the DOM around the button changes and stableId stays the same, which is the point: it is derived from your component, not from the element's position.

Who installs the callback

This is the decision from above, spelled out. The two arrangements differ in one important way — who's responsible for getting the function into every frame the engine scans, not just the top page.

The SDK installs it — you supply the function (or a .js file, or JavaScript source). The SDK puts it on the page for you, in the top document and in every frame it scans, re-installing into frames that navigate or appear later. Nothing further is required of you.

You install it — you assign the function to a global of your own on the page and pass only that name as the rule's ref. The SDK then installs nothing, and the engine looks the ref up separately in every frame it scans. So with iframe scanning enabled, you have to assign your function in each frame's own global scope as well; a frame where the ref is not defined reports __callbackError for every issue found there.

Either way the ref must start with __evUserLabelsFn_. The engine refuses any other name, because the global object is full of callable things that are not label callbacks: a ref of print or alert would otherwise resolve, pass the engine's is-it-a-function check, and be invoked once per issue element. Names beginning __evUserLabelsFn_auto_ are reserved for the ones the SDK generates, so a name you choose can never collide with one of those.

When a callback misbehaves

A broken callback never fails the scan. The problem is reported in the report itself, under a reserved __callbackError label key beside the real labels, so it can be found without reading SDK logs:

1"userLabels": {
2 "callback": {
3 "__callbackError": [
4 "callback '__evUserLabelsFn_0' threw: el.closest is not a function"
5 ]
6 }
7}

The same label key covers a callback that is missing, throws, returns the wrong shape, returns a promise, or exceeds the label limits. To audit a run, search a JSON report for __callbackError.

Axe Configuration

Configures Axe open-source accessibility toolkit, which the Evinced engine includes with its own, more extensive accessibility detection. For full Axe config options, see Axe Core API.

Recommended (per command):

1const evincedService = new EvincedSDK(page);
2await page.goto('https://demo.evinced.com/');
3const issues = await evincedService.evAnalyze({
4 analysis: {
5 AXE_CONFIG: {
6 rules: {
7 'link-name': { enabled: false }
8 }
9 }
10 }
11});

Deprecated (per command — legacy flat axeConfig):

1const issues = await evincedService.evAnalyze({
2 axeConfig: {
3 rules: {
4 'link-name': { enabled: false }
5 }
6 }
7});

Engine Logging

Set level of messages the Evinced engine will print to the console. Valid levels are "debug", "info", "warn" and "error".

Default: "error"

Configure SDK log output with the logging.* section in evConfig.yaml, evConfig.yml, or structured evConfig.json. To include analysis-engine messages in the console, set logging.channels.engine: true or use preset diagnostic (includes the engine channel).

Recommended (evConfig.yaml):

1logging:
2 preset: diagnostic

Recommended (enable engine channel only):

1logging:
2 channels:
3 engine: true

Deprecated (legacy flat sdkLogging key in evConfig):

1{
2 "sdkLogging": {
3 "enable": true,
4 "level": "debug",
5 "enableEngineLogs": true
6 }
7}

Reports Screenshots

When true, the Evinced SDK will include screenshots in its reports that highlight elements with accessibility issues.

Default: false.

Note: Enabling screenshots may affect test run performance.

screenshot of a demo.evinced.com page with an issue highlighted

Recommended (per command):

1const issues = await evincedService.evAnalyze({
2 scan: { screenshots: { enabled: true } }
3});

Deprecated (per command — legacy flat enableScreenshots):

1const issues = await evincedService.evAnalyze({
2 enableScreenshots: true
3});

How screenshots are captured

When screenshots are enabled, the SDK captures them itself, through Playwright's own screenshot API. On Chromium that is a Chrome DevTools Protocol capture — Playwright drives Chromium over CDP — and it works on Firefox and WebKit too.

If a capture fails, that issue appears in the report without a screenshot and an error naming the reason is written to the SDK log; the scan itself is unaffected. Later captures are attempted normally. There is no fallback to engine-side capture, because Playwright's capture works on every browser it supports.

Captures are full-page and rendered at one image pixel per CSS pixel, so the issue highlights in an HTML report line up with the image. This is not configurable.

Known limitations

An issue on an element inside an <iframe> is highlighted at the wrong position in the HTML report: the element's coordinates are relative to the iframe document rather than to the top-level page. The screenshot itself is correct and the issue is reported normally — only the highlight is misplaced.

Switching back to engine-side capture

scan.screenshots.mode chooses which side captures. It is read only when scan.screenshots.enabled is true.

ValueEffect
unsetThe SDK captures the screenshots. This is the default.
'sdk'Identical to leaving it unset. Accepted so that configurations written before SDK-side capture became the default keep working — it no longer switches anything on.
'browser'The analysis engine captures the screenshots in the page, as it did before SDK-side capture. The image is redrawn inside the page rather than taken by the browser's own compositor, so it can differ from what you see on screen.
'off'Skips screenshots for this call even when enabled: true is set globally. Useful for a single spec; to turn screenshots off everywhere, prefer enabled: false.

Reach for 'browser' only where SDK-side capture does not suit your environment; leave mode unset otherwise.

1const issues = await evincedService.evAnalyze({
2 scan: { screenshots: { enabled: true, mode: 'browser' } }
3});

Toggles

Enables experimental features. Feature names and values may vary from release to release.

Example: Recommended (per command):

1const issues = await evincedService.evAnalyze({
2 analysis: {
3 TOGGLES: {
4 USE_AXE_NEEDS_REVIEW: true,
5 USE_AXE_BEST_PRACTICES: true
6 }
7 }
8});

Deprecated (per command — legacy flat toggles):

1const issues = await evincedService.evAnalyze({
2 toggles: {
3 USE_AXE_NEEDS_REVIEW: true,
4 USE_AXE_BEST_PRACTICES: true
5 }
6});

Skip Validations

Sets validation types to be skipped for specified URL pattern and CSS selector. Issue type IDs can be found by inspecting a JSON report as described in Web Reports.

Default: no validations skipped.

Recommended (per command):

1const skipSelector = {
2 selector: 'testValue.slds-checkbox',
3 urlRegex: '.*',
4 validationTypes: ['WRONG_SEMANTIC_ROLE', 'NOT_FOCUSABLE', 'NO_DESCRIPTIVE_TEXT']
5};
6
7await evincedService.evStart({
8 analysis: { SKIP_VALIDATIONS: [skipSelector] }
9});

Deprecated (per command — legacy flat skipValidations):

1await evincedService.evStart({ skipValidations: [skipSelector] });

Skipping every validation type

To suppress all validation types for a selector, pass the wildcard * in place of a list of type names.

The wildcard must be the only value. Listed alongside concrete type names it is not treated as a wildcard — the wildcard entry is ignored and only the concrete types given with it are skipped.

1const skipEverythingInParent = {
2 selector: '#parent, #parent *',
3 urlRegex: '.*',
4 validationTypes: '*'
5};
6
7await evincedService.evStart({
8 analysis: { SKIP_VALIDATIONS: [skipEverythingInParent] }
9});

A selector matches elements, not subtrees

Skip rules are evaluated against each reported element on its own, so a selector suppresses issues only on the elements it matches — not on anything nested inside them. To cover a container and its contents, list both the container and its descendants:

1#parent, #parent *

#parent alone suppresses only issues reported on #parent itself.

Include Validations

Reports only the validation types listed, and drops everything else. This is the allowlist counterpart of Skip Validations: skip removes selected types from an otherwise full report, while include restricts the report to selected types.

Use it to focus a test run on one area, such as color contrast or missing labels, without listing every other type to exclude.

Issue type IDs can be found by inspecting a JSON report as described in Web Reports.

Default: every validation type is reported.

The setting is global. Unlike Skip Validations it takes no URL pattern and no CSS selector, and it applies to the whole scan. It is available only through the modern analysis configuration schema shown below; there is no legacy flat spelling for it.

1const includedTypes = ['NO_DESCRIPTIVE_TEXT', 'WRONG_SEMANTIC_ROLE'];
2
3// For one continuous-mode session:
4await evincedService.evStart({
5 analysis: { INCLUDE_VALIDATIONS: includedTypes }
6});
7
8// Or for a single analysis:
9const report = await evincedService.evAnalyze({
10 analysis: { INCLUDE_VALIDATIONS: includedTypes }
11});

Reporting every validation type

Leaving the setting out reports every validation type. So does the wildcard *, on its own or as a single-item list, and so does an empty list.

A wildcard listed next to concrete type names is not a wildcard. ['NOT_FOCUSABLE', '*'] reports only NOT_FOCUSABLE; the * entry is treated as a type name that matches nothing. This mirrors the behavior of Skip Validations.

Combining with Skip Validations

The two settings apply together, and a result is reported only if it passes both. Include narrows the set of types; skip can still suppress individual elements within the types that remain. A type named in both is reported only on the elements its skip rule does not match.

Effect on passed validations

The setting narrows passed results as well as failed ones. Passed results appear in a report only when Passed Validations is enabled. When it is enabled, they are restricted to the same list of types.

Type names are not validated

Type names are not checked against the engine's list of issue types, so a name that is not a real type ID matches nothing.

Note: an empty list and a list of names that match nothing produce opposite results. An empty list reports every validation type. A list such as ['NOT_FOCUSABL'] reports no issues at all. If a run returns zero issues unexpectedly, check the type names in the list against a JSON report.

Recording Service

This setting controls how frequently event-triggered functions are executed, helping to optimize performance and responsiveness. The option does not enable or disable the event-handling service itself but determines how frequently events are processed based on the selected mode.

Available modes:

  • debounce - Delays execution until a set time has passed since the last event. Useful for actions triggered by continuous input, such as typing in a search box, to avoid excessive function calls.
  • throttle - Ensures execution at fixed intervals, regardless of event frequency. Ideal for scenarios like handling window resize events or scroll tracking, where limiting execution prevents performance degradation.

Default: Functions execute as triggering events occur.

An example of how to modify settings: Recommended (per command):

1const evincedService = new EvincedSDK(page);
2
3await evincedService.evStart({
4 analysis: {
5 RECORDING_SERVICE: {
6 ENABLE_DEBOUNCE_NEW_SELECTORS: true,
7 DEBOUNCE_NEW_SELECTORS_MS: 500,
8 DELAY_MODE: 'throttle'
9 }
10 }
11});
12
13await page.goto('https://demo.evinced.com/');
14
15await evincedService.evStop();

Deprecated (per command — legacy flat recordingService):

1await evincedService.evStart({
2 recordingService: {
3 ENABLE_DEBOUNCE_NEW_SELECTORS: true,
4 DEBOUNCE_NEW_SELECTORS_MS: 500,
5 DELAY_MODE: 'throttle'
6 }
7});

Knowledge-Base Link Overrides

Sets customized knowledge-base links in the reports. The links are displayed in the reports “Issue Type” column, as shown in the following screenshot:

An issue report table where the “Issue Type” column contains links to a knowledge base

The knowledge base link can be overridden for every issue type ID. Issue type IDs can be found by inspecting a JSON report as described in Web Reports. For example, the issue with name Interactable Role has ID WRONG_SEMANTIC_ROLE.

Recommended (per command):

1const issues = await evincedService.evAnalyze({
2 analysis: {
3 ISSUE_CONTENT_PER_TYPE: {
4 WRONG_SEMANTIC_ROLE: {
5 knowledgeBaseLink: 'https://yourKnowlegdeBase.com/'
6 }
7 }
8 }
9});

Deprecated (per command — legacy flat issuesContentPerType):

1const issues = await evincedService.evAnalyze({
2 issuesContentPerType: {
3 WRONG_SEMANTIC_ROLE: {
4 knowledgeBaseLink: 'https://yourKnowlegdeBase.com/'
5 }
6 }
7});

Shadow DOM Support

Shadow DOM is now supported by default. No additional configuration is needed.

IFrames Support

When true, accessibility analysis includes iframe that exist inside the page.

Default: true.

Recommended (evConfig.yaml):

1scan:
2 iframes: false

Recommended (structured evConfig.json):

1{
2 "scan": {
3 "iframes": false
4 }
5}

Recommended (per session or analysis):

1await evincedService.evStart({ scan: { iframes: false } });
2const issues = await evincedService.evAnalyze({ scan: { iframes: false } });

Deprecated (legacy flat includeIframes key in evConfig — JSON, YAML, and YML remain supported):

1{
2 "includeIframes": false
3}

Deprecated (per session or analysis — legacy flat includeIframes):

1await evincedService.evStart({ includeIframes: false });
2const issues = await evincedService.evAnalyze({ includeIframes: false });

Important! The current implementation does not support an analysis of Cross Origin iFrames. Only issues from the iFrames with the origin of the parent iFrame will be gathered.

Note: Since iframe analysis is enabled by default, your tests may take longer to run when analyzing pages with multiple or complex iFrames.

Include Hidden IFrames Domain

Configures which hidden iframe domains are included in accessibility analysis. By default, hidden iframes are excluded for performance and security reasons.

This setting allows you to specify a list of domains to analyze even when the iframe is hidden.

Supported Pattern Formats:

  • Exact match: "example.com" - matches the exact URL string
  • Regex with flags: "/.*\\.google\\.com.*/i" - matches using regex with optional flags (i, g, m, u, y)
  • Raw regex: ".*\\.example\\.com.*" - matches using a raw regex pattern

Recommended (per command):

1const issues = await evincedService.evAnalyze({
2 scan: {
3 iframes: true,
4 iframeDomains: [
5 'https://exact-domain.com',
6 '/.*\\.google\\.com.*/i',
7 '.*\\.example\\.com.*'
8 ]
9 }
10});

Deprecated (per command — legacy flat keys):

1const issues = await evincedService.evAnalyze({
2 includeIframes: true,
3 includeHiddenIframeDomains: [
4 'https://exact-domain.com',
5 '/.*\\.google\\.com.*/i',
6 '.*\\.example\\.com.*'
7 ]
8});

Passed Validations

By default, the Evinced SDK only reports accessibility issues that have failed validation. However, you can also configure the SDK to include passed validations in your reports. Passed validations represent accessibility checks that were successfully completed without any issues found.

Why include passed validations?

Including passed validations in your reports provides several benefits:

  • Comprehensive Coverage: Get a complete picture of all accessibility checks performed, not just the failures
  • Compliance Documentation: Demonstrate which accessibility standards your application successfully meets
  • Trend Analysis: Track improvements over time by monitoring both failed and passed validation counts
  • Quality Assurance: Verify that accessibility checks are running as expected across your entire application
  • Regulatory Reporting: Provide evidence of accessibility testing coverage for compliance audits

Report Structure

When passed validations are enabled, your reports will include both:

  • failedValidations: Array of accessibility issues that need to be fixed
  • passedValidations: Array of accessibility checks that passed successfully

Default: false (passed validations are not included)

Enable Passed Validations

To include passed validations in your reports, set the includePassedValidations option to true:

Warning: Enabling passed validations has a performance impact on your testing as the reporting overhead is significantly greater than when reporting just found issues.

Single Page Analysis with Passed Validations

1import { test } from '@playwright/test';
2import { EvincedSDK } from '@evinced/js-playwright-sdk';
3
4test('Analyze page with passed validations', async ({ page }) => {
5 const evincedService = new EvincedSDK(page);
6
7 await page.goto('https://demo.evinced.com');
8
9 // Run analysis with passed validations enabled
10 const report = await evincedService.evAnalyze({
11 includePassedValidations: true
12 });
13
14 // Report now contains both failed and passed validations
15});

Continuous Analysis with Passed Validations

1import { test } from '@playwright/test';
2import { EvincedSDK } from '@evinced/js-playwright-sdk';
3
4test('Continuous analysis with passed validations', async ({ page }) => {
5 const evincedService = new EvincedSDK(page);
6
7 // Start continuous analysis with passed validations
8 await evincedService.evStart({
9 includePassedValidations: true
10 });
11
12 // Navigate and interact with your application
13 await page.goto('https://demo.evinced.com');
14 await page.getByText('Select').first().click();
15 await page.getByText('Tiny House').click();
16
17 // Stop analysis and get comprehensive report
18 const report = await evincedService.evStop();
19});

Saving Reports with Passed Validations

1test('Save report with passed validations', async ({ page }) => {
2 const evincedService = new EvincedSDK(page);
3
4 await evincedService.evStart({ includePassedValidations: true });
5 await page.goto('https://demo.evinced.com');
6 const report = await evincedService.evStop();
7
8 // Save complete report (includes both failed and passed validations)
9 await evincedService.evSaveFile(
10 report,
11 'json',
12 './reports/complete-report.json'
13 );
14});

Configuration file

Recommended (evConfig.yaml):

1scan:
2 withPasses: true
3 screenshots:
4 enabled: true
5 rootSelector: main

Recommended (structured evConfig.json):

1{
2 "scan": {
3 "withPasses": true,
4 "screenshots": {
5 "enabled": true
6 },
7 "rootSelector": "main"
8 }
9}

Deprecated (legacy flat keys in evConfig — JSON, YAML, and YML remain supported):

1{
2 "includePassedValidations": true,
3 "enableScreenshots": true,
4 "rootSelector": "main"
5}

Recommended (per command):

1await evincedService.evAnalyze({ scan: { withPasses: true } });
2await evincedService.evStart({ scan: { withPasses: true } });

Deprecated (per command — legacy flat includePassedValidations):

1await evincedService.evAnalyze({ includePassedValidations: true });
2await evincedService.evStart({ includePassedValidations: true });

Report Structure

Without passed validations (includePassedValidations: false):

1{
2 "issues": [
3 // Array of failed validation issues only
4 ],
5 "screenshotsMap": { /* screenshot data */ }
6}

With passed validations (includePassedValidations: true):

1{
2 "failedValidations": [
3 // Array of issues that failed validation
4 ],
5 "passedValidations": [
6 // Array of checks that passed validation
7 ],
8 "screenshotsMap": { /* screenshot data */ }
9}

Proxy

Configures proxy server access settings. Needed to enable outbound communication to the Evinced Platform through a proxy server.

network.proxy routes the SDK's own outbound HTTP through a proxy — platform upload, setCredentials() online token generation, usage analytics, and evExportReport()'s signed-URL calls. This is separate from Playwright's own use.proxy in playwright.config.ts, which routes the browser's page traffic (the pages under test) through a proxy. Configure both if you need proxying for both traffic types.

Configure network.proxy through evConfig.yaml, evConfig.yml, or structured evConfig.json:

Recommended (evConfig.yaml):

1network:
2 proxy:
3 host: myproxy.com
4 port: 3128
5 protocol: http
6 username: usr
7 password: pwd

Recommended (structured evConfig.json):

1{
2 "network": {
3 "proxy": {
4 "host": "myproxy.com",
5 "port": 3128,
6 "protocol": "http",
7 "username": "usr",
8 "password": "pwd"
9 }
10 }
11}

Deprecated (legacy flat proxy key in evConfig — JSON, YAML, and YML remain supported):

1{
2 "proxy": {
3 "host": "myproxy.com",
4 "port": 3128,
5 "protocol": "http",
6 "username": "usr",
7 "password": "pwd"
8 }
9}

Environment variable fallback (used only when network.proxy.host is not set):

1export HTTPS_PROXY=http://myproxy.com:3128
2export PROXY_USERNAME=usr
3export PROXY_PASSWORD=pwd

HTTPS_PROXY names the proxy used for HTTPS destinations — it does not mean the proxy endpoint itself speaks TLS. Most corporate proxies are plain HTTP servers even when proxying HTTPS traffic (they tunnel it via CONNECT), so http:// is almost always correct here; use https:// only if your proxy itself terminates TLS. HTTP_PROXY is read for non-HTTPS destination URLs, HTTPS_PROXY otherwise (lowercase http_proxy / https_proxy are also accepted). Credentials embedded directly in the proxy URL (http://usr:pwd@myproxy.com:3128) take precedence over PROXY_USERNAME / PROXY_PASSWORD.

Bypassing the Proxy for Specific Hosts (network.noProxy)

Use network.noProxy to skip the proxy for specific destination hosts — useful when your proxy can't (or shouldn't) reach an internal endpoint, or when only some of the SDK's outbound destinations need to go through it.

1network:
2 proxy:
3 host: myproxy.com
4 port: 3128
5 protocol: http
6 noProxy:
7 - internal.example.com
8 - '*.corp.local'

Each entry may be:

  • An exact hostname (internal.example.com) — matches that host and any of its subdomains
  • A domain with a leading dot (.corp.local) — matches the domain and its subdomains
  • '*' — bypasses the proxy for every destination

network.noProxy is merged with the standard NO_PROXY / no_proxy environment variable (comma-separated list, same entry forms) into a single bypass list — a match from either source skips the proxy, regardless of whether the proxy itself came from network.proxy or the HTTPS_PROXY/HTTP_PROXY environment fallback above.

Recommended (Playwright global proxy in playwright.config.ts, for page/browser traffic):

1use: {
2 proxy: {
3 server: 'http://myproxy.com:3128',
4 username: 'usr',
5 password: 'pwd'
6 }
7}

Network Idle Detection

The networkIdle configuration enables the SDK to wait for all network requests to complete before running accessibility analysis. This is particularly useful for modern web applications that load content dynamically after the initial page load.

Why use Network Idle Detection?

When testing modern web applications, accessibility analysis may run before all content has loaded, resulting in:

  • Incomplete results: Dynamic content not yet rendered won't be analyzed
  • False negatives: Accessibility issues in delayed content won't be detected
  • Inconsistent results: Analysis timing may vary between test runs

Network idle detection solves this by ensuring all network activity has settled before analysis begins.

Configuration Options:

OptionTypeDefaultDescription
enabledbooleanfalseEnables or disables network idle detection
idleTimeoutnumber300Time in milliseconds to wait after the last network request completes before considering the network idle
maxWaitTimenumber7500Maximum time in milliseconds to wait for network idle. If exceeded, analysis proceeds with a warning

Network idle detection can be configured globally during SDK initialization or per-command for granular control.

Global Configuration

Recommended (evConfig.yaml):

1network:
2 idle:
3 enabled: true
4 timeout: 300
5 maxWait: 7500

Recommended (structured evConfig.json):

1{
2 "network": {
3 "idle": {
4 "enabled": true,
5 "timeout": 300,
6 "maxWait": 7500
7 }
8 }
9}

Deprecated (legacy flat networkIdle key in evConfig — JSON, YAML, and YML remain supported):

1{
2 "networkIdle": {
3 "enabled": true,
4 "idleTimeout": 300,
5 "maxWaitTime": 7500
6 }
7}

Per-Command Configuration

Recommended:

1await evinced.evStart({
2 network: { idle: { enabled: true, maxWait: 10000 } }
3});
4
5await evinced.evAnalyze({
6 network: { idle: { enabled: true, timeout: 500, maxWait: 5000 } }
7});

Deprecated (per command — legacy flat networkIdle):

1await evinced.evStart({
2 networkIdle: {
3 enabled: true,
4 maxWaitTime: 10000
5 }
6});
7
8await evinced.evAnalyze({
9 networkIdle: {
10 enabled: true,
11 idleTimeout: 500,
12 maxWaitTime: 5000
13 }
14});

Usage Example

1import { test, expect } from "@playwright/test";
2import { EvincedSDK } from "@evinced/js-playwright-sdk";
3
4test('network idle with continuous mode', async ({ page }) => {
5 const evincedService = new EvincedSDK(page);
6
7 await evincedService.evStart({
8 network: { idle: { enabled: true } }
9 });
10 await page.goto('https://example.com/');
11 await page.locator('[data-nav="products"]').click();
12 const issues = await evincedService.evStop();
13
14 await page.goto('https://example.com/products');
15 await evincedService.evAnalyze({
16 network: { idle: { enabled: true } }
17 });
18});

Configuration Options

  • network.idle.enabled (boolean, default: false) - Enable/disable network idle detection
  • network.idle.timeout (number, default: 300) - Milliseconds to wait with no active requests before considering network idle
  • network.idle.maxWait (number, default: 7500) - Maximum time to wait before proceeding anyway

How It Works

Network idle detection waits for network activity to settle before running accessibility analysis. This ensures analysis runs on a stable page with all dynamic content loaded.

  • Static Analysis (evAnalyze): Waits for network idle before running analysis
  • Continuous Mode (evStart/evStop): Waits for network idle only at evStart() (session start), not after navigation or at evStop()

Priority Order

  1. Method options (highest priority) - Overrides everything
  2. evConfig.json - Used if not in method options
  3. Defaults - network.idle.enabled: false, network.idle.timeout: 300, network.idle.maxWait: 7500

How It Works:

The SDK monitors all network requests and waits for the network to become idle before proceeding with analysis:

  1. Network is considered "idle" when no requests are active for at least idleTimeout milliseconds
  2. Analysis waits for idle state or maxWaitTime, whichever comes first
  3. Configuration from evStart() automatically applies to subsequent page navigations and evStop()

Best Practices:

  • Start with defaults: Enable with default settings and adjust only if needed
  • Increase timeout for slow APIs: If your backend is slow, increase maxWaitTime
  • Use per-command for specific pages: Enable only for pages with dynamic content

Important Notes:

  • Disabled by default: The feature is opt-in to maintain backward compatibility
  • No overhead when disabled: When not enabled, there is zero performance impact
  • Automatic timeout extension: Command timeouts are automatically adjusted based on maxWaitTime
  • Warning on timeout: If maxWaitTime is exceeded, the SDK logs a warning and proceeds with analysis

Global Switch

When false, disables Evinced functionality. Enabled by default, use this setting to disable Evinced accessibility analysis when not needed during test development or when running CI jobs where accessibility testing is not intended.

Default: true.

When switched off:

  • evStart() and evSaveFile() will be bypassed.
  • evStop() and evAnalyze() will return an empty report.

Switching Evinced Functionality Off in Configuration

Add the following to your evConfig file (evConfig.yaml, evConfig.yml, or structured evConfig.json at the project root). JSON and YAML formats remain fully supported; only legacy top-level key names are deprecated.

Recommended (evConfig.yaml):

1switchOn: false

Recommended (structured evConfig.json):

1{
2 "switchOn": false
3}

Alternative (per command with the same modern key):

1await evincedService.evStart({ switchOn: false });

Deprecated (legacy flat sdkLogging keys in evConfig):

1{
2 "sdkLogging": {
3 "enable": true,
4 "level": "debug"
5 }
6}

Important! Global Switch environment variable overrides the global configuration option.

Switching Evinced Functionality Off in Environment

1export EV_SWITCH_ON=false

Uploading Reports to Evinced Platform

Introduction

Evinced Platform allows you to seamlessly collect, organize, visualize and monitor Evinced accessibility reports in one place. In this section, we will guide you through the key functionalities of the upload methods of the accessibility reports from the Evinced SDK to the Evinced Platform, which was introduced in version 2.14.0. This upload method is fully compatible with the previous versions of the Evinced SDK API, and is disabled by default.

Enable Upload Report to Platform

To enable the uploading functionality of accessibility reports to the Evinced Platform you will need to set the enableUploadToPlatform feature flag to true via the global setUploadToPlatformConfig method:

1import { setUploadToPlatformConfig } from "@evinced/js-playwright-sdk";
2setUploadToPlatformConfig({ enableUploadToPlatform: true });

You can also use evConfig.yaml, evConfig.yml, or structured evConfig.json.

Warning: The external config has more precedence if both initialization options are used.

Recommended (evConfig.yaml):

1platform:
2 upload:
3 enabled: true
4 autoUpload: false

Recommended (structured evConfig.json):

1{
2 "platform": {
3 "upload": {
4 "enabled": true,
5 "autoUpload": false
6 }
7 }
8}

Deprecated (legacy flat keys in evConfig.json):

1{
2 "uploadToPlatformOptions": {
3 "enableUploadToPlatform": true,
4 "setUploadToPlatformDefault": false
5 }
6}

Note: Using uploadToPlatform: true in method parameters (e.g., evAnalyze({ uploadToPlatform: true })) is not sufficient on its own. You must first enable the feature via setUploadToPlatformConfig(), or in your config file. The method parameter only controls whether a specific report uploads when the feature is already enabled.

Automatic Report Upload

Once the enableUploadToPlatform method is set to true and setUploadToPlatformDefault is true (which is the default), all generated reports will be automatically uploaded to the Platform immediately upon calling the evStop() or evAnalyze() command.

How it works:

  • When enableUploadToPlatform: true and setUploadToPlatformDefault: true (default), upload happens automatically
  • No additional code is needed - just call evAnalyze() or evStop() and the report uploads
  • Upload occurs synchronously as part of the command execution

If you want to change this behavior and control uploads manually, set the setUploadToPlatformDefault feature flag to false.

When upload is enabled, set platform.upload.autoUpload to true to upload reports automatically at the end of each analysis session (legacy: setUploadToPlatformDefault: true).

Recommended (evConfig.yaml):

1platform:
2 upload:
3 enabled: true
4 autoUpload: true

Recommended (structured evConfig.json):

1{
2 "platform": {
3 "upload": {
4 "enabled": true,
5 "autoUpload": true
6 }
7 }
8}

Deprecated (legacy flat keys in evConfig.json):

1{
2 "uploadToPlatformOptions": {
3 "enableUploadToPlatform": true,
4 "setUploadToPlatformDefault": true
5 }
6}

Deprecated (setUploadToPlatformConfig()):

1setUploadToPlatformConfig({
2 enableUploadToPlatform: true,
3 setUploadToPlatformDefault: true
4});

If the setUploadToPlatformDefault is disabled, you can still upload selected reports to the platform. For that, use the following parameter in the evStop() command:

1await evincedService.evStop({ uploadToPlatform: true });

Or, in the evAnalyze() command:

1await evincedService.evAnalyze({ uploadToPlatform: true });

Test Names

To facilitate report management and be able to distinguish between different reports on the Platform, use the addLabel method to inform the test name and test class. It’s recommended to do that in the “beforeEach” hook.

1let evincedService;
2test.beforeEach(async ({page}, testInfo)=> {
3 evincedService = new EvincedSDK(page);
4 evincedService.testRunInfo.addLabel({
5 testName: testInfo.title,
6 })
7})

Labels and Custom Fields

You can attach labels and custom fields to your report to enhance readability and organization in the platform. Labels help you filter, search, and organize reports on the Evinced Platform.

There are two types of labels:

Built-in Labels: Pre-defined labels that can be set using the addLabel method. Available built-in labels include:

  • testName - The name of the test
  • testFile - The file path of the test
  • environment - The environment where the test runs (e.g., "Development", "Staging", "Production")
  • flow - The test flow identifier
  • gitBranch - The Git branch name
  • gitUserName - The Git user name
  • gitVersion - The Git commit version

Custom Labels: Flexible key-value pairs that can be set using the customLabel method. You can use any custom key-value pairs, including:

  • Single values: { productVersion: '1.0.0' }
  • Multiple values (arrays): { browsers: ['Chrome', 'Firefox'] }
  • Special label unitId: Use this to tag tests for relevant units within your organization

See the following code examples of how to set up labels:

1let evincedService;
2test.beforeEach(async ({page}, testInfo)=> {
3 evincedService = new EvincedSDK(page);
4
5 // Set built-in labels
6 evincedService.testRunInfo.addLabel({
7 testName: testInfo.title,
8 testFile: testInfo.file,
9 environment: 'Development',
10 gitBranch: 'main'
11 });
12
13 // Set custom labels (including unitId for organizational tagging)
14 evincedService.testRunInfo.customLabel({
15 'anyCustomParameter': 'demo value',
16 productVersion: '0.0.1',
17 browsers: ['Chrome 1.00', 'Firefox 2.00'],
18 unitId: 'unit-123' // Tag for organizational unit
19 });
20})

Use of beforeEach and afterEach Hooks

We recommend using the beforeEach and afterEach hooks to control analysis sessions and upload reports to the platform. This way, each test will be uploaded separately with its own report.

In beforeEach hook use evStart() to start Evinced analysis and set any labels you want for the report to contain when uploading to the platform. In the afterEach hook call evStop() to stop analysis and upload reports to the platform. See this code example:

1 let evincedService;
2 test.beforeEach(async ({page}, testInfo)=>{
3 const evincedService = new EvincedSDK(page);
4 evincedService.testRunInfo.addLabel({
5 testName: testInfo.title,
6 testFile: testInfo.file,
7 environment: 'Development'
8 })
9 await evincedService.evStart();
10 })
11 test.afterEach(async ()=>{
12 await evincedService.evStop();
13 })

Putting All of This Together

Here is a complete code snippet of how to perform uploads to the platform on a per-test basis.

1import { EvincedSDK, setUploadToPlatformConfig } from '@evinced/js-playwright-sdk';
2
3test.describe('Upload demo', () => {
4 let evincedService;
5 test.beforeAll(() => {
6 setUploadToPlatformConfig({ enableUploadToPlatform: true });
7 })
8 test.beforeEach(async ({page}, testInfo)=>{
9 evincedService = new EvincedSDK(page);
10 evincedService.testRunInfo.addLabel({
11 testName: testInfo.title,
12 testFile: testInfo.file,
13 environment: 'Development'
14 })
15 await evincedService.evStart();
16 })
17
18 test.afterEach(async ()=>{
19 await evincedService.evStop();
20 })
21
22 test('Upload example playwright #0', async ({ page }) => {
23 await page.goto("https://demo.evinced.com");
24 });
25})

Tutorials

You can find fully functional example projects on our GitHub.

Fail the test if critical issues are found

Here you can see a way of failing your test if critical accessibility issues are found using the Playwright JS SDK.

Using evAnalyze:

1const issues = await evincedService.evAnalyze();
2
3const criticalIssues = issues.filter((issue) => issue.severity.name === 'Critical');
4await assert(criticalIssues.length === 0, 'found critical issues');

Using evStart/evStop:

1await evincedService.evStart();
2const issues = await evincedService.evStop();
3
4const criticalIssues = issues.filter((issue) => issue.severity.name === 'Critical');
5await assert(criticalIssues.length === 0, 'found critical issues');

The criticalIssues array will contain all the critical issues found during the scan. If the array is not empty, the test will fail on the assertion.

Complete Test Suite Integration

You can use Playwright Fixtures to integrate Evinced Playwright JS SDK into your test suite with global hooks that add logic to the Playwright test object. These hooks ensure that the Evinced SDK is properly initialized and managed across your test suite, which will reduce your setup effort and improve consistency.

Create a new module that extends the Playwright test object with beforeEach() and afterEach() hooks. The following example shows the module in file baseTestExtension.js placed in the same directory as the test specs, but you may use any file name or location in the project.

1// baseTestExtension
2const { test: base } = require("@playwright/test");
3const { EvincedSDK } = require("@evinced/js-playwright-sdk");
4
5const test = base.extend({
6 hookSetup: [
7 async ({ page }, use, testInfo) => {
8 const evincedService = new EvincedSDK(page);
9 await beforeEach(page, evincedService);
10 await use(); // this will execute the test
11 await afterEach(page, evincedService, testInfo);
12 },
13 { auto: true }, // when true, the hook will be automatically executed and attached to all tests
14 ],
15});
16
17async function beforeEach(page, evincedService) {
18 console.log("Global before each hook");
19 await evincedService.evStart();
20}
21
22async function afterEach(page, evincedService, testInfo) {
23 console.log("Global after each hook");
24 const issues = await evincedService.evStop();
25 await evincedService.evSaveFile(
26 issues,
27 "json",
28 `report-${testInfo.title}.json`
29 );
30}
31module.exports = { test };

In your test spec file, use the test object exported from baseTestExtension.js instead of the default Playwright test object as shown in the following example.

Make sure the test accepts a parameter with the page object. It is required.

1// a test spec
2const { test } = require("./baseTestExtension");
3
4test.describe("Test suite description", () => {
5 test("Test name", async ({ page }) => {
6 await page.goto("https://your-web-site.com");
7 // test logic here....
8 });
9});

For additional information on Playwright Fixtures, please refer to the official Playwright documentation: playwright fixtures

Support

Please feel free to reach out to support@evinced.com with any questions.

FAQ

  1. Can I configure which validations to run?

Yes, see the Configuration section for details on how to configure Axe validations to your needs.

  1. Can I run tests with Evinced using cloud-based services like Sauce Labs, Perfecto, or BrowserStack?

Yes, we have tested the Evinced SDK on many of these types of cloud-based services and expect no issues.