How to test the new ARIA Notify API with Cypress

September 23, 2025

By Mark Noonan

The ARIA Notify API is a new way to communicate live page updates to assistive technology like screen readers. This helps make interactive web applications more accessible and solves some problems with current workarounds.

In this blog post we'll explain what this new API does and how to test it using Cypress. We'll also end up other Cypress tips and tricks that come in handy for this kind of testing, and do a line-by-line explanation of the Cypress code.

Here's a full list of topics this post will touch on:

  • How ariaNotify works
  • Testing a local HTML file (no server needed)
  • Using a beta version of Chrome for testing
  • How to stub a method on a specific HTML element
  • How to quickly access stubbed method through a Cypress alias to test it after some user actions
  • How to control time with cy.clock() to keep our test fast even when we simulate a slow server response
  • How to trigger native keyboard events to test a button

How developers communicate state today

Let's take an example based on adding an item to to shopping cart. There are a few essential things that need to happen when a user clicks an "Add to cart" button:

  1. A request must be sent to the server to update the current state of the cart, and possibly update inventory or other records in the process. This is the actual action of the button.
  2. Some confirmation that the cart has been updated is shown to the user after the operation is completed. This is often subtle, like a number appearing next to a cart icon that indicates how many items are in the cart.
  3. Since server requests can be slow, there's often also some "pending" state built in to avoid accidentally adding too many items through double clicks, or just user uncertainty. This might look like flipping the "Add to cart" button into a disabled state until the item has been successfully added, or showing something like a loading spinner in place of the button.

To communicate this type of live status change to anybody who is using a screen reader to listen to the page content, developers today must use ARIA live regions. These are dedicated DOM elements that trigger announcements in screen readers. Elements with roles like status or alert are useful for this, as updates to the content of these elements will be automatically announced to screen readers. Still, the more complex an application is, the harder it is to manage updates through DOM elements, especially when there are multiple asynchronous actions happening close together.

Interactions like this can be broken at the best of times

While I've described the availability of live regions, my experience is that in reality these kinds of state changes are often totally forgotten about when it comes to accessibility, reducing the independence of disabled users completing critical tasks like making purchases.

I see states like this flagged regularly when walking through reports in Cypress Accessibility with Cypress Cloud customers. Often the reported issue is that some important control like a button has no label, because it had its text replaced with a loading indicator after an interaction. This technique provides visual feedback, but creates a blocker for visually impaired users. So it is definitely worth paying attention to these key state in your workflows, and maintain clarity and accuracy in the interface for all users, regardless of the technology they use.

Hopefully having a new built-in API will make it easier to do the right thing when issues are present, because the mental model is slightly easier to work with, and browsers can handling queueing and prioritizing messages. Screen reader support seems to be good for ariaNotify, with reported support in NVDA and JAWS , plus my own testing worked in VoiceOver on MacOS with Chrome Beta.

Example: How ariaNotify works

The ariaNotify API allows developers to use JavaScript to dispatch an event from any element in the DOM, or the document object itself, to announce a user-facing state change.

Live regions aren't going anywhere, but they will no longer be the only option for this kind of work, which means some workarounds used today can be avoided. In our shopping cart example, we'll avoid DOM manipulation and programmatically announce the updates when they happen, taking advantage of some automatic behaviors like language detection and notification queueing. You can read more about the details of ARIA Notify. in this blog post by Evelynn Kaplan and Patrick Brosset: Creating a more accessible web with Aria Notify.

Here is some example HTML to render the button we will test:

<button onclick="handleClick('Added item to cart', this)">
  Add to cart
</button>

We use onclick on the button to call a handler function that receives a message and a reference to the element itself.

function handleClick(message, element) {
  element.ariaNotify('Adding item to cart...')
  element.disabled = true
  
  setTimeout(() => {
    element.ariaNotify(message);
    element.disabled = false
  }, 2000);
}

When the handler is called, it will simulate the state changes mentioned above. First it will immediately update the button to a disabled state and announce that the item is being added. This lets the user know the input was received and everything is working. Then after a two-second delay, an update confirming the "added item to cart" success state will be sent.

Note: We're calling element.ariaNotify instead of document.ariaNotify because this allows the screenreader to use the surrounding DOM context to detect the correct language for the content being announced. Later when we want to stub this function, it's important to understand where it's going to be called.

Testing ARIA Notify in Cypress

To test the API, we must be in a browser that supports it, or our code must use an ariaNotify polyfill. The ariaNotify API will be live in Chrome 141, and is currently available in the beta release of that version. It has also been available earlier this year through a Microsoft Edge Origin trial. We'll test this in Chrome Beta.

Since Cypress automatically detects installed browsers, if you download and install Chrome Beta, it should appear for you as an available option when you open Cypress, after you choose a testing type:

The Cypress App's "Choose a browser" screen. "Chrome Beta" is highlighted. Chrome, Electron, and Firefox are also displayed.

We're going to build and run the following end-to-end test. I'll explain each line, but if you just want to see the code, here it is!

it('passes', () => {
    // ARRANGE
    cy.visit('cypress/e2e/example-pages/buttons.html')

    cy.clock()

    cy.contains('button', 'Add to cart')
      .then($el => {
        cy.stub($el[0], 'ariaNotify')
          .as('myAriaNotifyStub')
      })

    // ACT
    cy.contains('button', 'Add to cart')
      .focus()
  
    cy.press(Cypress.Keyboard.Keys.SPACE)

    // ASSERT
    cy.get('@myAriaNotifyStub')
      .should('have.been.called.with', 'Adding item to cart...')

    cy.tick(2000)
      
    cy.get('@myAriaNotifyStub')
      .should('have.been.called.with', 'Added item to cart')
  })

Line-by-line explanation

Step 1: Visit the page

The first line of this test uses the cy.visit() to load a HTML file from my file system. You don't need a server running or any framework for something basic like this, Cypress can just serve up the page directly.

cy.visit('cypress/e2e/example-pages/buttons.html')

Step 2: Set up the clock

Next, we call cy.clock() because I know we're testing a process with a simulated 2-second delay, and I don't want the test to have to wait for that delay every time it runs. This command lets use take control of the movement of time from the test and manually wind the clock forward when needed.

cy.clock()

Step 3: Stub the ariaNotify method on an element

We'll take the next four lines as a group. First I want to find the "Add to cart" button in the page, and my preferred way to do this is with cy.contains.

Once we've selected the button and gotten a reference to it with .then(), we call cy.stub(), passing it the element reference and and the ariaNotify method name. This replaces the ariaNotify method on this element with an empty function and lets us test whether it's called or not.

Finally, we use the as() command to get an easy reference to the stub for later – in this case, I'm naming the stub "myAriaNotifyStub", since this can be any name we choose. This will create a Cypress alias.

cy.contains('button', 'Add to cart')
  .then($el => {
    cy.stub($el[0], 'ariaNotify')
      .as('myAriaNotifyStub')
  })

Step 4: Activate the button

Now we're all set up. Time to act on the button and see what happens when the click handler is called. There are three ways to trigger the click event on a standard HTML button: mouse click, spacebar, and enter.

In Cypress version 15.1.0 we got increased support for native keypress events, so let's use one of those.

First we need to make sure the element has keyboard focus. We'll use cy.contains() to find a button element with the correct text, and then call cy.focus().

cy.contains('button', 'Add to cart')
      .focus()

After this, we can use cy.press() to send a spacebar keypress to the browser. This command is different than other Cypress interaction commands in that it doesn't run against a specific element. The keypress events will be received by whatever element has keyboard focus at the time.

Cypress provides a set of constants for the names of keys, so we'll use that here too:

cy.press(Cypress.Keyboard.Keys.SPACE)

The browser is smart enough to run the click hander in response to the spacebar being pressed on a button, so now the item has been added to the cart!

Step 5: Test the immediate feedback – the action was started

We expect that action to kick off a chain of events over the next couple of seconds.

First let's confirm that our notification stub was called inform the user that the item is in the process of being added to the cart. This provides immediate feedback that the click worked as intended and that something is happening. We can do this using the cy.get() command and the alias we created earlier.

cy.get('@myAriaNotifyStub')
  .should('have.been.called.with', 'Adding item to cart...')

If this passes, it means our application code called the correct function with the correct message.

Step 6: Test the delayed feedback – the action was successfully competed

Next we want to confirm that after the item is successfully added to the cart, there is a message about that too. We know that will take two seconds, and because we are controlling time in the browser with cy.clock(), we can move time forward by that about of time using cy.tick(). Now our test can finish in under 200 milliseconds, but still correctly simulate the expected timing conditions.

cy.tick(2000)

To finish of our test, we can make another assertion against our stubbed method, this time expecting the message to match the "added to cart" state.

cy.get('@myAriaNotifyStub')
  .should('have.been.calledWith', 'Added item to cart')

The finished test

Here's how the completed test looks in Cypress. The left-hand panel shows the log of commands, the middle panel shows our example HTML page, and the right hand panel is the Cypress Studio text editor, showing the underlying test code. We can see the test executed in 172 ms.

The three panel Cypress UI, showing a command log with the list of test steps that were carried out, plus the page under test, and the text editor panel with the test code on the right.

Takeaways

The ARIA Notify API is easily testable and easier to use than patterns that put all state updates into DOM manipulation of live regions. Announcing state changes in an orderly way should lead to improved experience for users with disabilities in modern interactive applications.

Our example test uses a lot of Cypress concepts that are useful in day-to-day test creation. You don't always need to take over the browser's clock or stub element methods, but sometimes these are the exact right solutions for a reliable, precise, and performant test.

All of these techniques all work perfectly with upcoming Cypress features like cy.prompt(), which introduces self-healing, natural-language capabilities for creating test steps. Mixing and matching advanced techniques with resilient plain language prompts for locating elements and performing user actions will help you create robust tests that are still able to get into the weeds when needed.

To learn more about accessibility testing in Cypress, you can check out our guide to accessibility testing in the open source Cypress App, to learn about plugins and testing techniques you can implement on your own.

You can also set up a free trial of the automatic accessibility reporting in Cypress Cloud. This provides automatic accessibility reports on all of the states your application reaches during test runs, broken out by page or component, with a range of analytics and debugging capabilities to help you scale your accessibility automation efforts without impacting your tests.