Live
Black Hat USAAI BusinessBlack Hat AsiaAI BusinessFrom False Positives to Real Risk: AI‑Driven Compliance in Modern UC - UC TodayGoogle News: Generative AIClaude Code Leak: What Went Wrong at Anthropic? - AI MagazineGoogle News: ClaudeU.S. Reportedly Seeking Access To Three Additional Bases In Greenland, The First Expansion In DecadesInternational Business TimesAnthropic's Claude Code source code got accidentally leaked - qz.comGoogle News: ClaudeAI’s Biggest Opportunity Lies in the 92% of Work It Hasn’t Touched - PYMNTS.comGoogle News: AIWhy is gaming becoming so expensive? The answer is found in AI - The GuardianGoogle News: AIChoosing the Right Model is Hard. Maintaining Accuracy is Harder.AI YouTube Channel 24A YouTuber channeled his distaste for the PS5’s design into slick console coversThe Verge AILess than a month: StrictlyVC San Francisco brings leaders from TDK Ventures, Replit, and more togetherTechCrunch AIThe Strange, Shaky Alliance Taking on Trump and His Big Tech Friends - PoliticoGoogle News: AI SafetyI Asked ChatGPT If It Was A Psychopath—Here’s What It Said - ForbesGoogle News: ChatGPTGoogle’s TurboQuant Marks A Turning Point In AI’s Evolution - ForbesGoogle News: LLMBlack Hat USAAI BusinessBlack Hat AsiaAI BusinessFrom False Positives to Real Risk: AI‑Driven Compliance in Modern UC - UC TodayGoogle News: Generative AIClaude Code Leak: What Went Wrong at Anthropic? - AI MagazineGoogle News: ClaudeU.S. Reportedly Seeking Access To Three Additional Bases In Greenland, The First Expansion In DecadesInternational Business TimesAnthropic's Claude Code source code got accidentally leaked - qz.comGoogle News: ClaudeAI’s Biggest Opportunity Lies in the 92% of Work It Hasn’t Touched - PYMNTS.comGoogle News: AIWhy is gaming becoming so expensive? The answer is found in AI - The GuardianGoogle News: AIChoosing the Right Model is Hard. Maintaining Accuracy is Harder.AI YouTube Channel 24A YouTuber channeled his distaste for the PS5’s design into slick console coversThe Verge AILess than a month: StrictlyVC San Francisco brings leaders from TDK Ventures, Replit, and more togetherTechCrunch AIThe Strange, Shaky Alliance Taking on Trump and His Big Tech Friends - PoliticoGoogle News: AI SafetyI Asked ChatGPT If It Was A Psychopath—Here’s What It Said - ForbesGoogle News: ChatGPTGoogle’s TurboQuant Marks A Turning Point In AI’s Evolution - ForbesGoogle News: LLM

Accessible web testing with Cypress and wick-a11y

DEV Communityby Vitaly SkadorvaApril 1, 202616 min read0 views
Source Quiz

<p>I spent a couple of hours building a custom logging callback for cypress-axe. It formatted violations into a console table and registered Cypress tasks in the config file. It worked. Then I installed wick-a11y and got better output with zero custom code.</p> <p>This is the second article in my accessibility testing series. The first one covered Cypress with cypress-axe, and you can find it here:<br> </p> <div class="ltag__link--embedded"> <div class="crayons-story "> <a href="https://dev.to/cypress/accessible-web-testing-with-cypress-and-axe-core-1af9" class="crayons-story__hidden-navigation-link">Accessible web testing with Cypress and Axe Core</a> <div class="crayons-story__body crayons-story__body-full_post"> <div class="crayons-story__top"> <div class="crayons-story__meta"> <div cla

I spent a couple of hours building a custom logging callback for cypress-axe. It formatted violations into a console table and registered Cypress tasks in the config file. It worked. Then I installed wick-a11y and got better output with zero custom code.

This is the second article in my accessibility testing series. The first one covered Cypress with cypress-axe, and you can find it here:

Sebastian Clavijo Suero (@sebastianclavijo) built wick-a11y on top of the same axe-core engine that powers cypress-axe. The difference is what happens after the scan. Where cypress-axe gives you a raw violations array and leaves the rest to you, wick-a11y processes the results into color-coded Cypress logs with visual highlighting in the runner, plus HTML reports with annotated screenshots and fix guidance. All of it works out of the box.

What wick-a11y adds over cypress-axe

I've been using cypress-axe in my project for some time, and it works well. However, there is a gap between when "axe-core detects violations" and when "the team understands what needs to be fixed." To address this, I created a terminalLog callback and implemented custom severity filtering. I've noticed that many teams do something similar to this.

The API itself shows the gap. cypress-axe uses positional arguments: cy.checkA11y(context, options, violationCallback, skipFailures). When you only need the skip flag, you end up writing cy.checkA11y(null, null, terminalLog, true). Three nulls to reach the one parameter you care about. wick-a11y uses cy.checkAccessibility(context, options) with a clean options object. Everything goes in one place.

wick-a11y closes that gap with features that come built in. It uses cy.checkAccessibility() instead of cy.checkA11y(), and here's what you get without writing any custom code:

Violations appear in the Cypress log grouped by severity, with a count summary (critical, serious, moderate, minor). Hovering over a violation in the log highlights the affected DOM element on the page with a color-coded outline. Clicking it prints full details to the browser console, including the WCAG rule and fix guidance.

HTML reports are generated automatically for every test that finds violations. Each report includes violation details grouped by severity and a screenshot highlighting the affected elements. The reports themselves are mostly WCAG 2.2 AAA compliant and support full keyboard navigation. An accessibility testing tool whose own reports are accessible is a detail Sebastian clearly thought about.

There's also a voice feature. When enabled, wick-a11y reads violation summaries aloud at the suite, test, violation type, and individual element levels. Sebastian added this because many people testing accessibility have disabilities themselves. The Cypress runner is not particularly accessible, and this gives auditory feedback for people who need it.

The official Cypress blog featured wick-a11y in their Open Source Accessibility Plugins in Cypress post, describing it as adding visual, reporting, and voice dimensions to the cypress-axe plugin.

Installation and setup

Two packages (wick-a11y pulls in cypress-axe as a dependency):

npm install --save-dev wick-a11y axe-core

Enter fullscreen mode

Exit fullscreen mode

Add the accessibility tasks to your cypress.config.js:

const addAccessibilityTasks = require('wick-a11y/accessibility-tasks');

module.exports = defineConfig({ e2e: { setupNodeEvents(on, config) { addAccessibilityTasks(on); }, }, });`

Enter fullscreen mode

Exit fullscreen mode

Import the custom commands in cypress/support/e2e.js:

import 'wick-a11y';

Enter fullscreen mode

Exit fullscreen mode

That's it. No terminalLog callback, no task registration for log and table, and no custom severity filtering code. The plugin manages everything.

HTML reports are saved to cypress/accessibility by default. You can change this:

module.exports = defineConfig({  accessibilityFolder: 'cypress/your-reports-folder', });

Enter fullscreen mode

Exit fullscreen mode

Basic usage patterns

Full page scan

it('home page has no detectable violations', () => {  cy.visit('/');  cy.injectAxe();  cy.checkAccessibility(); });

Enter fullscreen mode

Exit fullscreen mode

When I first ran this on my weather app, the Cypress log showed a severity summary at the top: 3 critical, 1 serious, 2 moderate, 1 minor. Each violation is listed below, along with the affected elements. On the page itself, every element with a violation had a colored outline matching its severity. Red for critical, blue for minor, and the rest in between.

Scope to an element

cy.checkAccessibility('[role="dialog"]'); cy.checkAccessibility('form');

Enter fullscreen mode

Exit fullscreen mode

Filter by severity

cy.checkAccessibility(null, {  includedImpacts: ['critical', 'serious'], });

Enter fullscreen mode

Exit fullscreen mode

Warn without failing

This is the feature I was building manually with the skipFailures flag in cypress-axe. The difference: cypress-axe's skipFailures is a boolean. Either all violations fail the test, or none do. wick-a11y has onlyWarnImpacts, which lets you pick which severity levels to warn without failing:

cy.checkAccessibility(null, {  onlyWarnImpacts: ['moderate', 'minor'], });

Enter fullscreen mode

Exit fullscreen mode

This keeps the pipeline green while still surfacing moderate and minor issues in the Cypress log and HTML report. The test fails only for critical and serious violations. With cypress-axe, you'd have to build that filtering yourself.

Filter by WCAG tags

wick-a11y supports WCAG 2.2 at all three levels (A, AA, AAA):

cy.checkAccessibility(null, {  runOnly: {  type: 'tag',  values: ['wcag2a', 'wcag2aa'],  }, });

Enter fullscreen mode

Exit fullscreen mode

Custom severity styles

You can override the default highlight colors for each severity level:

cy.checkAccessibility(null, {  impactStyling: {  critical: { icon: '🔴', style: 'fill: #E24B4A; stroke: #E24B4A;' },  serious: { icon: '🟠', style: 'fill: #EF9F27; stroke: #EF9F27;' },  }, });

Enter fullscreen mode

Exit fullscreen mode

Control HTML report generation

// detailed report (default) cy.checkAccessibility(null, { generateReport: 'detailed' });

// basic report (smaller file, no JavaScript) cy.checkAccessibility(null, { generateReport: 'basic' });

// no report cy.checkAccessibility(null, { generateReport: 'none' });`

Enter fullscreen mode

Exit fullscreen mode

Example 1: E2E test with real API calls

describe('E2E: city search + accessibility', () => {  beforeEach(() => {  cy.visit('/');  cy.injectAxe();  });

it('checks accessibility after a search', () => { cy.intercept('/search?q=').as('search'); cy.intercept('/weather?').as('weather');

cy.get('[data-cy="city-selector"]').type('Toronto'); cy.wait('@search').then((interception) => { expect(interception.response.statusCode).to.eq(200); });

cy.get('#city-select').select(1); cy.wait('@weather').then((interception) => { const cityName = interception.response.body.name; cy.get('[data-cy="weather-display"]') .should('be.visible') .and('contain', cityName); });

cy.checkAccessibility(null, { onlyWarnImpacts: ['moderate', 'minor'], }); }); });`

Enter fullscreen mode

Exit fullscreen mode

After the test runs, wick-a11y generates an HTML report in cypress/accessibility that groups violations by severity. The screenshot at the bottom of the report shows my weather app with the select dropdown highlighted in red (critical: no accessible name) and several text elements outlined in orange (serious: contrast ratio). I sent that report to the team, and they fixed the dropdown label within the hour. With cypress-axe, I would have had to explain the console output. The report explained itself.

Example 2: Scoped checks for modals and dynamic content

describe('Weather app modal', () => {  beforeEach(() => {  cy.visit('http://localhost:8080/');  cy.injectAxe();  });

it('checks modal accessibility', () => { cy.contains('button', 'Open Modal').click(); cy.checkAccessibility('[data-cy="modal-overlay"]', { onlyWarnImpacts: ['moderate', 'minor'], }); cy.get('[data-cy="modal-overlay"]').should('be.visible'); }); });`

Enter fullscreen mode

Exit fullscreen mode

Hovering over a violation in the Cypress log highlights the affected element in the runner. I found a close button with no accessible name this way. In the runner, the button lit up red when I hovered over the label violation in the log. That visual connection between the log entry and the actual element on the page is something I never had with cypress-axe.

Example 3: Keyboard navigation with cy.press()

wick-a11y handles the axe-core scan. For keyboard behavior, you still need cy.press() (Cypress 14+):

describe('Modal keyboard navigation', () => {  beforeEach(() => {  cy.visit('/');  cy.injectAxe();  });

it('traps focus inside the modal and closes on Escape', () => { cy.contains('button', 'Open Modal').click(); cy.get('[data-cy="modal-overlay"]').should('be.visible');

cy.focused().should('have.attr', 'data-cy', 'modal-close-btn');

cy.press('Tab'); cy.focused().should('be.within', cy.get('[data-cy="modal-overlay"]'));

cy.press('Escape'); cy.get('[data-cy="modal-overlay"]').should('not.exist');

cy.focused().should('contain', 'Open Modal');

cy.checkAccessibility(null, { onlyWarnImpacts: ['moderate', 'minor'], }); }); });`

Enter fullscreen mode

Exit fullscreen mode

This test evaluates four aspects that axe-core cannot check: 1) whether focus moves into the modal when it opens, 2) whether the Tab key does not allow focus to escape the modal boundary, 3) whether the Escape key closes the dialog, and 4) whether focus returns to the button that triggered the modal. All of these elements are requirements of WCAG 2.4.3 (Focus Order).

Example 4: Storybook integration

describe('Storybook: CitySelector', () => {  beforeEach(() => {  cy.visit(  'http://localhost:6006/iframe.html?id=components-cityselector--default'  );  cy.injectAxe();  });

it('has no accessibility violations', () => { cy.checkAccessibility(null, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'], }, }); }); });`

Enter fullscreen mode

Exit fullscreen mode

Each story gets its own HTML report. I started running these for our shared component library after a dropdown component shipped to multiple applications without an accessible name. The wick-a11y reports made it easy to share findings with the team because the screenshots showed exactly which elements failed and why.

Example 5: Component tests

import CitySelector from './CitySelector';

describe('', () => { beforeEach(() => { cy.injectAxe(); });

it('renders with no critical violations', () => { cy.mount(); cy.checkAccessibility(null, { includedImpacts: ['serious', 'critical'], }); cy.get('[data-cy="city-selector"]').should('be.visible'); }); });`

Enter fullscreen mode

Exit fullscreen mode

If the label is missing here, it will be missing everywhere the component is used. wick-a11y's HTML report for component tests includes the same annotated screenshot and fix guidance as the E2E reports, so the developer working on the component can see the problem and the suggested fix in a single document.

cypress-axe vs wick-a11y: when to use which

Both use the same axe-core engine underneath. The scan results are the same, but the differences lie elsewhere.

cypress-axe gives you the raw results and leaves the presentation to you. You write the logging callbacks, register the tasks, and build your own reporting. If you want minimal dependencies and full control, it works. But you'll write and maintain custom code that wick-a11y provides out of the box.

wick-a11y gives you color-coded Cypress logs, visual highlighting in the runner, HTML reports with annotated screenshots, and voice feedback. If your team needs to share findings with designers or product managers who don't read terminal output, the built-in reports save real time.

A note on cypress-axe maintenance

One thing worth knowing: cypress-axe caps some features that axe-core supports natively. Axe-core returns "incomplete" results for checks it can't fully confirm, such as certain color-contrast situations that require manual review. cypress-axe filters those out before they reach your test. Sebastian raised this as issue #191 in October 2025. As of this writing, it's still open with no response.

The maintenance gap goes beyond that one issue. The cypress-axe new API RFC (issue #75) has been open since November 2020 with community demand but no implementation. Most recent activity has been version bumps to keep pace with new Cypress releases. By contrast, wick-a11y has had over 14 releases in just over a year. Version 2.2.0 shipped for Cypress 15 within ten days of the Cypress release. Version 3.0.1 is the latest as of March 2026. Sebastian tracks Cypress major versions and publishes compatibility updates fast.

Sebastian's plan is to remove the cypress-axe dependency from wick-a11y and use axe-core directly. Since cypress-axe is a thin wrapper over axe-core with one additional filtering feature that wick-a11y already bypasses for its warnings system, the change should be transparent to users.

Resources

  • wick-a11y on GitHub

  • wick-a11y on npm

  • Sebastian's introductory article on dev.to

  • wick-a11y video tutorial

  • Open Source Accessibility Plugins in Cypress (official Cypress blog)

  • Axe Core documentation (Deque)

  • WCAG 2.2 quick reference (W3C)

  • Companion article: Cypress + cypress-axe version

The code examples in this article use a weather app I built for testing. My book Ultimate Web Automation Testing with Cypress covers the Cypress framework end to end.

Find me on LinkedIn.

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by AI News Hub · full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

More about

releaseversionupdate

Knowledge Map

Knowledge Map
TopicsEntitiesSource
Accessible …releaseversionupdateopen sourceopen-sourceproductDEV Communi…

Connected Articles — Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Knowledge Graph100 articles · 215 connections
Scroll to zoom · drag to pan · click to open

Discussion

Sign in to join the discussion

No comments yet — be the first to share your thoughts!

More in Releases