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-resultsdirectory - 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 installInstall 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_TOKENPYTHON_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.gzConfigure the SDK API key
First generate a token in accessFlow and then use it to set the API key.
Generate a token
- Go to your Profile menu in top right corner and select Token management.
- Select Generate token.
- Enter a token name.
- Select Audit SDK token and then Generate token.
- 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 chromiumExample 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
- Each call to
sdk.audit()automatically records results for aggregation. - 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_KEYset). - 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) <= 5Configure 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.
|
| localCheck | boolean |
(Optional) Whether threshold checking is enforced during test execution. Default is false.
|
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: numberOfIssuesFound, ruleViolations 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-resultsdirectory - 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 | A 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:
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 SDKACCESSFLOW_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 -vAdvanced 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. inpytest_sessionfinishinconftest.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