Search code examples
reactjsreact-testing-library

React Testing Library Accessible Name


I am confused, isn't the content of the element considered as part of it's accessible name, why does the below test fail to find the alert with the text?

import React from "react";
import "@testing-library/jest-dom/extend-expect";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <div>{count}</div>
      <div role="alert">Please wait</div>
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  );
}

test("renders alert", async () => {
  render(<Counter />);
  const loadingScreen = await screen.findByRole("alert", {
    name: /Please wait/i
  });
  expect(loadingScreen).toBeInTheDocument();
});

Solution

  • From the Accessible name calculation documentation, the div element in your case matches the last rule.

    Other elements The title attribute.

    Why? Because the div element with the alert role does not support getting their name from the content they contain. For example, the following link is named "Home".

    <a href="/">Home</a>
    

    When assistive technologies render an element that gets its accessible name from its content, such as a link or button, the accessible name is the only content the user can perceive for that element.

    Note: Not all accessible names of element and role are the text content of the element!

    Elements having one of the following roles are, by default, named by a string calculated from their descendant content. See the full list Roles Supporting Name from Content

    Two choices:

    1. Use the title attribute
    <div role='alert' title='Please wait'>
        Please wait...
    </div>
    
    1. Use the aria-label attribute
    <div role='alert' aria-label='Please wait'>
        Please wait...
    </div>
    

    Below is a test example, we can create a div element with a 'heading' role, and find it by role and its accessible name(the text content of it).

    import '@testing-library/jest-dom';
    import { render, screen } from '@testing-library/react';
    import React from 'react';
    
    function Counter() {
        const [count, setCount] = React.useState(0);
        return (
            <div>
                <div>{count}</div>
                <div role='alert' aria-label='Please wait'>
                    Please wait...
                </div>
                <div role='heading'>An example heading</div>
                <button onClick={() => setCount(count + 1)}>click me</button>
            </div>
        );
    }
    
    test('renders alert', async () => {
        render(<Counter />);
        const loadingScreen = await screen.findByRole('alert', {
            name: /Please wait/i,
        });
        expect(loadingScreen).toBeInTheDocument();
    
        const heading = await screen.findByRole('heading', { name: /An example heading/i });
        expect(heading).toBeInTheDocument();
    });
    

    Test result:

     PASS  stackoverflow/76800902/index.test.tsx
      ✓ renders alert (54 ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        1.289 s, estimated 2 s
    

    package versions:

    "@testing-library/react": "^11.2.7",
    "react": "^16.14.0",