Use the accessFlow SDK with Playwright or Selenium for JavaScript and TypeScript 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:
- NPM install token: Used to install the SDK package (double base64-encoded)
- NPM 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 NPM 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
- TypeScript 4.0+
- Playwright for JavaScript/TypeScript
- Playwright browsers must be installed.
npx playwright installInstall the SDK
Install from a private package registry (recommended)
Step 1: Configure .npmrc
Create a .npmrc file in your project root:
@acsbe:registry=https://NPM_REGISTRY_URL/ //NPM_REGISTRY_URL/:username=_json_key_base64 //NPM_REGISTRY_URL/:_password=NPM_INSTALL_TOKEN //NPM_REGISTRY_URL/:always-auth=true
Replace:
NPM_REGISTRY_URLNPM_INSTALL_TOKEN
with the credentials provided by accessFlow.
Use the registry URL without https:// (e.g. us-east1-npm.pkg.dev/PROJECT/REPO). The // on the auth lines is part of the key format, not a comment.
Why Double Base64?
npm automatically base64-decodes the _password field before sending it to the registry. Since GCP Artifact Registry expects a base64-encoded service account key as the password, the token must be encoded twice so that after npm decodes it once, the correct value is sent.
The accessFlow team provides the token pre-encoded - paste it as-is.
Step 2: Install the SDK
npm install -D @acsbe/accessflow-sdk
In CI/CD environments, store the token as a secret (e.g. ACCESSFLOW_NPM_REGISTRY_TOKEN) and reference it in your .npmrc:
@acsbe:registry=https://NPM_REGISTRY_URL/
//NPM_REGISTRY_URL/:username=_json_key_base64
//NPM_REGISTRY_URL/:_password=${ACCESSFLOW_NPM_REGISTRY_TOKEN}
//NPM_REGISTRY_URL/:always-auth=truenpm automatically interpolates ${ENV_VAR} references in .npmrc files.
Install from a local package file
If you received a .tgz file:
npm install -D /path/to/acsbe-accessflow-sdk.tgz
Configure 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
The SDK automatically reads this value - no additional configuration or initialization is required.
# .env file (use with dotenv)
ACCESSFLOW_SDK_API_KEY=your-api-key
// No init needed - SDK reads from environment automatically
import { test } from '@playwright/test';
import AccessFlowSDK from '@acsbe/accessflow-sdk';
test('accessibility check', async ({ page }) => {
const sdk = new AccessFlowSDK(page);
// SDK automatically uses ACCESSFLOW_SDK_API_KEY from environment
});For CI/CD: environments store the API key as a secret:
# GitHub Actions
env:
ACCESSFLOW_SDK_API_KEY: ${{ secrets.ACCESSFLOW_SDK_API_KEY }}Optional: Pass the API key explicitly
You can also provide the accessFlow API key programmatically by calling AccessFlowSDK.init once prior to running your test suite (for example, reading from a custom config):
import AccessFlowSDK from '@acsbe/accessflow-sdk';
// Call once before your tests run
AccessFlowSDK.init({ apiKey: process.env.ACCESSFLOW_SDK_API_KEY! });Use in your test suite
Here are examples of how to use the SDK in your test suite.
Playwright
Make sure you have installed Playwright.
npm install -D @playwright/testAdd in your tests:
import { test, expect } from '@playwright/test';
import AccessFlowSDK from '@acsbe/accessflow-sdk';
test('homepage accessibility', async ({ page }) => {
const sdk = new AccessFlowSDK(page);
await page.goto('https://example.com');
const audits = await sdk.audit();
const report = await sdk.generateReport(audits);
console.log('Issues found:', report.numberOfIssuesFound);
// { extreme: 0, high: 5, medium: 8, low: 12 }
// Assert no critical issues
expect(report.numberOfIssuesFound.extreme).toBe(0);
expect(report.numberOfIssuesFound.high).toBeLessThanOrEqual(5);
});Selenium
Install the Selenium package alongside the SDK:
npm install -D selenium-webdriver npm install -D @types/selenium-webdriver # TypeScript users
Add in your tests:
import { Builder } from 'selenium-webdriver';
import { AccessFlowSDK, SeleniumDriver } from '@acsbe/accessflow-sdk';
const driver = await new Builder().forBrowser('chrome').build();
await driver.get('https://example.com');
const sdk = new AccessFlowSDK(new SeleniumDriver(driver));
const audits = await sdk.audit();
const report = await sdk.generateReport(audits);
console.log('Issues found:', report.numberOfIssuesFound);
await driver.quit();Integrate with Playwright
How it works:
- Each call to
sdk.audit()automatically records results for aggregation - After all tests complete,
globalTeardownruns 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 globalTeardown.
To generate and upload audit summaries automatically after all the tests have run, add the SDK's global teardown to your Playwright configuration.
Playwright test
import { test, expect } from '@playwright/test';
import AccessFlowSDK from '@acsbe/accessflow-sdk';
// SDK automatically reads ACCESSFLOW_SDK_API_KEY from environment
test('homepage accessibility', async ({ page }) => {
const sdk = new AccessFlowSDK(page);
await page.goto('https://example.com');
const audits = await sdk.audit();
const report = await sdk.generateReport(audits);
// Assert no critical issues
expect(report.numberOfIssuesFound.extreme).toBe(0);
expect(report.numberOfIssuesFound.high).toBeLessThanOrEqual(5);
});Global setup/teardown
Add the following in playwright.config.ts:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTeardown: require.resolve('@acsbe/accessflow-sdk/dist/src/playwright/global-teardown'),
// ... other config
});Integrate with Selenium
How it works:
- Each call to
sdk.audit()automatically records results for aggregation - After all tests complete,
globalTeardownruns 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)
Selenium WebDriver
Use SeleniumDriver to wrap your WebDriver instance:
import { Builder, WebDriver } from 'selenium-webdriver';
import { AccessFlowSDK, SeleniumDriver } from '@acsbe/accessflow-sdk';
// Set ACCESSFLOW_SDK_API_KEY in your environment
async function runAccessibilityTests() {
const driver: WebDriver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com');
const sdk = new AccessFlowSDK(new SeleniumDriver(driver));
const audits = await sdk.audit();
const report = await sdk.generateReport(audits);
console.log('Issues found:', report.numberOfIssuesFound);
} finally {
await driver.quit();
}
}Teardown and report upload for Selenium
globalTeardown must be invoked manually after all tests complete. Import and call it from your test runner's after-all hook. This aggregates all recorded audits, uploads the report to accessFlow (when ACCESSFLOW_SDK_API_KEY is set in CI environments), and validates thresholds.
import { globalTeardown } from '@acsbe/accessflow-sdk';
// In your test framework's after-all hook, e.g.:
// - Jest: afterAll
// - Mocha: after (root-level)
// - node:test: after
afterAll(async () => {
await globalTeardown(null as any);
});Chrome/ChromeDriver setup
Selenium requires Chrome and a matching ChromeDriver. In CI environments, install them before running tests:
# GitHub Actions example - name: Install Chrome uses: browser-actions/setup-chrome@v1 - name: Install ChromeDriver uses: nanasess/setup-chromedriver@v2
Or manually:
# 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/Configure the SDK (optional)
Create a configuration file
Create sdk.config.ts in your project root:
import { SDKConfig } from '@acsbe/accessflow-sdk';
const config: SDKConfig = {
apiKey: process.env.ACCESSFLOW_SDK_API_KEY,
uploadEnabled: true,
outputDir: './accessibility-reports',
wcagLevel: 'AA', // 'A', 'AA', or 'AAA'
};
export default config;Configure thresholds
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. The configuration is automatically loaded during the global teardown phase.
If no config file is provided, thresholds are not enforced (tests won’t fail because of counts). Reports are still generated.
Example
{
"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.
|
Final report and localCheck
Final report
When all tests have run, the global teardown (or Selenium globalTeardown() call) aggregates every recorded audit into a single report, uploads it to the accessFlow Audit page in CI, and validates it against issuesFoundThreshold. No extra code is required. The report JSON includes:
-
pages - object keyed by URL; each value has
numberOfIssuesFound(severity counts) andruleViolations(rule id → description, severity, selectors,selectorDatawith HTML/suggestionLabel/suggestionType, WCAG level/link). - totalNumberOfIssuesFound - aggregated severity counts across all pages.
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
By default, thresholds are only enforced in CI environments. To fail local runs when limits are exceeded, set "localCheck": true in accessflow.config.json:
{
"issuesFoundThreshold": { "extreme": 0, "high": 5, "medium": 10, "low": 20 },
"localCheck": true
}With localCheck: true, running npm test (or your test command) locally will exit with an error and print issue counts if the aggregated report exceeds the configured limits.
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_NPM_REGISTRY_TOKEN: To install the SDK
ACCESSFLOW_SDK_API_KEY: To authenticate audit requests
Variables
| Variable | Purpose | Example |
|---|---|---|
GCP_SDK_NPM_REGISTRY_URL |
NPM registry URL (without https://) |
us-east1-npm.pkg.dev/PROJECT/stag-accessflow-npm |
Important: Do not include https:// in the registry URL variable - it's added automatically by the .npmrc configuration.
Your .npmrc should reference these environment variables.
GitHub Actions example
```yaml
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-node@v3
with:
node-version: '18'
- run: npm install
env:
ACCESSFLOW_NPM_REGISTRY_TOKEN: ${{ secrets.ACCESSFLOW_NPM_REGISTRY_TOKEN }}
- run: npx playwright install chromium
- run: npm test
env:
ACCESSFLOW_SDK_API_KEY: ${{ secrets.ACCESSFLOW_SDK_API_KEY }}
```CircleCI example
jobs:
test:
docker:
- image: mcr.microsoft.com/playwright:v1.40.0-focal
steps:
- checkout
- run: npm install
- run: npm test
environment:
ACCESSFLOW_NPM_REGISTRY_TOKEN: ${ACCESSFLOW_NPM_REGISTRY_TOKEN}
ACCESSFLOW_SDK_API_KEY: ${ACCESSFLOW_SDK_API_KEY}Jenkins example
API Reference
Constructor
AccessFlowSDK.init(options)
Initializes the SDK configuration (optional).
AccessFlowSDK.init({ apiKey: 'your-api-key' });new AccessFlowSDK(page, testInfo?, options?)
Creates a new SDK instance for a page. If testInfo is provided, HTML or JSON reports are attached directly to the test results.
new AccessFlowSDK(page, testInfo?, options?)
| Parameter | Description |
|---|---|
| page | Playwright Page object |
| testInfo | Playwright testInfo |
| options | Threshold configuration |
const sdk = new AccessFlowSDK(page);Run an audit
Runs an accessibility audit on the current page.
await sdk.audit()
const audits = await sdk.audit();
Returns
Returns audit results including:
- Severity levels
- Rule violations
- WCAG references
- Element selectors
Generate a report
Takes the results returned by audit() and produces a structured report. counting issues by severity. Can be generated per test.
When exportType is set to "html" or "json", a human readable report is attached to the test results. (when testInfo is provided) or written to the test-results folder.
await sdk.generateReport(audits)
const report = await sdk.generateReport(audits);
console.log(report.numberOfIssuesFound);
// { extreme: 0, high: 5, medium: 8, low: 12 }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. |
| Additional metadata |
Environment variables
| Variable | Required | Purpose |
|---|---|---|
ACCESSFLOW_SDK_API_KEY |
Yes | Your accessFlow SDK API key (for runtime authentication) |
ACCESSFLOW_NPM_REGISTRY_TOKEN |
CI/CD only | Double base64-encoded registry token (referenced in .npmrc) |
ACCESSFLOW_OUTPUT_DIR |
Optional | Custom report directory |
TypeScript support
The SDK is written in TypeScript and includes full type definitions:
import AccessFlowSDK, {
IAudits,
AuditReport,
SDKConfig,
SeleniumDriver,
PlaywrightDriver,
IBrowserDriver,
} from '@acsbe/accessflow-sdk';
const config: SDKConfig = {
apiKey: 'your-key',
wcagLevel: 'AA',
};
AccessFlowSDK.init(config);
// Playwright — types are automatically inferred
const audits: IAudits = await sdk.audit();
const report: AuditReport = await sdk.generateReport(audits);
// Selenium — explicit driver type
const driver: IBrowserDriver = new SeleniumDriver(webDriver);
const seleniumSdk = new AccessFlowSDK(driver);Troubleshooting
API key not found
// Check if environment variable is set
console.log('API Key:', process.env.ACCESSFLOW_SDK_API_KEY ? 'Set' : 'Missing');
// Or initialize manually
AccessFlowSDK.init({ apiKey: 'your-api-key' });Reports not uploading
- Ensure the SDK is running as part of your CI environment.
- Confirm the API key is valid
// Check if CI is detected
console.log('CI detected:', process.env.CI);
// Disable uploads for testing
process.env.ACCESSFLOW_UPLOAD_ENABLED = 'false';Playwright browser issues
Make sure the Playwright browsers are properly initialized.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
const sdk = new AccessFlowSDK(page);
// Now you can use the SDKSelenium WebDriver issues
Ensure Chrome and ChromeDriver versions match:
google-chrome --version
chromedriver --version
# Both should report the same major versionIf Selenium can't find the driver binary, pass the path explicitly:
import { Builder, Capabilities } from 'selenium-webdriver';
import chrome from 'selenium-webdriver/chrome';
const options = new chrome.Options();
options.addArguments('--headless', '--no-sandbox', '--disable-dev-shm-usage');
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();