accessiBe Help Center

accessFlow SDK - Java

  • Updated

Use the accessFlow SDK with Playwright or Selenium for Java applications to run automated accessibility audits as part of your test suite.

Supports JUnit 5, JUnit 4, TestNG, and Cucumber.

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)
  • Maven 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

  • Java 11+
  • Playwright or Selenium for Java
  • Maven or Gradle
  • Gson

Install the SDK

Install from a private package registry (recommended)

Maven (pom.xml)

Add the accessFlow repository and dependency:

<repositories>
  <repository>
    <id>accessflow-registry</id>
    <url>https://ACCESSFLOW_MAVEN_REGISTRY_URL</url>
  </repository>
</repositories>

<dependency>
    <groupId>com.acsbe</groupId>
    <artifactId>accessflow-sdk</artifactId>
    <version>1.0.1</version>
    <scope>test</scope>
</dependency>

Add a runtime dependency:

  • Playwright: com.microsoft.playwright:playwright
  • Selenium: org.seleniumhq.selenium:selenium-java

Configure credentials in ~/.m2/settings.xml:

<settings>
  <servers>
    <server>
      <id>accessflow-registry</id>
      <username>_json_key_base64</username>
      <password>REGISTRY_INSTALL_TOKEN</password>
    </server>
  </servers>
</settings>

Gradle (build.gradle)

repositories {
    maven {
        url "https://ACCESSFLOW_MAVEN_REGISTRY_URL"
        credentials {
            username = "_json_key_base64"
            password = project.findProperty("REGISTRY_INSTALL_TOKEN")
                ?: System.getenv("ACCESSFLOW_REGISTRY_TOKEN")
        }
    }
}

dependencies {
    testImplementation 'com.acsbe:accessflow-sdk:1.1.0'
}

Install from a local JAR

If you received a JAR file:

Install to the local Maven repository

mvn install:install-file
  -Dfile=/path/to/accessflow-sdk.jar
  -DgroupId=com.acsbe
  -DartifactId=accessflow-sdk
  -Dversion=1.1.0
  -Dpackaging=jar

Then use the dependency block normally.

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):

export ACCESSFLOW_SDK_API_KEY=your-api-key-here

Then initialize normally:

AccessFlowSDK sdk = new AccessFlowSDK(page);

Playwright example

Page page = browser.newPage();
AccessFlowSDK sdk = new AccessFlowSDK(page);

page.navigate("<https://example.com>");

Map<String, Object> audits = sdk.audit();
Map<String, Object> report = sdk.generateReport(audits);

Selenium example 

WebDriver driver = new ChromeDriver();

AccessFlowSDK sdk =
    new AccessFlowSDK(new SeleniumDriver(driver));

driver.get("<https://example.com>");

Map<String, Object> audits = sdk.audit();
Map<String, Object> report = sdk.generateReport(audits);

driver.quit();

Integrate with the test suite

Note: Manual teardown is not needed in modern frameworks.

Automatic behavior

  • sdk.audit() automatically records results
  • Reports are automatically aggregated
  • Reports are automatically uploaded in CI environments
Framework Behavior
JUnit 5 Fully automatic
TestNG 7.6+ Fully automatic
TestNG <7.6 Manual listener required
JUnit 4 Manual finalize required

JUint 5 example (recommended)

This is an example of how to use the SDK.

In this example, AccessFlowTeardown.finalizeReports() is not needed.
@Test
public void testAccessibility() {
    Page page = browser.newPage();
    AccessFlowSDK sdk = new AccessFlowSDK(page);

    page.navigate("https://example.com");
    sdk.audit(); // auto-recorded

    page.close();
}
 

JUnit 4 example (manual)

In this example, AccessFlowTeardown.finalizeReports() is needed.

@AfterClass
public static void teardown() {
    AccessFlowTeardown.finalizeReports();
}

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:

{
  "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, 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 page in CI environments, and validates it against issuesFoundThreshold

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

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 mvn test locally will exit with an error and print issue counts if the aggregated report exceeds the configured limits.

API Reference

AccessFlowSDK

Constructors

AccessFlowSDK(Page page)
AccessFlowSDK(Page page, String apiKey)
Parameters
Parameter Description
page Playwright Page object
apiKey The API key generated

Run an audit

Runs an accessibility audit on the current page.

sdk.audit()
Map<String, Object> audits = sdk.audit();
Returns

Raw audit data as a dictionary.

Generate a report

Generates a structured summary report from audit data.

sdk.generate_report(audits)
Map<String, Object> report = sdk.generateReport(audits)
Returns
  • Issue counts by severity
  • Rule violations
  • WCAG level
  • Descriptions
  • CSS selectors

AccessFlowTeardown

Manual teardown is usually handled automatically.

AccessFlowTeardown.finalizeReports();

Integrate with your CI/CD 

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_MAVEN_REGISTRY_URL Maven registry URL (full URL with https://) https://us-east1-maven.pkg.dev/PROJECT/stag-accessflow-maven

Note: Maven requires the full URL including https://  unlike npm and pip which add it automatically.

Maven example

Generate settings.xml dynamically in your pipeline using the secret:

mkdir -p ~/.m2
cat > ~/.m2/settings.xml << EOF
<settings>
  <servers>
    <server>
      <id>accessflow-registry</id>
      <username>_json_key_base64</username>
      <password>${ACCESSFLOW_REGISTRY_TOKEN}</password>
    </server>
  </servers>
</settings>
EOF

GitHub Actions example 

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-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'
      - name: Configure Maven authentication
        env:
          ACCESSFLOW_REGISTRY_TOKEN: ${{ secrets.ACCESSFLOW_REGISTRY_TOKEN }}
          GCP_SDK_MAVEN_REGISTRY_URL: ${{ vars.GCP_SDK_MAVEN_REGISTRY_URL }}
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <servers>
              <server>
                <id>accessflow-registry</id>
                <username>_json_key_base64</username>
                <password>${ACCESSFLOW_REGISTRY_TOKEN}</password>
              </server>
            </servers>
            <profiles>
              <profile>
                <id>accessflow</id>
                <repositories>
                  <repository>
                    <id>accessflow-registry</id>
                    <url>${GCP_SDK_MAVEN_REGISTRY_URL}</url>
                  </repository>
                </repositories>
              </profile>
            </profiles>
            <activeProfiles>
              <activeProfile>accessflow</activeProfile>
            </activeProfiles>
          </settings>
          EOF      - name: Resolve dependencies
        run: mvn dependency:resolve
      - name: Install Playwright browsers
        run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.classpathScope=test -D exec.args="install --with-deps chromium"
      - run: mvn test
        env:
          ACCESSFLOW_SDK_API_KEY: ${{ secrets.ACCESSFLOW_SDK_API_KEY }}

Jenkins example

pipeline {
  agent any

  environment {
    ACCESSFLOW_REGISTRY_TOKEN = credentials('accessflow-registry-token')
    ACCESSFLOW_SDK_API_KEY = credentials('accessflow-api-key')
  }

  stages {
    stage('Setup') {
      steps {
        sh '''
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <servers>
              <server>
                <id>accessflow-registry</id>
                <username>_json_key_base64</username>
                <password>${ACCESSFLOW_REGISTRY_TOKEN}</password>
              </server>
            </servers>
          </settings>
          EOF
        '''
      }
    }
    stage('Install Playwright') {
      steps {
        sh 'mvn dependency:resolve'
        sh 'mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.classpathScope=test -D exec.args="install --with-deps chromium"'
      }
    }
    stage('Test') {
      steps {
        sh 'mvn test'
      }
    }
  }
}

Gradle example

Pass the token as an environment variable:

ACCESSFLOW_REGISTRY_TOKEN=your-base64-token mvn test

The build.gradle configuration reads from this environment variable automatically.

Framework integration

JUnit 5 (recommended - zero config)

The SDK ships a TestExecutionListener registered via Java ServiceLoader. JUnit 5 picks it up automatically - no annotations or configuration needed. 

Reports are finalized and uploaded once after the whole test plan finishes.

import com.acsbe.accessflow.*;
import org.junit.jupiter.api.*;
import java.util.Map;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class AccessibilityTest {
    private Playwright playwright;
    private Browser browser;

    @BeforeAll void setup() {
        playwright = Playwright.create();
        browser = playwright.chromium().launch();
    }

    @AfterAll void teardown() {
        // AccessFlowTeardown.finalizeReports() is called automatically by
        // the SDK's TestExecutionListener — no manual call needed.
        if (browser != null) browser.close();
        if (playwright != null) playwright.close();
    }

    @Test
    public void testAccessibility() {
        Page page = browser.newPage();
        AccessFlowSDK sdk = new AccessFlowSDK(page);
        page.navigate("https://example.com");
        sdk.audit(); // automatically records results
        page.close();
    }
}

JUnit 4 (manual teardown required)

JUnit 4 has no TestExecutionListener concept, so you must call finalizeReports() yourself. The recommended approach is a @ClassRule or @AfterClass:

import com.acsbe.accessflow.*;
import org.junit.*;
import java.util.Map;

public class AccessibilityTest {
    private static Playwright playwright;
    private static Browser browser;

    @BeforeClass
    public static void setup() {
        playwright = Playwright.create();
        browser = playwright.chromium().launch();
    }

    @AfterClass
    public static void teardown() {
        // Finalize and upload all recorded audits at the end of the class
        AccessFlowTeardown.finalizeReports();

        if (browser != null) browser.close();
        if (playwright != null) playwright.close();
    }

    @Test
    public void testAccessibility() {
        Page page = browser.newPage();
        AccessFlowSDK sdk = new AccessFlowSDK(page);
        page.navigate("https://example.com");
        sdk.audit(); // automatically records results
        page.close();
    }
}

If you have multiple test classes and want a single suite-level teardown, register a JUnit 4 RunListener  on your test runner (e.g. Maven Surefire):

```java
// src/test/java/com/example/AccessFlowRunListener.java
import com.acsbe.accessflow.AccessFlowTeardown;
import org.junit.runner.notification.RunListener;
import org.junit.runner.Description;

public class AccessFlowRunListener extends RunListener {
    @Override
    public void testRunFinished(org.junit.runner.Result result) {
        AccessFlowTeardown.finalizeReports();
    }
}
```

```xml
<!-- pom.xml — register listener with Surefire -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <properties>
            <property>
                <name>listener</name>
                <value>com.example.AccessFlowRunListener</value>
            </property>
        </properties>
    </configuration>
</plugin>
```

TestNG (automatic via ServiceLoader - zero config with TestNG 7.6+)

The SDK ships an ISuiteListener (AccessFlowSuiteListener) registered via Java ServiceLoader. TestNG 7.6+ discovers it automatically from the JAR - no annotations or testng.xml changes needed. 

Reports are finalized and uploaded once after the whole suite finishes.

Zero-config (TestNG 7.6+)

Just run your tests normally - the listener is auto-registered:

import com.acsbe.accessflow.*;
import org.testng.annotations.*;
import java.util.Map;

public class AccessibilityTest {
    private Playwright playwright;
    private Browser browser;

    @BeforeClass
    public void setup() {
        playwright = Playwright.create();
        browser = playwright.chromium().launch();
    }

    @AfterClass
    public void teardown() {
        // AccessFlowSuiteListener finalizes reports automatically after the suite.
        // No manual call needed here.
        if (browser != null) browser.close();
        if (playwright != null) playwright.close();
    }

    @Test
    public void testAccessibility() {
        Page page = browser.newPage();
        AccessFlowSDK sdk = new AccessFlowSDK(page);
        page.navigate("https://example.com");
        sdk.audit(); // automatically records results
        page.close();
    }
}

Explicit registration (older TestNG or if ServiceLoader is disabled)

Option A - testng.xml

<suite name="Accessibility Suite">
  <listeners>
    <listener class-name="com.acsbe.accessflow.AccessFlowSuiteListener"/>
  </listeners>
  <test name="Accessibility Tests">
    <classes>
      <class name="com.example.AccessibilityTest"/>
    </classes>
  </test>
</suite>

Option B - @Listeners annotation on your test class

import com.acsbe.accessflow.AccessFlowSuiteListener;
import org.testng.annotations.Listeners;

@Listeners(AccessFlowSuiteListener.class)
public class AccessibilityTest {
    // ...
}

Cucumber

import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.en.*;

public class AccessibilitySteps {
    private Page page;
    private AccessFlowSDK sdk;

    @Before
    public void setup() {
        Playwright playwright = Playwright.create();
        Browser browser = playwright.chromium().launch();
        page = browser.newPage();
        sdk = new AccessFlowSDK(page);
    }

    @When("I visit the homepage")
    public void visitHomepage() {
        page.navigate("https://example.com");
    }

    @Then("the page should be accessible")
    public void checkAccessibility() {
        Map<String, Object> audits = sdk.audit();
        // Note: audit() automatically records results

        Map<String, Object> report = sdk.generateReport(audits);
        @SuppressWarnings("unchecked")
        Map<String, Object> issues =
            (Map<String, Object>) report.get("numberOfIssuesFound");

        assertEquals(0, issues.getOrDefault("extreme", 0));
    }

    @After
    public void teardown() {
        if (page != null) page.close();
    }
}

Advanced usage

Running an audit on multiple pages

```java
@Test
public void testMultiplePages() {
    Page page = browser.newPage();
    AccessFlowSDK sdk = new AccessFlowSDK(page);

    // Test homepage
    page.navigate("https://example.com");
    Map<String, Object> homeAudits = sdk.audit();
    // Note: audit() automatically records results

    // Test about page
    page.navigate("https://example.com/about");
    Map<String, Object> aboutAudits = sdk.audit();
    // Note: audit() automatically records results

    page.close();
}
```

Setting a custom API key per test

@Test
public void testWithCustomKey() {
    Page page = browser.newPage();
    AccessFlowSDK sdk = new AccessFlowSDK(page, "custom-api-key");

    page.navigate("https://example.com");
    Map<String, Object> audits = sdk.audit();
    Map<String, Object> report = sdk.generateReport(audits);

    // Assertions...
    page.close();
}

Troubleshooting

API key issues

// Check if environment variable is set
System.out.println(System.getenv("ACCESSFLOW_SDK_API_KEY"));

// Pass API key explicitly (recommended)
AccessFlowSDK sdk = new AccessFlowSDK(page, "your-api-key");

// Or set as system property (for testing)
System.setProperty("ACCESSFLOW_SDK_API_KEY", "your-api-key");

Never hardcode API keys. Use:

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

Reports not uploading

  • Verify API key has upload permissions
  • Check AccessFlowTeardown.finalizeReports() is called after all tests

Playwright browser not found

# Install Playwright browsers
# Note: -D exec.classpathScope=test is required because Playwright is a test-scoped dependency
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.classpathScope=test -D exec.args="install --with-deps chromium"

Build errors

# Clean and rebuild
mvn clean install

# Update dependencies
mvn dependency:resolve

Was this article helpful?

0 out of 0 found this helpful