This episode covers accessibility testing with Cypress: adding axe-core for accessibility assertions, testing keyboard navigation, ARIA roles, and semantic HTML, understanding accessibility rules, and building accessible test flows.

A web that is not accessible is a web that closes its doors on some users. Episode 17 covers accessibility testing in Cypress: adding axe-core, testing keyboard navigation, ARIA roles, and semantic HTML, understanding accessibility rules, and building test flows that cover accessibility.
Accessibility is not an add-on feature — it is part of quality. Tests that check it keep the application friendly to screen reader users and keyboard navigation.
axe-core is an accessibility analysis engine you can use directly in Cypress:
npm install -D cypress-axe axe-corenpm install -D cypress-axe axe-core adds the cy.checkA11y command and the analysis engine. Make sure it is also imported in cypress/support/e2e.js:
import "cypress-axe";import "cypress-axe" makes the accessibility commands available across all specs.
After the page loads, run the automatic check:
it("halaman login tidak punya pelanggaran a11y", () => {
cy.visit("/login");
cy.checkA11y();
});cy.checkA11y() scans the page and fails if it finds any violations of the axe rules. By default, all WCAG rules are run; failures are shown in full with the elements and fix recommendations.
Users who do not use a mouse rely on the keyboard. Test the tab order and shortcuts:
cy.get("[data-cy=search-input]").focus();
cy.get("body").type("{tab}");
cy.focused().should("have.attr", "data-cy", "search-button");cy.focused() reads the element that currently has focus. The have.attr assertion ensures tab moved focus to the correct button — a real verification of the keyboard navigation order.
Check that interactive elements use the correct roles:
cy.get("[role=dialog]").should("be.visible");
cy.get("[data-cy=close-dialog]").should("have.attr", "aria-label", "Tutup dialog");[role=dialog] ensures the modal is declared as a dialog. aria-label: "Tutup dialog" gives the close button a name readable by screen readers — a required practice for icon elements without text.
Sometimes you want to override or focus on specific rules:
cy.checkA11y(null, {
rules: {
"color-contrast": { enabled: false },
},
});"color-contrast": { enabled: false } disables the color contrast check. Use it selectively — for example when testing components whose contrast is set by the brand — and do not disable it without reason.
The check can be scoped to a specific region of the page:
cy.checkA11y("[data-cy=form-pendaftaran]");cy.checkA11y("[data-cy=form-pendaftaran]") limits the scan to that form. This pattern speeds up the scan and makes failure messages easier to trace.
Make accessibility part of the flow, not a separate test. Add the check after the main page loads:
it("alur pendaftaran dapat diakses", () => {
cy.visit("/daftar");
cy.checkA11y();
cy.get("[data-cy=nama]").type("Arman");
cy.get("[data-cy=email]").type("user@example.com");
cy.checkA11y("[data-cy=form-daftar]");
cy.get("[data-cy=submit]").click();
cy.contains("Pendaftaran berhasil").should("be.visible");
});The cy.checkA11y() checks at the start and in the middle of the flow ensure both the page and the interactions stay accessible. With this pattern, accessibility regressions are caught early, not after release.
Tip
Integrate accessibility checks into the main flows your users visit most often. Two or three key pages that are accessibility-tested are worth more than hundreds of pages that are never checked.
Episode 17 made accessibility part of testing: adding axe-core through cypress-axe, checking keyboard navigation with cy.focused(), validating ARIA roles and semantic HTML, filtering rules as needed, and embedding checks into the main flows.
Key takeaways:
cy.checkA11y() scans the page against WCAG rules.cy.focused() after pressing {tab}.aria-label.In the next episode, episode 18, we will cover custom plugins and extensions — using Cypress plugins and community modules, building custom plugins, extending commands and task APIs, and reusable plugin patterns.