illustration of two developers with cypress and axe logos

How to test for accessibility with Cypress

Cypress is a complete end-to-end testing tool. It reduces complexity by offering an all-inclusive testing platform, rather than requiring you to select and piece together individual libraries.

Creating, writing, running, and debugging becomes a simple, trivial process with Cypress. A few key features to call out are time travel (timestamped snapshots at each step), real-time reloading, automatic waiting, and screen & video captures. The best part is all of these features and more are available in the open source package.

In this article, we’re going to discuss how to:

  1. Create test cases in Cypress
  2. Integrate and use axe to check for accessibility violations
  3. Enhance accessibility tests

Creating Test Cases

In this post, I will be focussing on testing the login form for my app. I want to ensure that my login form is functional before creating a release build. Cypress tests can be used to verify the correct classes, IDs, elements, etc. We can also simulate user actions such as clicks, drags, drops, hovers, etc. We won’t need to get too sophisticated with this form, but just know that these tests are available if you need them.

Starting off, we need to create a file which will contain all of our test case logic. In the project directory, I created a file at this location –  `./cypress/integration/login.js` .

Next, I constructed some simple conditions for a successful test suite. Nothing too fancy, but this will help cover the basics and ensure that my application remains functional every time I make a build.

// ./integration/Login.js

describe('Login', function () {

    it('Should load the correct URL', function () {});
    
    it('Has a valid login form.', function () {});

    it('Should display an error message after login failure.', function () {});

    it('Should redirect to the dashboard after login success.', function () {});

});

After a short roast in the easy-bake-oven and diligent documentation browsing, we end up with a solid set of test cases.

// ./integration/Login.js

describe('Login', function () {

    before( function () {
        cy.visit('http://localhost:9999/');
    });

    it('Should load the correct URL', function () {
        cy.url().should('eq', 'http://localhost:9999/#/login');
    });
    
    it('Has a valid login form.', function () {
        cy.get('form').within(() => {
            cy.get('input#email').should('be.visible');

            cy.get('input#password').should('be.visible');

            cy.get('button').should('be.visible');
        });
    });

    it('Should display an error message after login failure.', function () {
        cy.get('input#email').type('fail@test.com');

        cy.get('input#password').type('swordfish');

        cy.get('button').click();

        // wait for the server to respond, then test for the error
        cy.wait(1000)
            .get('div.alert')
            .should('be.visible');

        
    });

    it('Should redirect to the dashboard after login success.', function () {
        cy.get('input#email')
            .clear()
            .type('test@test.com')
            .should('have.value', 'test@test.com');

        cy.get('input#password')
            .clear()
            .type('123@123')
            .should('have.value', '123@123');

        cy.get('button').click();

        cy.wait(1000)
            .url().should('eq', 'http://localhost:9999/#/');
    });

});

Integrating and using axe

Great! Everything is working. Now, with a few simple additions, we can increase the amount of coverage that our test suite performs. We can also automate the accessibility testing process and capture a huge chunk of common and easy to address accessibility issues of the Web Content Accessibility Guidelines (WCAG) without even breaking a sweat. Let’s dive in!

I’ll assume you’ve heard of axe-core– it’s kind of a big deal as it’s the most widely used open source accessibility rules library. If not, head over here and check out what it’s all about: axe – Accessibility for Development Teams.

An awesome web citizen named Andy Van Slaars did all of the heavy lifting for an axe + Cypress integration. Now, all we have to do is install the plugin and fire off the commands to test for accessibility.

Cypress-axe – npm

First, we install the package using NPM or Yarn.
`npm i cypress-axe` or `yarn add cypress-axe`

Then, follow the documentation to integrate into your Cypress test cases.

In my example, I am using the `before()` hook load the URL for the login page. This is a good spot for me to inject the axe-core library. I can do that using the `cy.injectAxe()` command.

Now I can place the `cy.checkA11y()` command in various locations of my test script to validate or expose accessibility violations.

To really make use of the violation results, you’re going to need to toggle the developer tools in the Cypress test runner window. Once you have the dev tools console open, you can get a bit more detail about what the issues are, why they are issues, and how to resolve them.

Here is the completed test logic, with axe-core integration.

// ./integration/Login.js

describe('Login', function () {

    before( function () {
        cy.visit('http://localhost:9999/');

        // Inject the axe-core library
        cy.injectAxe();
    });

    it('Should load the correct URL', function () {
        cy.url().should('eq', 'http://localhost:9999/#/login');
    });
    
    it('Has a valid login form.', function () {
        cy.get('form').within(() => {
            cy.get('input#email').should('be.visible');

            cy.get('input#password').should('be.visible');

            cy.get('button').should('be.visible');
        });

        // first a11y test
        cy.checkA11y();
    });

    it('Should display an error message after login failure.', function () {
        cy.get('input#email').type('fail@test.com');

        cy.get('input#password').type('swordfish');

        cy.get('button').click();

        // wait for the server to respond, then test for the error
        cy.wait(1000)
            .get('div.alert')
            .should('be.visible');

        // test a11y again, but only the alert container.
        cy.checkA11y('div.alert');
    });

    it('Should redirect to the dashboard after login success.', function () {
        cy.get('input#email')
            .clear()
            .type('test@test.com')
            .should('have.value', 'test@test.com');

        cy.get('input#password')
            .clear()
            .type('123@123')
            .should('have.value', '123@123');

        cy.get('button').click();

        cy.wait(1000)
            .url().should('eq', 'http://localhost:9999/#/');
    });

});

As a developer or QA engineer, this has greatly increased my ability to find and resolve accessibility issues within the end-to-end testing process. We now have a singular test suite which will inform us when our application is not doing what we expect it to do or has accessibility violations. Because we’re using the axe-core rules engine, the accessibility violations will contain an explanation of why they failed and offer notes on how we should remediate the issue.

Enhanced Accessibility Testing

Worldspace Attest is the next step to take for a more scalable, automated accessibility testing initiative. The Attest tool offers customized rulesets, advanced reporting, enterprise support, and more.

We may find the need to have easily-digestible reporting on hand after an automated run. The Attest Reporter will allow us to produce HTML reports that are easy-to-read and offer violation remediation notes.

At the time of this article, there isn’t an official Cypress integration. Not to worry, however, because I’ve put together a sample integration that can be used as a starting point.

Click here to view the cypress-attest repository!

Assuming you already have Attest installed in your project and have also installed the cypress-attest repo, you simply need to swap out `cy.injectAxe()` with the `cy.injectAttest()`. Your before block should look something like this…

before( function () {
    cy.injectAttest();
});

Now we can test for accessibility and produce the reporting we need using the following code.

cy.checkA11y();

The function checkA11y() allows you to define the reporting options. For instance, if I wanted to include specific naming conventions for the page and component, I can simply use the following options:

cy.checkA11y({
    reportName: 'Login',
    scopeName: 'Entire Page'
});

Since I have supplied the report naming options when I run the test suite I will receive an HTML reports located on my project’s root directory – `./cy-a11y-results/Login-Entire Page-{timestamp}.html`. This will allow me to view the reports of an automated test run.

Here is what our login test suite looks like at the end of the day:

describe('Login', function () {

    before( function () {
        cy.visit('http://localhost:9999/');
        cy.injectAttest();
    });

    it('Should load the correct URL', function () {
        cy.url().should('eq', 'http://localhost:9999/#/login');
        cy.checkA11y({
            reportName: 'Login',
            scopeName: 'Entire Page'
        });
    });
    
    it('Has a valid login form.', function () {
        cy.get('form').within(() => {
            cy.get('input#email').should('be.visible');

            cy.get('input#password').should('be.visible');

            cy.get('button').should('be.visible');
        });
    });

    it('Should display an error message after login failure.', function () {
        cy.get('input#email').type('fail@test.com');

        cy.get('input#password').type('swordfish');

        cy.get('button').click();

        // wait for the server to respond, then test for the error
        cy.wait(1000)
            .get('div.alert')
            .should('be.visible');
    });

    it('Should redirect to the dashboard after login success.', function () {
        cy.get('input#email')
            .clear()
            .type('tester')
            .should('have.value', 'tester');

        cy.get('input#password')
            .clear()
            .type('123@123')
            .should('have.value', '123@123');

        cy.get('button').click();

        cy.wait(1000)
            .url().should('eq', 'http://localhost:9999/#/');
    });

});

With a small amount of effort, we’re able to increase coverage within test suites and deliver advanced reporting of accessibility test results which can tell us what the violations are, why they are violations, and how to fix them. This could potentially save us from spending numerous hours and non-trivial effort to find and fix front-end and accessibility issues.

If accessibility test coverage is a priority, consider starting with axe-core. This will help cover the immediate needs and pave your way to a more sophisticated approach: Attest. The Attest tool does this by arming you with customized rules, advanced reporting, and technical support for those advanced integration cases that have you stumped.

Photo of Josh McClure

About Josh McClure

Josh helps organizations discover technical solutions to challenging business problems. During his career, he has worn many hats and played various parts on product engineering teams to help fill the gaps. Some of his roles include: developer, consultant, devops engineer, project manager, and sales engineer. Apart from work, Joshua enjoys making music, hiking, camping, and kicking back with his family.
update selasa

Comments 12 responses

  1. Hi Josh. This is a really helpful article. Thank you for sharing. I am interested in `Enhanced Accessibility Testing` and I tried to implement it, however, I got some error. If you have any repository to see the whole code, it can be perfect.
    Thank you in advance.

  2. @Eyup Aydin, I’m glad you’re finding this helpful! If you aren’t a paying WorldSpace Attest customer, you’ll probably encounter an error attempting to install the Attest package. I am assuming this is where you are seeing the error, but let me know if my assumption is incorrect. Thanks!

  3. I’ve installed the Axe plugin, but when I run the test I get

    “TypeError: cy.injectAxe is not a function

    Because this error occurred during a ‘before all’ hook we are skipping the remaining tests in the current suite: ‘filling in forms test'”

    It said it was installed, do I need to add some custom commands to recognize Axe ? Or? What can I check?

  4. @Catherine Anderson-Karena – Did you also install `cypress-axe`? It sounds like that package may be missing.

  5. How did you get it to report actual problems encountered by checka11y? I’m getting things like “1) Accessibility Tests Global Component Assessments Desktop Assessments Petition Petition form should be accessible.: AssertionError: 1 accessibility violation was detected: expected 1 to equal 0” which literally tells me nothing about what’s wrong, let alone how to fix it =/

  6. Can we specify the accessibility standard we want the test to run for?
    Ex:
    WCAG2A
    WCAG2AA
    WCAG2AAA

    And what are the differences between this and using Pally tool for accessibility testing? Thnx

  7. Hi,

    I would love to use Cypress and Axe to perform Accessibility testing on our app. But is it feasible ? The current approach requires to do a cy.visit(myURL) first before injecting Axe. The problem is with my web APP is that I don’t have proper URL for each page. You get to each page through clicks on different menus.

    Can this still work somehow, is there any workaround ?

    Thanks
    Kevin

Leave a Reply

Your email address will not be published. Required fields are marked *