Skip to main content
Back to Blog
Guide
2026-09-28

IBM Equal Access Accessibility Checker: Automated a11y Testing Guide

Equal Access Accessibility Checker guide for QA teams: configure scans, add CI gates, use baselines, and triage a11y failures with confidence.

IBM Equal Access Accessibility Checker: Automated a11y Testing Guide

IBM Equal Access Accessibility Checker is active, not renamed, and not discontinued. I verified the Node package accessibility-checker at version 4.0.34, published in September 2026, and the IBM repository shows recent release automation for the same version. The tool family includes browser extensions, a Node package, a Cypress wrapper, a Java checker, an accessibility rules engine, and published IBM rule-set documentation.

For QA engineers, the payoff is straightforward: use the equal access accessibility checker for deterministic accessibility scans inside tests, then keep manual accessibility work for keyboard journeys, screen reader behavior, UX intent, and criteria that automation cannot prove. It is a strong fit for teams that already run Playwright, Puppeteer, Selenium, Cypress, or URL-based smoke tests in CI.

The first decision is scope. Do not wire the checker into every end-to-end test and call the suite accessible. Instead, build a small accessibility contract around stable pages and critical states: unauthenticated marketing routes, logged-in dashboard shells, modal dialogs, form errors, navigation menus, and data tables. Use the checker as a gate for regressions, then pair it with deeper workflows from accessibility testing automation and focused CLI scans like Pa11y accessibility testing when you need a second engine or broader URL coverage.

Verified Project Snapshot

AreaVerified status on 2026-09-28What QA teams should do
Node packageaccessibility-checker is active at 4.0.34 on npmPin the package in a lockfile and review release notes before broad upgrades
RepositoryIBMa/equal-access has recent main-4.x activity and release jobsTreat it as maintained, but still pin dependencies in CI
RuntimeRepository build requirements list Node 22Run scanner jobs on Node 22 or newer, matching your app test runner where possible
Browser toolingNode docs name Selenium, Puppeteer, and Playwright page objectsPrefer the driver you already use for user-state setup
Cypresscypress-accessibility-checker wraps the Node packageUse it for Cypress suites, but verify plugin compatibility with your Cypress major
RulesDefault policy is IBM_Accessibility; npx achecker archives lists archives and policiesPin ruleArchive: versioned when audit repeatability matters

The practical implication is that IBM Equal Access can sit at three levels. A browser extension is useful while debugging a single page. The Node package is useful for Playwright, Puppeteer, Selenium, URL, file, and HTML-string scans. The Cypress package is useful when your existing Cypress tests already navigate the state that needs a scan. Do not mix all three in one pipeline unless you have a reason. One well-owned CI gate beats three noisy scans that nobody triages.

Choose the Integration Surface

IntegrationBest useWatch out for
Browser extensionExploratory checks during story review or bug reproductionResults are easy to forget unless you copy them into a ticket
npx acheckerBatch URL, file, or directory scans with minimal test codeIt may instantiate Puppeteer, so CI browser dependencies still matter
Playwright or Puppeteer APIScan authenticated pages, component states, modals, and SPA routesAlways close the checker engine after scans finish
Selenium APIEnterprise suites already built around WebDriverUse a unique label per scan so baseline matching is predictable
Cypress wrapperCypress teams that want scans inside existing command chainsKeep accessibility checks away from highly dynamic tests unless baselined

The mistake people make is treating accessibility scans like visual snapshots: run everywhere, approve once, forget. Equal Access baselines compare results by rule and XPath. That is useful, but it can also hide an issue that moved to a different element or changed because the DOM was refactored. The baseline should be a temporary contract for known exceptions, not a permanent excuse file.

Install the Node Checker

Use the Node package when you want one integration that works across test runners. The official install command is short:

npm install --save-dev accessibility-checker
npx achecker --version
npx achecker archives

For CI, the first command belongs in normal dependency installation. The second command gives you a quick sanity check that the binary is on the path. The third command matters because policy and archive names are not guesses. If an AI coding agent proposes a policy string, make it prove the string appears in npx achecker archives or in your already approved config.

If your project uses ES modules, the IBM docs call out a separate aceconfig.mjs option because Node cannot load a CommonJS aceconfig.js from an ESM package. That is a small detail, but it is exactly the detail that breaks a CI job after a repo switches "type": "module".

Configure .achecker.yml For CI

The default configuration uses the latest archive, the IBM_Accessibility policy, and default report behavior. That is fine for a first scan. For CI, write the intent down in .achecker.yml so the runner, local machine, and agent all use the same thresholds.

ruleArchive: versioned
policies:
  - IBM_Accessibility
failLevels:
  - violation
  - potentialviolation
reportLevels:
  - violation
  - potentialviolation
  - recommendation
  - potentialrecommendation
  - manual
outputFormat:
  - json
  - html
outputFolder: results/accessibility
outputFilenameTimestamp: false
baselineFolder: test/accessibility/baselines
cacheFolder: .cache/accessibility-checker
puppeteerArgs:
  - --no-sandbox
  - --disable-setuid-sandbox

ruleArchive: latest gives you the newest rules. That is useful for exploratory work and scheduled drift checks. ruleArchive: versioned uses the rule release aligned with the tool version, which makes CI less surprising. A team that owns regulated reports should prefer repeatability in pull requests, then run a separate scheduled job against the latest archive to discover new rule coverage.

failLevels and reportLevels solve different problems. reportLevels controls what appears in the output. failLevels controls whether assertCompliance returns a failure. Do not hide manual and recommendation findings from reports just because they should not block the build. QA leads need that data for backlog planning.

Scan From Playwright Without Losing State

A strong Playwright pattern is to navigate and assert page readiness before invoking the checker. That prevents scans against loading skeletons, empty placeholders, or modals that have not opened yet.

const { test, expect } = require('@playwright/test');
const aChecker = require('accessibility-checker');

test.afterAll(async () => {
  await aChecker.close();
});

test('settings page has no blocking IBM Equal Access findings', async ({ page }) => {
  await page.goto('http://localhost:3000/settings');
  await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
  await expect(page.getByRole('button', { name: 'Save changes' })).toBeEnabled();

  const result = await aChecker.getCompliance(page, 'settings/default');
  const code = aChecker.assertCompliance(result.report);

  if (code !== 0) {
    console.log(aChecker.stringifyResults(result.report));
  }

  expect(code).toBe(0);
});

Two details are doing real work here. The heading and button assertions prove the app is in the intended state before scanning. The afterAll hook closes the engine, which IBM documents as important for proper report output and cleanup. If you scatter one-off scans without closing the engine, you can get missing reports or slow worker shutdown.

For agent-written tests, make the label scheme explicit. A good label like settings/default, checkout/payment-error, or admin/users-table survives refactors. A label like test 1 makes baselines and report folders hard to trust.

Use Cypress When Cypress Owns The Journey

The Cypress wrapper adds commands that map to the Node API. The typical flow is cy.getCompliance(label).assertCompliance(). Register the plugin in the Cypress node events setup and import the support command once.

const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    setupNodeEvents(on) {
      on('task', {
        accessibilityChecker: require('cypress-accessibility-checker/plugin'),
      });
    },
  },
});
import 'cypress-accessibility-checker';
// cy.findByRole comes from Cypress Testing Library: npm install --save-dev @testing-library/cypress
import '@testing-library/cypress/add-commands';

describe('billing accessibility', () => {
  it('checks the declined-card error state', () => {
    cy.visit('/billing');
    cy.findByRole('button', { name: 'Update payment method' }).click();
    cy.findByLabelText('Card number').type('4000000000000002');
    cy.findByRole('button', { name: 'Save card' }).click();
    cy.findByText('Your card was declined.').should('be.visible');

    cy.getCompliance('billing/declined-card').assertCompliance();
  });
});

The Cypress example checks a failure state, not just a happy page load. That matters because many serious accessibility regressions appear only after validation errors, loading failures, disabled actions, toast messages, and focus moves. A checkout screen can pass on first render and still fail when the error summary is not announced or a modal traps keyboard users.

Baselines Without Normalizing Failure

Baselines are valuable when a known accessibility issue cannot be fixed in the same sprint. IBM documents that assertCompliance compares matching baseline results by XPath and rule ID; without a baseline, it evaluates the configured failLevels.

Use baselines for known, ticketed exceptions. Do not use them to get the first pipeline green.

Baseline situationGood responseRisky response
Third-party widget has a known violationBaseline one route, link to the vendor ticket, add expiry reviewBaseline every page that imports the widget
Large legacy app introduces a first gateStart with report-only, then gate changed routesGenerate baselines for the entire app and never revisit
Dynamic table creates unstable XPathAdd stable markup and reduce scan scopeAccept repeated baseline churn
Rule archive update changes outputRun scheduled latest-archive job, then plan fixesChange failLevels to ignore the new category

A useful baseline review includes three questions. Is the finding still present? Is the affected flow still important? Is the original owner still accountable? If any answer is unknown, the baseline has become debt rather than documentation.

GitHub Actions Pipeline

This workflow runs app tests, preserves the reports, and uses current GitHub Actions majors supplied for this environment. It assumes your npm scripts start the app and run Playwright accessibility specs.

name: accessibility

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  equal-access:
    runs-on: ubuntu-24.04
    steps:
      - name: Check out code
        uses: actions/checkout@v7

      - name: Set up Node
        uses: actions/setup-node@v7
        with:
          node-version: 22
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run IBM Equal Access tests
        run: npm run test:a11y

      - name: Upload accessibility reports
        if: always()
        uses: actions/upload-artifact@v7
        with:
          name: equal-access-reports-${{ github.run_id }}
          path: results/accessibility
          if-no-files-found: warn

For pull requests, fail on violation and potentialviolation after the team has burned down the first wave. During adoption, run the same job with continue-on-error: true or a report-only script for a week, but publish the report every time. The habit you want is not "green at all costs." It is "new accessibility debt is visible immediately."

Multi-Page CLI Scans

The npx achecker CLI can scan paths from a text file. Use that when the state does not require complex authentication or when you can seed a static preview.

http://localhost:3000/
http://localhost:3000/pricing
http://localhost:3000/docs/getting-started
./storybook-static/button-primary.html
npm run build
npm run preview
npx achecker ./a11y-targets.txt

This is intentionally boring. It catches broken language attributes, missing image alternatives, invalid ARIA, landmark mistakes, contrast issues that the engine can detect, and recurring template defects. It will not prove a whole product is accessible. That is fine. A fast smoke scan earns its keep by catching regression classes before they reach manual audit.

Diagnose A Real Failure Mode

A common CI failure looks like this: local Playwright accessibility tests pass, but GitHub Actions fails with a content security policy error while loading the checker engine. IBM documents a known CSP issue where the engine script can be blocked when loaded from a CDN. The quick diagnosis is to open the browser console or saved Playwright trace and look for a refused script load tied to the accessibility checker engine.

Do not fix this by disabling CSP in tests unless your production app also disables it, which it should not. Prefer a test-specific configuration that points the rule server to the allowed IBM host, or run the scan against a page state where the checker can inject what it needs without relaxing unrelated security controls. If the failure happens only on CI, also compare headers from local preview and CI preview. Reverse proxies and preview servers often add stricter CSP than the developer server.

Another failure is much quieter: the scan passes because it ran too early. The report shows a tiny number of executed rules, few elements, and no meaningful findings. The fix is not to trust the pass. Add readiness assertions before the scan, and for SPA screens wait on user-visible landmarks rather than network idle. A page can stop making requests while still rendering empty tabs or delayed form controls.

What People Get Wrong About Automated A11y

Automation is a detector, not a judge. Equal Access can flag rule violations and potential violations, but it cannot understand whether the product explanation makes sense, whether focus order matches the user task, whether an error recovery path is humane, or whether screen reader output is understandable in context. The experimental simulation API can help developers inspect announcements, but IBM labels it experimental, so do not build a permanent pass or fail policy around its exact output shape.

The second misconception is that one engine is enough. Different tools encode different rule sets and reporting styles. Equal Access is especially attractive in IBM-flavored compliance environments because it aligns with IBM Accessibility Requirements and IBM_Accessibility policy. Axe, Pa11y, Lighthouse, and manual audits still have roles. Your architecture should make it easy to add another checker without rewriting every test.

The third misconception is that AI coding agents can "fix accessibility" safely in bulk. Agents are helpful at adding labels, replacing invalid ARIA, and creating tests. They can also invent role names, overuse aria-label, remove visible text, or satisfy a scanner while hurting real users. When you ask Claude Code, Cursor, or Copilot to help, give it the failing rule, the DOM snippet, the expected user behavior, and a test that asserts the visible interaction still works.

Operating Model For QA Leads

Make the equal access accessibility checker part of a review loop:

CadenceOwnerOutput
Every pull requestFeature QA or owning engineerGated scan for touched critical states
NightlyQA automationLatest archive report over stable URLs
Sprint reviewQA lead and product ownerBaseline review and fix prioritization
Release candidateAccessibility specialist if availableManual keyboard and assistive technology pass

If your team packages reusable automation routines, ready-made QA skills can install from qaskills.sh with the qaskills CLI. Treat those skills as starting points: review the generated .achecker.yml, labels, and CI thresholds before letting them block releases.

Frequently Asked Questions

Is IBM Equal Access Accessibility Checker still maintained?

Yes. I verified the accessibility-checker npm package at version 4.0.34 and saw recent release automation in the IBMa/equal-access repository for September 2026. The broader toolkit also remains linked from IBM's accessibility tools pages. Maintenance does not remove the need to pin versions. Accessibility rules change, dependencies change, and a green build can become noisy after an unplanned upgrade.

Should I use ruleArchive: latest or ruleArchive: versioned?

Use versioned for pull-request gates when repeatability matters. It ties rule behavior to the tool version, which makes failures easier to reproduce. Use latest in a scheduled discovery job when you want to learn about new or changed rules before they block feature work. Teams under audit often run both: stable gates for development, current-rule reports for planning.

Can the checker replace manual accessibility testing?

No. It can catch many machine-detectable issues, including invalid ARIA, missing text alternatives, language defects, contrast failures, and structural mistakes. It cannot fully validate keyboard strategy, screen reader comprehension, focus recovery, cognitive load, or whether a component is usable in the real task. Use it to remove obvious defects before manual testing starts.

Why did my scan pass with almost no findings?

Check whether the page was actually ready. A scan against a loading shell, empty route, hidden modal, or unauthenticated redirect can pass while testing the wrong thing. Add visible assertions before getCompliance, use stable labels, and inspect the report summary for executed rule count and page URL. A tiny scan of a complex page is usually a setup bug.