accessiBe Help Center

accessFlow SDK - Python

  • Updated

Use the accessFlow SDK with Playwright or Selenium for Python applications to run automated accessibility audits as part of your test suite. This helps you catch accessibility issues early, and track results in the accessFlow Audits page.

How it works

Local development

  • Audits run on the selected pages
  • JSON reports saved to ./test-results directory
  • Reports are NOT uploaded to the accessFlow Audits page (prevents accidental uploads)

CI/CD environment

  • Audits run on the selected pages
  • Results aggregated from parallel test workers
  • Automatically uploaded to the accessFlow Audits page
  • Threshold checks applied (fail build if exceeded)

Prerequisites

You'll need:

  • Registry install token: Used to install the SDK package (for remote installation - provided by accessiBe)
  • Python registry URL: The private package registry location  (for remote installation - provided by accessiBe)
  • SDK API key: Used to authenticate audit requests at runtime (see Generate a token)

Important: The Registry install token is used to download/install the SDK package. The SDK API key is used at runtime when running accessibility audits. These are two separate credentials.

Requirements

  • Python 3.7+
  • Playwright for Python
  • Playwright browsers must be installed 
npx playwright install

Install the SDK

Install from a private registry (recommended)

Direct installation

pip install accessflow-sdk 
  --index-url https://_json_key_base64:REGISTRY_INSTALL_TOKEN@PYTHON_REGISTRY_URL/simple/

Replace:

  • REGISTRY_INSTALL_TOKEN
  • PYTHON_REGISTRY_URL

with the credentials provided by accessiBe.

CI/CD installation

Store the registry token as a secret (e.g., ACCESSFLOW_REGISTRY_TOKEN):

pip install accessflow-sdk \\
  --index-url https://_json_key_base64:${ACCESSFLOW_REGISTRY_TOKEN}@PYTHON_REGISTRY_URL/simple/

Install from a local package file

# Install from wheel file (recommended)
pip install /path/to/accessflow_sdk-1.1.0-py3-none-any.whl

# Or install from tar.gz source distribution
pip install /path/to/accessflow_sdk-1.1.0.tar.gz

Configure the SDK API key

First generate a token in accessFlow and then use it to set the API key.

Generate a token

  1. Go to your Profile menu in top right corner and select Token management
  2. Select Generate token.
  3. Enter a token name.
  4. Select Audit SDK token and then Generate token.
  5. Copy the token and store it securely. You won't be able to see it again.

Provide the API key

Recommended: Use an environment variable

Provide your accessFlow API key using an environment variable (recommended).

The SDK automatically reads this value - no additional configuration required.

# .env file or shell
export ACCESSFLOW_SDK_API_KEY=your-api-key-here

 

from accessflow_sdk import AccessFlowSDK

# No api_key parameter needed - reads from environment automatically
sdk = AccessFlowSDK(page)

Optional: Pass the API key explicitly

When you need to set the API key programmatically:

sdk = AccessFlowSDK(page, api_key="your-api-key")

Use in your test suite

Here are examples of how to use the SDK in your test suite.

Playwright

Install Playwright and the Playwright browser.

pip install playwright
playwright install chromium

Example of how to use the SDK in Playwright:

from playwright.sync_api import sync_playwright
from accessflow_sdk import AccessFlowSDK

def test_homepage_accessibility():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()

        sdk = AccessFlowSDK(page)
        page.goto("https://example.com")

        audits = sdk.audit()
        report = sdk.generate_report(audits)

        print(f"Issues found: {report['numberOfIssuesFound']}")
        assert report['numberOfIssuesFound'].get('extreme', 0) == 0

        browser.close()

Selenium

Install Selenium and ensure Chrome/Chromedriver are available.

pip install "selenium>=4.35"

Example of how to use the SDK in Selenium:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from accessflow_sdk import AccessFlowSDK, SeleniumDriver

def test_homepage_accessibility_selenium():
    options = Options()
    options.add_argument('--headless')
    driver = webdriver.Chrome(options=options)

    try:
        driver.get("https://example.com")

        sdk = AccessFlowSDK(SeleniumDriver(driver))
        audits = sdk.audit()
        report = sdk.generate_report(audits)

        print(f"Issues found: {report['numberOfIssuesFound']}")
        assert report['numberOfIssuesFound'].get('extreme', 0) == 0
    finally:
        driver.quit()

Pytest integration (recommended)

Create conftest.py

import pytest
from accessflow_sdk import AccessFlowSDK, finalize_reports

@pytest.fixture
def sdk(page):
    """Provides AccessFlow SDK instance"""
    return AccessFlowSDK(page)

def pytest_sessionfinish(session, exitstatus):
    """
    Global teardown: finalize all accessibility reports.
    This runs once after all tests have completed.
    Aggregates and uploads reports in CI environments (when ACCESSFLOW_SDK_API_KEY is set).
    """
    finalize_reports()

Test example

How it works

  1. Each call to sdk.audit() automatically records results for aggregation.
  2. After all tests are complete, pytest_sessionfinish() runs once to:
    • Aggregate all recorded audits into a single report.
    • Upload the report to accessFlow (in CI environments with ACCESSFLOW_SDK_API_KEY set).
    • Validate against configured thresholds (if any).

No manual recording needed - just call audit() in your tests and configure the session finish hook.

Then use this in your tests:

def test_homepage(page, sdk):
    page.goto("https://example.com")

    audits = sdk.audit()
    report = sdk.generate_report(audits)

    # Assert no critical issues
    assert report['numberOfIssuesFound'].get('extreme', 0) == 0
    assert report['numberOfIssuesFound'].get('high', 0) <= 5

Configure the SDK (optional)

Create an accessflow.config.json file in your project root. Use it to define thresholds for accessibility issues that will fail your test suite if exceeded.

If no config file is provided, thresholds are not enforced (tests won’t fail because of counts). Reports are still generated.

Example

Final report and localCheck

Final report

After all tests are complete, the SDK's listener (JUnit 5 / TestNG) or your manual AccessFlowTeardown.finalizeReports() call aggregates all recorded audits into one report, uploads it to the accessFlow Audits pagein CI environments, and validates it against issuesFoundThreshold. 

The report JSON includes pages (keyed by URL: numberOfIssuesFound, ruleViolations with description, severity, selectors, selectorData with HTML/suggestionLabel/suggestionType, WCAG level/link) and totalNumberOfIssuesFound.

Example shape (abbreviated):{
  "issuesFoundThreshold": {
    "extreme": 0,
    "high": 5,
    "medium": 10,
    "low": 20
  },
  "localCheck": false
}

Options

Parameter Type Description
issuesFoundThreshold object

(Optional) Defines the maximum allowed number of accessibility issues per severity. Non-numeric values are ignored.

  • extreme: (number) Max extreme issues allowed.
  • high: (number) Max high issues allowed.
  • medium: (number) Max medium issues allowed.
  • low: (number) Max low issues allowed.
localCheck boolean

(Optional) Whether threshold checking is enforced during test execution. Default is false.

  • true: Violations will fail the tests with a detailed error message. Default. Provides immediate feedback during development and CI/CD pipelines. 
  • false: Violations are recorded in the report, but don't fail tests. Useful for monitoring and gradual improvement without breaking builds.

Final report and localCheck

Final report

After all tests are complete, finalize_reports() (e.g. in pytest_sessionfinish) aggregates all recorded audits into one report, uploads it to the accessFlow Audits page in CI environments, and validates it against issuesFoundThreshold

The report is a JSON object with pages (per-URL breakdown: numberOfIssuesFoundruleViolations with description, severity, selectors, selectorData with HTML/suggestionLabel/suggestionType, WCAG level/link) and totalNumberOfIssuesFound (aggregated severity counts).

Example shape (abbreviated):

{
  "pages": {
    "https://example.com/1": {
      "numberOfIssuesFound": { "extreme": 0, "high": 1, "medium": 1, "low": 0 },
      "ruleViolations": {
        "IMG_MISSING_ALT": {
          "description": "Images must have alternate text",
          "name": "Images must have alternate text",
          "severity": "high",
          "selectors": ["img.hero"],
          "selectorData": [{ "selector": "img.hero", "HTML": "<img src=\"/banner.png\">", "suggestionLabel": "Add alt text", "suggestionType": "addAttribute" }],
          "WCAGLevel": "A",
          "WCAGLink": "https://www.w3.org/TR/WCAG21/#text-alternatives"
        },
        "HEADING_ORDER": {
          "description": "Headings must be in order",
          "name": "Heading order",
          "severity": "medium",
          "selectors": ["h3:first-of-type"],
          "selectorData": [{ "selector": "h3:first-of-type", "HTML": "<h3>Section</h3>", "suggestionLabel": "Use h2 before h3", "suggestionType": "changeTag" }],
          "WCAGLevel": "A",
          "WCAGLink": "https://www.w3.org/TR/WCAG21/#info-and-relationships"
        }
      }
    },
    "https://example.com/2": {
      "numberOfIssuesFound": { "extreme": 0, "high": 1, "medium": 1, "low": 0 },
      "ruleViolations": {
        "LINK_EMPTY": {
          "description": "Links must have discernible text",
          "name": "Links must have discernible text",
          "severity": "high",
          "selectors": ["a.nav-link", "a[href='#']"],
          "selectorData": [
            { "selector": "a.nav-link", "HTML": "<a class=\"nav-link\" href=\"/about\"></a>", "suggestionLabel": "Add link text", "suggestionType": "addContent" },
            { "selector": "a[href='#']", "HTML": "<a href=\"#\"> </a>", "suggestionLabel": "Add link text", "suggestionType": "addContent" }
          ],
          "WCAGLevel": "A",
          "WCAGLink": "https://www.w3.org/TR/WCAG21/#link-purpose-in-context"
        },
        "CONTRAST_MINIMUM": {
          "description": "Text must meet minimum contrast ratio",
          "name": "Contrast (Minimum)",
          "severity": "medium",
          "selectors": [".muted"],
          "selectorData": [{ "selector": ".muted", "HTML": "<span class=\"muted\">Disclaimer</span>", "suggestionLabel": "Increase contrast", "suggestionType": "changeStyle" }],
          "WCAGLevel": "AA",
          "WCAGLink": "https://www.w3.org/TR/WCAG21/#contrast-minimum"
        }
      }
    }
  },
  "totalNumberOfIssuesFound": { "extreme": 0, "high": 2, "medium": 2, "low": 0 }
}

localCheck

To fail local runs when thresholds are exceeded, set "localCheck": true in accessflow.config.json:

{
  "issuesFoundThreshold": { "extreme": 0, "high": 5, "medium": 10, "low": 20 },
  "localCheck": true
}

With localCheck: true, running pytest locally will exit with an error and print issue counts if the aggregated report exceeds the limits.

How it works

Local development

  • Audits run on each page
  • JSON reports saved to ./test-results directory
  • Reports are NOT uploaded to the accessFlow Audits page (prevents accidental uploads)

CI/CD environment

  • Audits run on each page
  • Results aggregated from parallel test workers
  • Automatically uploaded to the accessFlow Audits page
  • Threshold checks applied (fail build if exceeded)

Supported CI platforms: CircleCI, GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, Bitbucket Pipelines, Travis CI

Integrate with Selenium

With Selenium WebDriver

Install Selenium alongside the SDK:

pip install selenium

Use SeleniumDriver to wrap your WebDriver instance:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from accessflow_sdk import AccessFlowSDK, SeleniumDriver, finalize_reports

options = Options()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

driver = webdriver.Chrome(options=options)
driver.get("https://example.com")

sdk = AccessFlowSDK(SeleniumDriver(driver))
audits = sdk.audit()

driver.quit()

Selenium with Pytest

Add a conftest.py for Selenium-based pytest tests:

import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from accessflow_sdk import AccessFlowSDK, SeleniumDriver, finalize_reports

@pytest.fixture(scope='session')
def chrome_driver():
    options = Options()
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    driver = webdriver.Chrome(options=options)
    yield driver
    driver.quit()

@pytest.fixture
def selenium_sdk(chrome_driver):
    return AccessFlowSDK(SeleniumDriver(chrome_driver))

def pytest_sessionfinish(session, exitstatus):
    finalize_reports()

Then use this in your tests:

```python
def test_homepage_selenium(chrome_driver, selenium_sdk):
    chrome_driver.get("https://example.com")

    audits = selenium_sdk.audit()
    report = selenium_sdk.generate_report(audits)

    assert report['numberOfIssuesFound'].get('extreme', 0) == 0
```

Chrome/ChromeDriver Setup

Selenium requires a matching ChromeDriver version. In CI environments:

# GitHub Actions
- name: Setup Chrome
  uses: browser-actions/setup-chrome@v1
- name: Setup ChromeDriver
  uses: nanasess/setup-chromedriver@v2

Or install manually on Ubuntu:

sudo apt-get install -y google-chrome-stable
CHROME_VERSION=$(google-chrome --version | grep -oP '\\d+\\.\\d+\\.\\d+\\.\\d+')
wget "<https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chromedriver-linux64.zip>"
unzip chromedriver-linux64.zip && sudo mv chromedriver-linux64/chromedriver /usr/local/bin/

API Reference

AccessFlowSDK

class AccessFlowSDK(driver_or_page, api_key=None)

Parameters

Parameter Description
driver_or_page BrowserDriver (PlaywrightDriver or SeleniumDriver), or a Playwright Page object (automatically wrapped in PlaywrightDriver for backward compatibility)
api_key  (str, optional): API key (defaults to ACCESSFLOW_SDK_API_KEY env var)

Methods

Run an audit

Runs an accessibility audit on the current page.

audit()
audits = sdk.audit()
Returns

Raw audit data as a dictionary.

Note: 

audit() automatically records results for CI/CD report aggregation. You don't need to manually call record_audit(). Just configure a session-level teardown to call finalize_reports(). 

Generate a report

Generates a structured summary report from audit data. Optional.

generate_report(audits)
report = sdk.generate_report(audits)
Parameters
Parameter Type Description
audits dict The number of extreme, high, medium and low issues found.
export_type string Report format (JSON)
Returns
Parameter Description
numberOfIssuesFound The number of extreme, high, medium and low issues found.
ruleViolations A list of rules violated including severity, WCAG level and which selector was involved.

Here is an example of a report that is produced.

{
    'numberOfIssuesFound': {
        'extreme': 0,
        'high': 5,
        'medium': 8,
        'low': 12
    },
    'ruleViolations': {
        'colorContrast': {
            'name': 'Color Contrast',
            'severity': 'medium',
            'numberOfOccurrences': 3,
            'WCAGLevel': 'AA',
            'WCAGLink': 'https://www.w3.org/WAI/WCAG21/...',
            'description': 'Ensure sufficient color contrast...',
            'selectors': ['.header', '.footer']
        }
    }
}

Driver classes

PlaywrightDriver(page)

Wraps a Playwright Page for use with the SDK. Passing a Playwright Page directly to AccessFlowSDK is equivalent and preferred for Playwright users.

from accessflow_sdk import PlaywrightDriver, AccessFlowSDK

sdk = AccessFlowSDK(PlaywrightDriver(page))
# Same as: sdk = AccessFlowSDK(page)

SeleniumDriver(driver)

Wraps a Selenium WebDriver for use with the SDK.

from accessflow_sdk import SeleniumDriver, AccessFlowSDK

sdk = AccessFlowSDK(SeleniumDriver(webdriver_instance))

Teardown functions

These functions are needed to sync the results of your tests with the accessFlow app to upload reports and view results in the Audits page. Unlike the Node.js SDK (which uses Playwright's globalTeardown in config), Python has no built-in global teardown. 

Add finalize_reports() in your conftest.py using the pytest_sessionfinish hook so it runs once after all tests complete. See Pytest Integration for details.

record_audit(url: str, audits: dict)

Records an audit for later aggregation (useful for parallel test execution).

from accessflow_sdk import record_audit
record_audit(page.url, audits)

finalize_reports(api_key: str = None, run_id: str = None, output_dir: str = './test-results')

Finalizes and uploads all recorded audits. Call this once after all tests complete.

from accessflow_sdk import finalize_reports
finalize_reports()

Parameters

Parameter Type Description
api_key string Override API key from environment (optional)
run_id string Custom run id for reports  (optional)
output_dir string Directory for local JSON reports (default: './test-results')

Configure your CI/CD for accessFlow

In your CI pipelines, configure these secrets and variables. This will ensure your test results are displayed in the accessFlow Audits page.

Secrets

  • ACCESSFLOW_REGISTRY_TOKEN: To install the SDK

  • ACCESSFLOW_SDK_API_KEY: To authenticate audit requests

Variables

Variable Purpose Example
GCP_SDK_PYTHON_REGISTRY_URL Python registry URL (without https://) us-east1-python.pkg.dev/PROJECT/stag-accessflow-python

Important: Do not include https:// in the registry URL variable - it's added automatically in the pip install command.

GitHub actions

name: Accessibility Tests

on: [push]

jobs:
  test:
    runs-on: ubuntu-22.04  # Use 22.04 for Playwright compatibility
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      - run: |
          pip install -r requirements.txt
          pip install accessflow-sdk --index-url https://_json_key_base64:${ACCESSFLOW_REGISTRY_TOKEN}@PYTHON_REGISTRY_URL/simple/
          playwright install chromium        env:
          ACCESSFLOW_REGISTRY_TOKEN: ${{ secrets.ACCESSFLOW_REGISTRY_TOKEN }}
      - run: pytest -v
        env:
          ACCESSFLOW_SDK_API_KEY: ${{ secrets.ACCESSFLOW_SDK_API_KEY }}

CircleCI

jobs:
  test:
    docker:
      - image: cimg/python:3.10
    steps:
      - checkout
      - run: |
          pip install -r requirements.txt
          pip install accessflow-sdk --index-url https://_json_key_base64:${ACCESSFLOW_REGISTRY_TOKEN}@PYTHON_REGISTRY_URL/simple/
          playwright install chromium      - run: pytest -v
    environment:
      ACCESSFLOW_REGISTRY_TOKEN: ${ACCESSFLOW_REGISTRY_TOKEN}
      ACCESSFLOW_SDK_API_KEY: ${ACCESSFLOW_SDK_API_KEY}

GitLab CI

test:
  image: python:3.10
  variables:
    ACCESSFLOW_REGISTRY_TOKEN: $ACCESSFLOW_REGISTRY_TOKEN
    ACCESSFLOW_SDK_API_KEY: $ACCESSFLOW_SDK_API_KEY
  script:
    - pip install -r requirements.txt
    - pip install accessflow-sdk --index-url https://_json_key_base64:${ACCESSFLOW_REGISTRY_TOKEN}@PYTHON_REGISTRY_URL/simple/
    - playwright install chromium
    - pytest -v

Advanced usage

Running an audit on multiple pages

def test_multi_page(page, sdk):
    # Test homepage
    page.goto("https://example.com")
    home_audits = sdk.audit()

    # Test about page
    page.goto("https://example.com/about")
    about_audits = sdk.audit()

    # Generate reports
    home_report = sdk.generate_report(home_audits)
    about_report = sdk.generate_report(about_audits)

Async Playwright

import pytest
from playwright.async_api import async_playwright
from accessflow_sdk import AccessFlowSDK

@pytest.mark.asyncio
async def test_async_accessibility():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()

        sdk = AccessFlowSDK(page)
        await page.goto("https://example.com")

        audits = sdk.audit()  # Note: audit() is synchronous
        report = sdk.generate_report(audits)

        assert report['numberOfIssuesFound'].get('extreme', 0) == 0
        await browser.close()

Troubleshooting

API key issues

# Check if environment variable is set
import os
print(os.getenv('ACCESSFLOW_SDK_API_KEY'))

# Pass API key explicitly
sdk = AccessFlowSDK(page, api_key='your-api-key')

Never hardcode API keys in your source code.

Use:

  • Constructor parameters for local development
  • Environment variables for CI/CD (stored as secrets)
  • Configuration files excluded from version control

Reports not uploading

  • Check that finalize_reports() is called after all tests (e.g. in pytest_sessionfinish in conftest.py)
  • Verify your API key has upload permissions

Selenium WebDriver issues

Ensure Chrome and ChromeDriver versions match:

google-chrome --version
chromedriver --version
# Both should report the same major version

If ChromeDriver is not on your PATH, pass the service path explicitly:

from selenium.webdriver.chrome.service import Service

service = Service('/path/to/chromedriver')
driver = webdriver.Chrome(service=service, options=options)

For CI headless Chrome, always include these options to avoid sandbox errors:

options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')

Playwright issues

Make sure the Playwright browsers are installed.

npx playwright install

Was this article helpful?

0 out of 0 found this helpful