Selenium Python SDK

The Evinced Selenium Python SDK integrates with new or existing Selenium WebDriver tests to automatically detect accessibility issues. By adding a few lines of code to your Selenium WebDriver 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

  • Python 3.10 or higher (tested on 3.10, 3.11, and 3.12) - check with python3 --version on macOS / Linux or py --version on Windows
  • Selenium 4.46 or higher
  • pytest 8 or higher for the examples in this guide (optional - the SDK works with any test runner)
  • A Chrome, Edge, or Firefox browser and matching WebDriver (Selenium Manager resolves drivers automatically)
  • Evinced credentials (service account ID and either a JWT or a secret)

Get started

Installation

To install Selenium Python 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.

Adding Evinced to an existing Python project

evinced-selenium-sdk is a standard Python package with ordinary dependencies, so it does not impose any project layout or tooling. Install it into your existing setup exactly the way you install any other dependency.

Starting a Python project from scratch

The standard way to work on a Python project is one virtual environment (venv) per project: a private Python environment inside the project folder that keeps its packages separate from other projects and from the system-wide Python. Create it once, then activate it in every new terminal you open for this project.

On macOS / Linux:

1mkdir my-a11y-tests
2cd my-a11y-tests
3python3 -m venv .venv
4source .venv/bin/activate

On Windows:

1mkdir my-a11y-tests
2cd my-a11y-tests
3py -m venv .venv
4.venv\Scripts\activate

While the environment is active, your prompt is prefixed with (.venv) and the plain python command refers to the project's own interpreter - from this point on, the commands in this documentation are identical on every operating system.

Running pip from your project folder does not install into that folder. Without an activated virtual environment, the package goes into your global Python installation. Activate your project's environment first (.venv in this guide), then check where pip will install to:

1python -m pip -V

The printed path should point inside your project's environment.

This documentation uses python -m pip rather than the bare pip command. On Windows in particular, pip may be missing from PATH or bound to a different Python installation than the one you intend to use - python -m pip always runs pip for exactly the interpreter you invoke it with, on every operating system. The python command itself is guaranteed inside an activated virtual environment; outside one, substitute python3 -m pip on macOS / Linux or py -m pip on Windows.

A typical pytest project for accessibility testing looks like this:

1my-a11y-tests/
2├── .venv/ # virtual environment - never commit; add to .gitignore
3├── requirements.txt # the project's dependencies
4├── conftest.py # shared pytest fixtures: credentials and driver setup
5└── tests/
6 └── test_home_page.py

Declare your dependencies in requirements.txt so teammates and CI can recreate the same environment:

1selenium>=4.46
2pytest>=8.0

and install them all at once with:

1python -m pip install -r requirements.txt

How the Evinced SDK itself is added depends on which installation method below applies to you: with remote repository access, add evinced-selenium-sdk==<version> to requirements.txt together with the --extra-index-url line shown in that section; with a locally provided file, either reference the wheel by path (./evinced_selenium_sdk-<version>-py3-none-any.whl on its own line) or install it as a separate step on each machine.

Installation with a locally provided file

Selenium Python SDK is distributed as a pre-built wheel - the accessibility engine and report generators are vendored inside it, so no Node.js is required. This is the supported way to install the SDK today.

Obtain the distribution file (a .whl wheel or a .tar.gz source archive) from Evinced Support and place it in your project folder. Then install it - if your project uses a virtual environment, activate it first.

On macOS / Linux / Windows:

1python -m pip install evinced_selenium_sdk-<version>-py3-none-any.whl

Verify the installation:

1python -m pip show evinced-selenium-sdk

Installation from a remote repository

Evinced Customers have the option of accessing Selenium Python 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, Selenium Python SDK is published to the Evinced JFrog Artifactory. The repository requires authentication - Evinced provides a JFrog username and access token, which you embed in the index URL. Activate your project's virtual environment if you use one, then install by pointing pip at the Evinced index:

1python -m pip install evinced-selenium-sdk --extra-index-url "https://<JFROG_USER>:<JFROG_TOKEN>@evinced.jfrog.io/artifactory/api/pypi/restricted-python/simple"

If your JFrog username is an email address, URL-encode the @ as %40 (for example jane.doe%40example.com).

Verify the installation:

1python -m pip show evinced-selenium-sdk

AI Skills

The evinced-selenium-sdk package ships built-in AI agent skills that guide your AI assistant through using the Evinced Selenium Python SDK - from initial setup and test writing to configuration, reporting, and CI integration. They work with any AI assistant that reads project context files (Claude Code, Cursor, Copilot, Windsurf, Gemini, and others).

Installing the skills

After installing the SDK, run the bundled installer from your project root:

1evinced-ai install

The command detects your assistant and writes the skills pointer into the matching context file - .cursor/rules/evinced-ai.mdc for Cursor, CLAUDE.md for Claude Code, or AGENTS.md otherwise. The pointer locates the skills inside the installed package rather than at a machine-specific path, so it is safe to commit and keeps working for every team member, on any operating system, after a plain package install.

Manual installation

To add the pointer yourself, copy the following block into your project's AGENTS.md or CLAUDE.md (for Cursor, put it in .cursor/rules/evinced-ai.mdc). Running evinced-ai snippet prints the same block:

1<!-- evinced-ai:begin (managed by `evinced-ai install`) -->
2
3## Evinced accessibility SDKAI skills
4
5This project uses the Evinced Selenium Python SDK (`evinced-selenium-sdk`), which ships AI skills for setting up and using accessibility testing. Before working on any web accessibility task:
6
71. **Make sure the SDK is installed** in this project's Python environment:
8
9 ```bash
10 python -c "import evinced_selenium_sdk"
11 ```
12
13 If that fails, install `evinced-selenium-sdk` **the way this project manages dependencies**, and add it as a project dependency — match the existing setup, don't switch package managers:
14 - **pip / venv** (`requirements.txt` or a `.venv/`): activate the venv, then `pip install evinced-selenium-sdk`, and add it to `requirements.txt`.
15 - **Poetry** (`pyproject.toml` with `[tool.poetry]`): `poetry add evinced-selenium-sdk`.
16 - **uv** (`uv.lock` / `[tool.uv]`): `uv add evinced-selenium-sdk`.
17 - **Pipenv** (`Pipfile`): `pipenv install evinced-selenium-sdk`.
18 - **conda**: `pip install evinced-selenium-sdk` inside the active conda env.
19
202. **Find the skills entry point** (run in the same environment):
21
22 ```bash
23 python -c "import evinced_selenium_sdk, pathlib; print(pathlib.Path(evinced_selenium_sdk.__file__).parent / 'evinced_ai' / 'entry.mdc')"
24 ```
25
263. **Read the file it prints, then follow it.** It routes you to the right skill (setup, writing tests, reporting, configuration) and to the SDK integration rule.
27
28The skills only add to the project and never overwrite existing code.
29
30<!-- evinced-ai:end -->

What the skills do

The entry point routes the assistant by intent:

  • "install" - installs the SDK and creates a standalone demo test (one-shot and continuous) that showcases it, without touching your existing tests.
  • "integrate" - a guided wizard that wires the SDK into your existing suite: it asks whether you authenticate online or offline, places the credential call and driver fixture in conftest.py, keeps your test_*.py files clean, and finishes with the exact credential steps and a copy-paste verification command.

The skills can also:

  • Write accessibility tests using ev_analyze, ev_start, and ev_stop
  • Configure accessibility rules, skip validations, and scope analysis to a DOM subtree
  • Generate HTML, JSON, SARIF, or CSV reports
  • Configure the SDK through EvincedConfig - screenshots, iframes, and network-idle waiting
  • Enable and tune SDK logging and log levels
  • Control SDK toggles and the kill switch
  • Integrate accessibility checks into CI/CD pipelines

Credentials and driver setup always go in conftest.py (pytest's fixture and configuration file), so your test files contain only tests.

Note. The skills entry point resolves the SDK in the active Python environment. If your assistant cannot find it, activate the project's virtual environment (or select the correct interpreter) and try again.

Example prompts:

  • "Install Evinced and show me a demo test"
  • "Integrate Evinced into my existing Selenium WebDriver tests"
  • "Fail my tests when critical accessibility issues are found"

Authentication

To launch Selenium Python 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.

On macOS / Linux:

1# Online mode
2export AUTH_SERVICE_ID=YOUR_SERVICE_ID
3export AUTH_SECRET=YOUR_API_SECRET
4
5# Offline mode - when a JWT has been provided by Evinced
6export AUTH_SERVICE_ID=YOUR_SERVICE_ID
7export AUTH_TOKEN=YOUR_JWT

On Windows:

1# Online mode
2$env:AUTH_SERVICE_ID = "YOUR_SERVICE_ID"
3$env:AUTH_SECRET = "YOUR_API_SECRET"
4
5# Offline mode - when a JWT has been provided by Evinced
6$env:AUTH_SERVICE_ID = "YOUR_SERVICE_ID"
7$env:AUTH_TOKEN = "YOUR_JWT"

Both forms last only for the current terminal session. To persist them, add the export lines to your shell profile (~/.zshrc or ~/.bashrc) on macOS / Linux, or the $env: lines to your PowerShell profile (open it with notepad $PROFILE) on Windows.

Setting credentials, an example:

1# conftest.py - project root
2import os
3
4import pytest
5
6from evinced_selenium_sdk import set_credentials
7
8
9@pytest.fixture(scope="session", autouse=True)
10def evinced_credentials():
11 # Online mode - exchange a service-account secret for a JWT
12 set_credentials(
13 service_id=os.environ["AUTH_SERVICE_ID"],
14 secret=os.environ["AUTH_SECRET"],
15 )

If Evinced has supplied you with a signed JWT (offline mode), use the same fixture with set_offline_credentials instead:

1# conftest.py - project root
2import os
3
4import pytest
5
6from evinced_selenium_sdk import set_offline_credentials
7
8
9@pytest.fixture(scope="session", autouse=True)
10def evinced_credentials():
11 # Offline mode - you already hold a signed JWT from Evinced
12 set_offline_credentials(
13 service_id=os.environ["AUTH_SERVICE_ID"],
14 token=os.environ["AUTH_TOKEN"],
15 )

Your First Test

SDK Initialization

To use Selenium Python SDK, you first need to authenticate. Please refer to Authentication for details.

The examples in this guide use pytest, but nothing in the SDK requires it - EvincedWebDriver wraps a plain Selenium WebDriver, so unittest, Robot Framework, Behave, or a bare script all work the same. Framework-neutral, the contract is: call set_credentials (or set_offline_credentials) once per process before creating the first EvincedWebDriver, and save any reports you need before calling quit(). Everything else is regular Selenium.

Using the SDK without pytest
1import os
2
3from selenium import webdriver
4
5from evinced_selenium_sdk import EvincedWebDriver, SaveFileFormat, set_credentials
6
7set_credentials(
8 service_id=os.environ["AUTH_SERVICE_ID"],
9 secret=os.environ["AUTH_SECRET"],
10)
11
12driver = EvincedWebDriver(webdriver.Chrome())
13try:
14 driver.get("https://demo.evinced.com")
15 report = driver.ev_analyze()
16 driver.ev_save_file(report, "report.html", SaveFileFormat.HTML)
17finally:
18 driver.quit()

Add the import at the top of your test module:

1from evinced_selenium_sdk import EvincedWebDriver
2from selenium import webdriver

The Evinced SDK wraps your Selenium driver and intercepts navigation and interactions for continuous analysis. Initialize it with a Chrome (or other) WebDriver instance:

1raw = webdriver.Chrome()
2driver = EvincedWebDriver(raw)

Chrome, Edge, and Firefox are all supported - pass webdriver.Edge() or webdriver.Firefox() the same way. Chromium-only capabilities are called out where they apply (for example ScreenshotMode.SDK).

Using Evinced with RemoteWebDriver or Selenium Grid

Use the same EvincedWebDriver constructor with any Selenium WebDriver, including webdriver.Remote (Selenium Grid, BrowserStack, Sauce Labs, and other remote endpoints). There is no separate remote wrapper class:

1from selenium import webdriver
2from evinced_selenium_sdk import EvincedWebDriver
3
4remote = webdriver.Remote(command_executor="http://localhost:4444", options=webdriver.ChromeOptions())
5driver = EvincedWebDriver(remote)

Add Evinced Accessibility Checks (Single Run Mode)

This is a simple pytest example of a single-page Evinced accessibility scan.

1# tests/test_home_page.py
2from selenium import webdriver
3
4from evinced_selenium_sdk import EvincedWebDriver
5
6
7def test_home_page_accessibility():
8 driver = EvincedWebDriver(webdriver.Chrome())
9 try:
10 driver.get("https://demo.evinced.com")
11 report = driver.ev_analyze()
12 # demo.evinced.com intentionally contains issues, so this assertion
13 # fails there - point the test at your own application
14 assert len(report.get_failed_validations()) == 0
15 finally:
16 driver.quit()

Add Evinced Accessibility Checks (Continuous Mode)

This is a simple pytest example of continuous accessibility scanning. Using ev_start() and ev_stop(), the Evinced engine scans in the background as your test runs, capturing DOM changes and navigations.

1# tests/test_continuous_scan.py
2import pytest
3from selenium import webdriver
4from selenium.webdriver.common.by import By
5
6from evinced_selenium_sdk import EvincedWebDriver, SaveFileFormat
7
8
9@pytest.fixture
10def driver():
11 d = EvincedWebDriver(webdriver.Chrome())
12 yield d
13 d.quit()
14
15
16def test_trip_planner_accessibility(driver):
17 driver.get("https://demo.evinced.com")
18 driver.ev_start()
19
20 # Interact with the page - each step below reveals new UI
21 # (dropdown options, calendar) that is scanned in the background
22 driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(1) > div > div.dropdown.line").click()
23 driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(2) > div > div.dropdown.line").click()
24 driver.find_element(By.CSS_SELECTOR, ".react-date-picker").click()
25
26 report = driver.ev_stop()
27
28 driver.ev_save_file(report, "test-results.html", SaveFileFormat.HTML)
29 driver.ev_save_file(report, "test-results.json", SaveFileFormat.JSON)
30 # the TRVL demo site intentionally contains issues, so this assertion
31 # fails there - point the test at your own application
32 assert len(report.get_failed_validations()) == 0
Using Evinced with Selenium BiDi

You can configure the Evinced SDK to use Selenium BiDi (Bidirectional Protocol). BiDi reduces communication overhead between the test runner and the browser, which can speed up continuous accessibility scanning.

By default, continuous mode uses interaction-based transport (clicks and navigation hooks). BiDi is opt-in: set continuous_transport="bidi" on a driver that advertises the webSocketUrl capability.

1from selenium import webdriver
2from selenium.webdriver.chrome.options import Options
3from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
4
5options = Options()
6options.set_capability("webSocketUrl", True)
7raw = webdriver.Chrome(options=options)
8config = EvincedConfig(continuous_transport="bidi")
9driver = EvincedWebDriver(raw, config)
10driver.ev_start()

API


EvincedWebDriver(driver, config)

Prepares the Evinced object for use in the project.

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(root_selector="#main", include_iframes=True)
5driver = EvincedWebDriver(webdriver.Chrome(), config)

Refer to Configuration to see examples of initializing with options.


ev_analyze(config, upload_to_platform=None)

Scans the current page and returns a Report with the accessibility issues found. This is the recommended method for static page analysis.

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

1from evinced_selenium_sdk import EvincedConfig, Report
2
3# Basic usage - uses the driver-level EvincedConfig (if any)
4report: Report = driver.ev_analyze()
5failed = report.get_failed_validations()
6
7# Optional per-call config override
8report = driver.ev_analyze(EvincedConfig(root_selector="#main"))

Both parameters are optional. The optional upload_to_platform parameter overrides platform upload for this call - see Uploading Reports to Evinced Platform.

Calling ev_analyze() during an active ev_start session raises SessionError (importable with from evinced_selenium_sdk import SessionError). Call ev_stop() first.

Returns Report.

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.


ev_start(config)

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

1# Uses the EvincedConfig passed to EvincedWebDriver(driver, config), if any
2driver.ev_start()
3
4# Optional per-call override for this session only
5driver.ev_start(EvincedConfig(include_iframes=False))

The optional config parameter accepts an EvincedConfig instance or a plain dict. It overrides the driver-level settings for this continuous session only.

Returns None.


ev_stop(upload_to_platform=None)

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

1from evinced_selenium_sdk import Report
2
3driver.ev_start()
4report: Report = driver.ev_stop()
5failed = report.get_failed_validations()

The optional upload_to_platform parameter overrides platform upload for this call - see Uploading Reports to Evinced Platform.

When include_passed_validations=True was enabled for the session, report.get_passed_validations() is also populated.

Returns Report.

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

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


ev_save_file(report, destination, file_format)

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.

ev_save_file has two forms - the first argument decides which one you get:

GoalCall
Save one specific reportev_save_file(report, path, format)
Save the aggregated run (every ev_analyze + ev_stop on this driver)ev_save_file(path, format)

Save a single report

Pass the Report from ev_analyze or ev_stop:

1from evinced_selenium_sdk import Report, SaveFileFormat
2
3report: Report = driver.ev_stop()
4# or: report = driver.ev_analyze()
5
6driver.ev_save_file(report, "jsonReport.json", SaveFileFormat.JSON)
7driver.ev_save_file(report, "htmlReport.html", SaveFileFormat.HTML)
8driver.ev_save_file(report, "sarifReport.sarif.json", SaveFileFormat.SARIF)
9driver.ev_save_file(report, "csvReport.csv", SaveFileFormat.CSV)

String format names ("json", "html", "sarif", "csv") are also accepted. A plain list[dict] of failed issues is still accepted for backward compatibility.

Pass a scale keyword argument (float) to adjust HTML report scaling.

When include_passed_validations=True, pass the full Report to include passed validations in JSON output.

Save an aggregated run report

After multiple ev_analyze and ev_stop calls on the same driver, save one combined deduplicated report without passing a Report object:

1driver.ev_save_file("combined.html", SaveFileFormat.HTML)
2driver.ev_save_file("combined.json", SaveFileFormat.JSON)

See Aggregated Report for a full example.

SaveFileFormat

Defines the report file type. Options are JSON, HTML, SARIF, and CSV (via the SaveFileFormat enum or equivalent strings).

Returns None.

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 ev_start() and ev_stop() commands were called. It is still possible to use the ev_save_file() command in any place of your code along with this Aggregated Report feature.

Each EvincedWebDriver keeps its own aggregation store. Every ev_analyze and ev_stop call on that driver contributes deduplicated issues you can save as one combined report.

1from evinced_selenium_sdk import SaveFileFormat
2
3driver.get("https://demo.evinced.com/page1")
4driver.ev_analyze()
5
6driver.get("https://demo.evinced.com/page2")
7driver.ev_start()
8driver.find_element("id", "next").click()
9driver.ev_stop()
10
11driver.ev_save_file("evinced-combined.html", SaveFileFormat.HTML)
12driver.ev_save_file("evinced-combined.json", SaveFileFormat.JSON)

Save the aggregated report before calling quit() or close() - quitting the driver clears the aggregation store, and issues collected during the run are lost.

To save a single analyze/stop result instead, pass the Report as the first argument:

1report = driver.ev_analyze()
2driver.ev_save_file(report, "page-only.json", SaveFileFormat.JSON)

Merge arbitrary issue lists (for example from separate drivers) with ev_merge_issues:

1merged = driver.ev_merge_issues(report_page1, report_page2)
2driver.ev_save_file(merged, "merged.json", SaveFileFormat.JSON)

Configuration

The same configuration object can be used when initializing the Evinced object using EvincedConfig() and when calling the ev_start() and ev_analyze() methods but with a bit different consequences. Providing options when initializing defines a global configuration for all calls of ev_analyze() and ev_start(), while providing options to either of those methods affect only the test in which they are called.

Options provided in either ev_analyze() or ev_start() override those set in Evinced engine initialization.

Engines Configuration

Evinced uses two separate engines when scanning for accessibility issues: the Axe (axe-core) engine and the proprietary Evinced engine. By default, Evinced disables Axe Needs Review and Best Practices rules because they are mostly false positives. Keep this in mind when comparing issue counts with other tools. See Toggles to enable them.

Configuration Object

Evinced configuration is passed as an EvincedConfig dataclass (or a plain dict with the same keys). You can set it in two places:

  1. Per driver - pass config when constructing EvincedWebDriver. These values apply to every ev_analyze and ev_start on that driver unless overridden.
  2. Per call - pass config to a single ev_analyze(config) or ev_start(config) to override the driver-level settings for that command only. Precedence: SDK defaults → per-driver config → per-call config.

Both snake_case (include_iframes) and camelCase (includeIframes) keys are accepted in dicts. When both forms of the same option are set, camelCase wins. Note that keys inside nested options (for example skip_validations entries such as urlRegex and validationTypes) are passed to the engine as-is and are always camelCase, as are the keyword arguments of set_upload_to_platform_config.

Per-driver configuration

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 include_iframes=False,
6 root_selector="#app",
7 screenshot_mode="sdk",
8)
9driver = EvincedWebDriver(webdriver.Chrome(), config)

Per-call override

1from evinced_selenium_sdk import EvincedConfig
2
3# Include passed validations for one analyze call
4report = driver.ev_analyze(EvincedConfig(include_passed_validations=True))
5
6# Override root selector for one continuous session
7driver.ev_start(EvincedConfig(root_selector="#content"))

The sections below cover each option in detail.

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

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(root_selector=".some-selector")
5driver = EvincedWebDriver(webdriver.Chrome(), config)

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.

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 axe_config={"rules": {"html-has-lang": {"enabled": False}}},
6)
7driver = EvincedWebDriver(webdriver.Chrome(), config)

Engine Logging

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

Default: "error"

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(logging={"loggingLevel": "debug"})
5driver = EvincedWebDriver(webdriver.Chrome(), config)

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

Screenshots are disabled by default because capturing and embedding element images increases test runtime and report size. Enable them when visual context in HTML or SARIF reports is worth the overhead - for example when triaging issues offline or sharing reports with designers.

The SDK supports three screenshot modes via the screenshot_mode option (the ScreenshotMode enum, or the equivalent string):

ModeDescription
ScreenshotMode.PAGEIn-page capture via html2canvas (default when screenshots are enabled)
ScreenshotMode.SDKDriver-side capture via Chrome DevTools (Page.captureScreenshot) - pixel-true, Chromium-only
ScreenshotMode.DISABLEDScreenshots off (default)

ScreenshotMode.SDK is recommended when you need pixel-accurate images: it uses the real browser compositor, avoiding the html2canvas artifacts that can appear on overlays or fixed elements. It is Chromium-only - on other browsers the SDK logs a warning and falls back to PAGE (html2canvas).

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver, ScreenshotMode
2from selenium import webdriver
3
4# Recommended: driver-side (CDP) capture, pixel-true
5config = EvincedConfig(screenshot_mode=ScreenshotMode.SDK) # or screenshot_mode="sdk"
6driver = EvincedWebDriver(webdriver.Chrome(), config)
7
8# Per-call - screenshots for one analyze only
9report = driver.ev_analyze(EvincedConfig(screenshot_mode="page"))

Tune SDK-side capture with screenshot_config (all optional): quality (JPEG 1–100, default 70), timeout (per-capture bound in ms, default 5000), scale (capture scale, default 1.0).

1config = EvincedConfig(
2 screenshot_mode="sdk",
3 screenshot_config={"quality": 70, "timeout": 5000, "scale": 1.0},
4)

Backward compatibility: the flag options still work - enable_screenshots=True is equivalent to ScreenshotMode.PAGE, and adding sdk_side_screenshots=True is equivalent to ScreenshotMode.SDK.

1# Legacy flags - still supported
2EvincedConfig(enable_screenshots=True) # == ScreenshotMode.PAGE
3EvincedConfig(enable_screenshots=True, sdk_side_screenshots=True) # == ScreenshotMode.SDK

Toggles

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

Example:

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 toggles={
6 "USE_AXE_NEEDS_REVIEW": True,
7 "USE_AXE_BEST_PRACTICES": True,
8 },
9)
10driver = EvincedWebDriver(webdriver.Chrome(), config)

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.

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 skip_validations=[
6 {
7 "selector": "test.1--selector",
8 "urlRegex": "http://url.to.skip/path1",
9 "validationTypes": ["NO_DESCRIPTIVE_TEXT", "NOT_FOCUSABLE"],
10 },
11 {
12 "selector": "test.2--selector",
13 "urlRegex": "http://url.to.skip/path2",
14 "validationTypes": ["NOT_FOCUSABLE", "ONE_MORE_TYPE_TO_EXCLUDE", "NO_DESCRIPTIVE_TEXT"],
15 },
16 ],
17)
18driver = EvincedWebDriver(webdriver.Chrome(), config)

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.

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: The config keys map to the modes above: DELAY_MODE selects "debounce" or "throttle", and ENABLE_DEBOUNCE_NEW_SELECTORS / DEBOUNCE_NEW_SELECTORS_MS debounce the analysis of newly appearing elements:

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 recording_service={
6 "ENABLE_DEBOUNCE_NEW_SELECTORS": True,
7 "DEBOUNCE_NEW_SELECTORS_MS": 1000,
8 "DELAY_MODE": "throttle",
9 },
10)
11driver = EvincedWebDriver(webdriver.Chrome(), config)

These settings apply only during continuous mode (ev_start / ev_stop); they have no effect on ev_analyze. They tune scanning driven by the page's own DOM changes; scans triggered by your test's interactions are tuned in Settled Analysis.

Settled Analysis

Settled analysis is built into continuous mode: after each interaction (a click, a key press), the engine waits for the page to settle and analyzes the newly revealed content - dropdowns, modals, dynamically loaded sections. It is always on during ev_start / ev_stop sessions and needs no configuration; rapid interactions are coalesced automatically (at most one settled scan per 300 ms).

Two options bound how long each scan waits for the page to settle. The wait holds the driver's command lock, so it can delay your test's next command by up to the timeout - lower it if your app is sensitive to that (0 disables the wait):

OptionTypeDefaultDescription
settle_wait_timeout_msint1500Maximum time to wait for the page to settle before analyzing
settle_wait_poll_msint50How often the page state is re-checked while waiting
1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(settle_wait_timeout_ms=500)
5driver = EvincedWebDriver(webdriver.Chrome(), config)

Settled analysis reacts to your test's interactions; scanning driven by the page's own DOM changes is tuned separately - see Recording Service.

Single-page applications. SPA flows are covered by the same mechanism: route changes triggered by your test's interactions are analyzed like any other interaction, with no extra configuration. Content that appears without any interaction (for example a timer-driven banner) is not captured automatically - call ev_analyze() once that state is on screen, or interact with the page to trigger a scan.

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.

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(include_iframes=False)
5driver = EvincedWebDriver(webdriver.Chrome(), config)

Visible iframes are analyzed when include_iframes=True (default). Hidden iframes are skipped unless their domain is listed in include_hidden_iframe_domains - see Include Hidden IFrames Domain below.

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.

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 include_iframes=True,
6 include_hidden_iframe_domains=["example.com", "auth.example.com"],
7)
8driver = EvincedWebDriver(webdriver.Chrome(), config)

Hidden iframes (for example SSO checksession frames or zero-size tracking widgets) are skipped by default. List a domain here to include its hidden iframes in analysis.

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 include_passed_validations to True.

Note. Enabling passed validations noticeably increases analysis and reporting overhead - the report carries every check that ran, not only the failures.

Single Page Analysis with Passed Validations

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver, Report, SaveFileFormat
2from selenium import webdriver
3
4config = EvincedConfig(include_passed_validations=True)
5driver = EvincedWebDriver(webdriver.Chrome(), config)
6driver.get("https://demo.evinced.com")
7
8report: Report = driver.ev_analyze()
9failed = report.get_failed_validations()
10passed = report.get_passed_validations()
11
12driver.ev_save_file(report, "complete-report.json", SaveFileFormat.JSON)

Continuous Analysis with Passed Validations

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver, Report
2from selenium import webdriver
3
4config = EvincedConfig(include_passed_validations=True)
5driver = EvincedWebDriver(webdriver.Chrome(), config)
6
7driver.ev_start(config)
8driver.get("https://demo.evinced.com")
9driver.find_element("css selector", ".dropdown").click()
10
11report: Report = driver.ev_stop()
12failed = report.get_failed_validations()
13passed = report.get_passed_validations()

The option can also be set per call, like any other configuration option:

1report = driver.ev_analyze(EvincedConfig(include_passed_validations=True))

Proxy

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

Routing browser traffic - configure Selenium's own Proxy object. This affects the pages the browser loads, not the SDK:

1from selenium import webdriver
2from selenium.webdriver.common.proxy import Proxy
3from selenium.webdriver.chrome.options import Options
4from evinced_selenium_sdk import EvincedWebDriver
5
6proxy_string = "proxy.example.com:8080"
7# Or with credentials: proxy_string = "user:password@proxy.example.com:8080"
8
9proxy = Proxy()
10proxy.http_proxy = proxy_string
11proxy.ssl_proxy = proxy_string
12
13options = Options()
14options.proxy = proxy
15driver = EvincedWebDriver(webdriver.Chrome(options=options))

Routing the SDK's own requests (authentication and platform upload) - a separate mechanism: the SDK honors the standard proxy environment variables HTTP_PROXY, HTTPS_PROXY, NO_PROXY, and optional PROXY_USERNAME / PROXY_PASSWORD. NO_PROXY supports wildcard and suffix patterns (e.g. *.internal, .example.com, <local>), and socks5:// / socks4:// proxy URLs are supported.

Network Idle Detection

Network-idle gating waits for network activity to settle before analysis, so the scan runs on a stable page with dynamic content loaded. It is opt-in and requires the WebDriver BiDi transport (continuous_transport="bidi" on a driver that advertises webSocketUrl); on the interaction (click) transport it is a no-op.

Configure it with the nested network_idle option:

KeyTypeDefaultDescription
enabledboolFalseEnable network-idle gating
timeoutint (ms)300Quiet window - idle means 0 in-flight requests for this long
maxWaitint (ms)7500Hard cap; proceed anyway after this even if the network never quiets
1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(
5 continuous_transport="bidi",
6 network_idle={"enabled": True, "timeout": 300, "maxWait": 7500},
7)
8driver = EvincedWebDriver(webdriver.Chrome(), config)
9
10# Per-call override for one analysis (e.g. a slower API)
11report = driver.ev_analyze(
12 EvincedConfig(network_idle={"enabled": True, "maxWait": 10000})
13)

The wait applies before one-shot ev_analyze and before each continuous-mode scan. A page that never quiets (persistent polling, WebSocket, SSE) proceeds after maxWait with a logged warning - it never hangs.

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:

  • ev_start() and ev_save_file() will be bypassed.
  • ev_stop() and ev_analyze() will return an empty report.

Switching Evinced Functionality Off in Configuration

Disable all Evinced analysis at runtime - useful during local development when you want to run functional tests without accessibility overhead, or in CI jobs that skip a11y checks.

1from evinced_selenium_sdk import evinced_disable, evinced_enable
2
3evinced_disable()
4
5# ev_start is skipped; ev_analyze returns an empty Report
6report = driver.ev_analyze()
7assert report.get_failed_validations() == []
8
9# Re-enable for the next test
10evinced_enable()
11driver.ev_analyze() # runs normally again

If you call evinced_disable() after ev_start(), you must still call ev_stop() to finalize the session - the switch gates new work, not teardown of an active session.

Switching Evinced Functionality Off in Environment

1# Canonical cross-SDK variable
2export EV_SWITCH_ON=false
3
4# Python-only alias - takes precedence over EV_SWITCH_ON
5export EVINCED_DISABLED=true

Note the opposite polarity: disabling is EV_SWITCH_ON=false but EVINCED_DISABLED=true. Setting the wrong value to the wrong variable (for example EVINCED_DISABLED=false intending to disable) silently does nothing - no error or warning is logged.

The variables are read once, the first time the SDK checks the switch, and cached for the rest of the process - set them before your first use of the SDK. Programmatic evinced_disable() / evinced_enable() calls take precedence from the moment they run, including over the environment.

Alerts

When true, Evinced expects alerts to be displayed at any time, after any action, and will wait for them to be closed. When false, Evinced will not wait for alerts to be closed and will continue the analysis.

Default: false.

With the default alert_expected=False, the SDK's background analysis that runs after each interaction can dismiss an alert before your test handles it - the alert simply disappears. Set alert_expected=True when your flow triggers alerts: the SDK then leaves them alone (analysis waits until the alert is closed) and your test handles them as usual with driver.switch_to.alert.

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3
4config = EvincedConfig(alert_expected=True)
5driver = EvincedWebDriver(webdriver.Chrome(), config)
6driver.ev_start()

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 0.1.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

Check which SDK version you have installed with python -m pip show evinced-selenium-sdk.

Call set_upload_to_platform_config before creating your EvincedWebDriver, typically in a session-scoped pytest fixture or conftest.py:

1from evinced_selenium_sdk import set_upload_to_platform_config
2
3set_upload_to_platform_config(enableUploadToPlatform=True)

Without this master switch, no upload happens regardless of per-call flags.

Upload is skipped when there are no failed validations, even if passed validations were collected.

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 ev_stop() or ev_analyze() command.

How it works:

  • When enableUploadToPlatform: true and setUploadToPlatformDefault: true (default), upload happens automatically
  • No additional code is needed - just call ev_analyze() or ev_stop() 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.

1from evinced_selenium_sdk import set_upload_to_platform_config
2
3set_upload_to_platform_config(setUploadToPlatformDefault=False)

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

1driver.ev_stop(upload_to_platform=True)

Or, in the ev_analyze() command:

1driver.ev_analyze(upload_to_platform=True)

upload_to_platform has three states: None (the default) follows the global upload configuration described above, while True / False force uploading (or not uploading) that report regardless of it.

Test Names

To facilitate report management and be able to distinguish between different reports on the Platform, use the set_test_info method to inform the test name and test class. It’s recommended to do that in a per-test pytest fixture.

1driver.set_test_info("test_method_name", "tests/test_module.py")

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 add_label 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 custom_label 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:

1driver.test_run_info.add_label(environment="production", flow="standard")
2driver.test_run_info.custom_label(
3 Product_version="1.2.3",
4 OS_Type="Linux",
5 suite=["smoke", "regression"], # a list adds one label per value
6)

To set labels once for every driver created afterwards (e.g. in a session fixture), use the process-global run info:

1from evinced_selenium_sdk import get_test_run_info
2
3get_test_run_info().custom_label(team="a11y").add_label(environment="staging")

The built-in gitUserName / gitBranch / gitVersion labels are not collected automatically - set them explicitly when you want them, for example from CI environment variables:

1import os
2
3driver.test_run_info.add_label(
4 gitBranch=os.getenv("GIT_BRANCH", ""),
5 gitVersion=os.getenv("GIT_COMMIT", ""),
6)

Per-test Setup and Teardown

Run each test in its own analysis session, so every test uploads its own report: start the session and set labels before the test, stop the session (which uploads) after it. With pytest, that is an autouse fixture:

1@pytest.fixture(autouse=True)
2def evinced_continuous(driver, request):
3 driver.set_test_info(request.node.name, str(request.fspath))
4 driver.ev_start()
5 yield
6 driver.ev_stop()

Putting All of This Together

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

pytest conftest.py example with platform upload:

1import os
2import pytest
3from selenium import webdriver
4from evinced_selenium_sdk import EvincedWebDriver, set_offline_credentials, set_upload_to_platform_config
5
6
7@pytest.fixture(scope="session", autouse=True)
8def evinced_credentials():
9 set_offline_credentials(
10 service_id=os.environ["AUTH_SERVICE_ID"],
11 token=os.environ["AUTH_TOKEN"],
12 )
13 set_upload_to_platform_config(enableUploadToPlatform=True)
14
15
16@pytest.fixture(scope="module")
17def driver():
18 d = EvincedWebDriver(webdriver.Chrome())
19 d.test_run_info.add_label(gitUserName="git", gitBranch="main")
20 d.test_run_info.custom_label(Testing_purpose="Test platform uploading feature")
21 yield d
22 d.quit()
23
24
25@pytest.fixture(autouse=True)
26def evinced_session(driver, request):
27 driver.set_test_info(request.node.name, str(request.fspath))
28 driver.ev_start()
29 yield
30 driver.ev_stop()
31
32
33def test_demo_home(driver):
34 driver.get("https://demo.evinced.com")

Accessing Upload Information

After a successful upload, retrieve the test ID and platform URL from the driver's run metadata:

1test_id = driver.test_run_info.get_test_id()
2platform_url = driver.test_run_info.get_upload_test_url()

Complete example:

1report = driver.ev_analyze(upload_to_platform=True)
2if report.get_failed_validations():
3 print(f"Upload test ID: {driver.test_run_info.get_test_id()}")
4 print(f"Platform URL: {driver.test_run_info.get_upload_test_url()}")

Upload runs only when platform upload is enabled, the page is not blank, and there is at least one failed validation.

Tutorials

You can find fully functional example projects on our GitHub.

Generating a comprehensive accessibility report for your application

In this tutorial, we enhance an existing Selenium UI test with the Evinced Selenium Python SDK to check an application for accessibility issues. Prerequisites:

  1. All prerequisites for the Evinced Selenium Python SDK are met
  2. evinced-selenium-sdk is installed in your project
  3. Credentials are configured once in conftest.py, as shown in Authentication - none of the test code below mentions them

Preface - existing UI test overview

Starting point - a functional UI test:

1from selenium import webdriver
2from selenium.webdriver.common.by import By
3
4
5def test_trvl_filters():
6 driver = webdriver.Chrome()
7 try:
8 driver.get("https://demo.evinced.com/")
9 driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(1) > div > div.dropdown.line").click()
10 driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(2) > div > div.dropdown.line").click()
11 driver.find_element(By.CSS_SELECTOR, ".react-date-picker").click()
12 finally:
13 driver.quit()

We use the demo travel site TRVL with known accessibility issues.

Step 1 - Initialize EvincedWebDriver

1import pytest
2from selenium import webdriver
3from selenium.webdriver.common.by import By
4from evinced_selenium_sdk import EvincedWebDriver, SaveFileFormat
5
6
7@pytest.fixture
8def driver():
9 d = EvincedWebDriver(webdriver.Chrome())
10 yield d
11 d.quit()
12
13
14def test_trvl_filters(driver):
15 driver.get("https://demo.evinced.com/")
16 driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(1) > div > div.dropdown.line").click()
17 driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(2) > div > div.dropdown.line").click()
18 driver.find_element(By.CSS_SELECTOR, ".react-date-picker").click()

Step 2 - Start the Evinced engine

1@pytest.fixture
2def driver():
3 d = EvincedWebDriver(webdriver.Chrome())
4 d.ev_start()
5 yield d
6 d.quit()

Step 3 - Stop the engine and save reports

1@pytest.fixture
2def driver(request):
3 d = EvincedWebDriver(webdriver.Chrome())
4 d.ev_start()
5 yield d
6 report = d.ev_stop()
7 d.ev_save_file(report, f"{request.node.name}.html", SaveFileFormat.HTML)
8 d.ev_save_file(report, f"{request.node.name}.json", SaveFileFormat.JSON)
9 assert len(report.get_failed_validations()) == 0 # optional gating assertion
10 d.quit()

Run the test with pytest. Additional configuration options are documented in the API section.

Additional Configuration Examples

Testing accessibility in a specific state of the application

Open UI state first, then analyze:

1from selenium import webdriver
2from selenium.webdriver.common.by import By
3from evinced_selenium_sdk import EvincedWebDriver, SaveFileFormat
4
5driver = EvincedWebDriver(webdriver.Chrome())
6driver.get("https://demo.evinced.com/")
7driver.find_element(By.CSS_SELECTOR, "div.filter-container > div:nth-child(1) > div > div.dropdown.line").click()
8report = driver.ev_analyze()
9assert len(report.get_failed_validations()) >= 1
10driver.ev_save_file(report, "test-results.html", SaveFileFormat.HTML)

Running ev_analyze on multiple tabs

ev_analyze runs on the current window. Switch handles before analyzing a new tab:

1from selenium.webdriver.common.by import By
2
3driver.get("page1.html")
4report = driver.ev_analyze()
5assert len(report.get_failed_validations()) >= 1
6
7driver.find_element(By.ID, "open-tab-link").click()
8handles = driver.window_handles
9driver.switch_to.window(handles[1])
10
11report = driver.ev_analyze()
12assert len(report.get_failed_validations()) >= 1

Running ev_start and ev_stop on multiple tabs

In continuous mode, driver.switch_to.window(...) or driver.switch_to.new_window(...) flushes the current slice and restarts analysis on the destination window. You do not need to call ev_stop before switching - a single ev_stop() at the end can include issues from every window visited during the session:

1from selenium.webdriver.common.by import By
2
3driver.get("page1.html")
4driver.ev_start()
5# interact on page1...
6driver.find_element(By.ID, "open-tab-link").click()
7driver.switch_to.window(driver.window_handles[1])
8# interact on the popup tab...
9driver.switch_to.window(driver.window_handles[0]) # optional: return to the original window
10report = driver.ev_stop()

Closing a popup with driver.close() keeps the session alive; only closing the last remaining window tears it down. Issues not yet flushed on the closed window are dropped (the SDK logs a warning) - switch away from the popup or interact on it before closing if you need its final state analyzed.

Iframes and continuous mode

Enable iframe analysis with include_iframes=True (default). The engine always runs in the main frame. if your test is inside an iframe when a slice fires, the SDK restores your frame context afterward - you typically do not need to match iframe context before ev_stop:

1from evinced_selenium_sdk import EvincedConfig, EvincedWebDriver
2from selenium import webdriver
3from selenium.webdriver.common.by import By
4
5config = EvincedConfig(include_iframes=True)
6driver = EvincedWebDriver(webdriver.Chrome(), config)
7driver.get("page1.html")
8driver.switch_to.frame("iframe1")
9driver.ev_start()
10# interact inside iframe...
11report = driver.ev_stop()

Fail the test if critical issues are found

Using ev_analyze

1report = driver.ev_analyze()
2critical = [
3 i for i in report.get_failed_validations()
4 if (i.get("severity") or {}).get("id") == "CRITICAL"
5]
6assert not critical, "Critical accessibility issues found"

Using ev_start / ev_stop

1driver.ev_start()
2# ... test steps ...
3report = driver.ev_stop()
4critical = [
5 i for i in report.get_failed_validations()
6 if (i.get("severity") or {}).get("id") == "CRITICAL"
7]
8assert not critical, "Critical accessibility issues found"

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.