Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

findByRole doesn't find the role #1248

Open
Nexucis opened this issue Nov 9, 2023 · 10 comments
Open

findByRole doesn't find the role #1248

Nexucis opened this issue Nov 9, 2023 · 10 comments

Comments

@Nexucis
Copy link

Nexucis commented Nov 9, 2023

  • @testing-library/react version: 14.1.0
  • Testing Framework and version:
    • Jest: 29.5.0
  • DOM Environment:

Relevant code or config:

https://github.com/perses/perses/blob/nexucis/update-dev-deps/ui/plugin-system/src/components/PluginKindSelect/PluginKindSelect.test.tsx#L54-L55

What you did:

I just upgraded the testing-library from the version 13 to 14 and somehow the function findByRole doesn't find my component but in the error console it shows it.

I tried to increase the timeout, I also tried a regexp for the name. It doesn't work.
I have also taken a look at #835 but I'm not using a fancy role here. So I guess my issue is somewhere else.

As a side note, the role attributed previously was button and now it is combobox. I don't know if it's really important to notice it.

What happened:

Here the result shown in the console once the test is crashing.

Unable to find role="combobox" and name "Ernie Variable 1"

Ignored nodes: comments, script, style
<body
  style=""
>
  <div>
    <div
      class="MuiFormControl-root MuiTextField-root css-1u3bzj6-MuiFormControl-root-MuiTextField-root"
      data-testid="plugin-kind-select"
    >
      <div
        class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl css-9ddj71-MuiInputBase-root-MuiOutlinedInput-root"
      >
        <div
          aria-controls=":r3:"
          aria-expanded="false"
          aria-haspopup="listbox"
          aria-labelledby=":r2:"
          class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input css-11u53oe-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input"
          id=":r2:"
          role="combobox"
          tabindex="0"
        >
          Ernie Variable 1
        </div>
        <input
          aria-hidden="true"
          aria-invalid="false"
          class="MuiSelect-nativeInput css-yf8vq0-MuiSelect-nativeInput"
          tabindex="-1"
          value="ErnieVariable1"
        />
        <svg
          aria-hidden="true"
          class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-hfutr2-MuiSvgIcon-root-MuiSelect-icon"
          data-testid="ArrowDropDownIcon"
          focusable="false"
          viewBox="0 0 24 24"
        >
          <path
            d="M7 10l5 5 5-5z"
          />
        </svg>
        <fieldset
          aria-hidden="true"
          class="MuiOutlinedInput-notchedOutline css-1d3z3hw-MuiOutlinedInput-notchedOutline"
        >
          <legend
            class="css-ihdtdm"
          >
            <span
              class="notranslate"
            ></span>
          </legend>
        </fieldset>
      </div>
    </div>
  </div>
</body>

But we clearly see that the component with the role combobox and the name Ernie Variable 1 is there. I know the component is loaded asynchronously, so this combobox with the accurate name is not present immediately but even after increasing the timeout it is still crashing.

So I'm not sure what is wrong in my component or even in my usage of the testing-library.

@valleywood
Copy link

valleywood commented Nov 10, 2023

Experiencing similar thing with findByTestId after upgrading @testing-library/dom from version 9.3.1 to 9.3.2 or greater.
I see that you don't seem to use @testing-library/dom explicitly in your package.json but it's a dependency of @testing-library/react so what version of @testing-library/dom are linked in your solution?

I'm thinking about filing an issue on @testing-library/dom for this but it would be good to know if it works for you if forcing version 9.3.1 of @testing-library/dom by either adding a resolutions/override field to your package json like below:

"resolutions": {
    "@testing-library/dom": "9.3.1",
  },
  "overrides": {
     "@testing-library/dom": "9.3.1",
  },

Update: Filed an issue on @testing-library/dom that might be related:
testing-library/dom-testing-library#1276

@Nexucis
Copy link
Author

Nexucis commented Nov 10, 2023

It seems I'm using the latest version of @testing-library/dom :

npm ls @testing-library/dom
perses-dev@0.1.0 /Users/ahusson/workspace/go/src/perses/perses/ui
├─┬ @perses-dev/storybook@0.41.1 -> ./storybook
│ └─┬ @storybook/testing-library@0.2.2
│   └── @testing-library/dom@9.3.3 deduped
├─┬ @testing-library/react@14.1.0
│ └── @testing-library/dom@9.3.3
└─┬ @testing-library/user-event@14.5.1
  └── @testing-library/dom@9.3.3

@Nexucis
Copy link
Author

Nexucis commented Nov 10, 2023

I tried using what you suggested to override the version and now I'm using the version 9.3.1.

npm ls @testing-library/dom
perses-dev@0.1.0 /Users/ahusson/workspace/go/src/perses/perses/ui
├─┬ @perses-dev/storybook@0.41.1 -> ./storybook
│ └─┬ @storybook/testing-library@0.2.2
│   └── @testing-library/dom@9.3.1 deduped
├─┬ @testing-library/react@14.1.0
│ └── @testing-library/dom@9.3.1 overridden
└─┬ @testing-library/user-event@14.5.1
  └── @testing-library/dom@9.3.1

But unfortunately I'm experiencing the same issue :(.

@valleywood
Copy link

/user-event@14.5.1
  └── @testing-library/dom@9.3.1

OK, it might be unrelated to the issue I was seeing with @testing-library/dom then, it just sounded so similar 😢

@alexmngn
Copy link

alexmngn commented Nov 21, 2023

I'm experiencing the same issue with a react-native application. Weirdly though, using the new API with role="button" doesn't work but using the older react-native API with accessibilityRole="button" does work fine.

@agentdylan
Copy link

agentdylan commented Dec 4, 2023

I looked in to this a little bit and found it has to do with not getting the accessible name for the element, seemingly due to an issue with handling of the aria-labelledby attribute.

Failing case

When the aria-labelledby attribute value is the id of the same element (referring to itself) then React Testing Library fails to find it.

As in the example given by @Nexucis... the element has aria-labelledby=":r2:" and id=":r2:".

<div
  aria-controls=":r3:"
  aria-expanded="false"
  aria-labelledby=":r2:"
  id=":r2:"
  role="combobox"
>
  Ernie Variable 1
</div>

Example test code added to plugin-system\src\components\PluginKindSelect\PluginKindSelect.test.tsx, which FAILS
(I ran npm run test -- -- -t PluginKindSelect)

it('shows the correct selected value', async () => {
    render(
      <div>
        <div aria-controls="controlsEl" aria-expanded="false" aria-labelledby="myDiv" id="myDiv" role="combobox">
          Ernie Variable 1
        </div>
      </div>
    );
    // Use findByRole to wait for loading to finish and selected value to appear
    screen.debug();
    const select = await screen.findByRole('combobox', { name: 'Ernie Variable 1' });
    expect(select).toBeInTheDocument();
  });
Test output
@perses-dev/plugin-system:test: FAIL src/components/PluginKindSelect/PluginKindSelect.test.tsx (7.981 s)
@perses-dev/plugin-system:test:   ● Console    
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:     console.log
@perses-dev/plugin-system:test:       <body    
@perses-dev/plugin-system:test:         style=""
@perses-dev/plugin-system:test:       >
@perses-dev/plugin-system:test:         <div>
@perses-dev/plugin-system:test:           <div>
@perses-dev/plugin-system:test:             <div
@perses-dev/plugin-system:test:               aria-controls="controlsEl"
@perses-dev/plugin-system:test:               aria-expanded="false"
@perses-dev/plugin-system:test:               aria-labelledby="myDiv"
@perses-dev/plugin-system:test:               id="myDiv"
@perses-dev/plugin-system:test:               role="combobox"
@perses-dev/plugin-system:test:             >
@perses-dev/plugin-system:test:               Ernie Variable 1
@perses-dev/plugin-system:test:             </div>
@perses-dev/plugin-system:test:           </div>
@perses-dev/plugin-system:test:         </div>
@perses-dev/plugin-system:test:       </body>
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:       at logDOM (../node_modules/@testing-library/dom/dist/pretty-dom.js:87:13)
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:   ● PluginKindSelect › shows the correct selected value
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:     Unable to find role="combobox" and name "Ernie Variable 1"
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:     Ignored nodes: comments, script, style
@perses-dev/plugin-system:test:     <body
@perses-dev/plugin-system:test:       style=""
@perses-dev/plugin-system:test:     >
@perses-dev/plugin-system:test:       <div>
@perses-dev/plugin-system:test:         <div>
@perses-dev/plugin-system:test:           <div
@perses-dev/plugin-system:test:             aria-controls="controlsEl"
@perses-dev/plugin-system:test:             aria-expanded="false"
@perses-dev/plugin-system:test:             aria-labelledby="myDiv"
@perses-dev/plugin-system:test:             id="myDiv"
@perses-dev/plugin-system:test:             role="combobox"
@perses-dev/plugin-system:test:           >
@perses-dev/plugin-system:test:             Ernie Variable 1
@perses-dev/plugin-system:test:           </div>
@perses-dev/plugin-system:test:         </div>
@perses-dev/plugin-system:test:       </div>
@perses-dev/plugin-system:test:     </body>
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:       75 |     // Use findByRole to wait for loading to finish and selected value to appear
@perses-dev/plugin-system:test:       76 |     screen.debug();
@perses-dev/plugin-system:test:     > 77 |     const select = await screen.findByRole('combobox', { name: 'Ernie Variable 1' });
@perses-dev/plugin-system:test:          |                                 ^
@perses-dev/plugin-system:test:       78 |     expect(select).toBeInTheDocument();
@perses-dev/plugin-system:test:       79 |   });
@perses-dev/plugin-system:test:       80 |
@perses-dev/plugin-system:test: 
@perses-dev/plugin-system:test:       at waitForWrapper (../node_modules/@testing-library/dom/dist/wait-for.js:162:27)
@perses-dev/plugin-system:test:       at ../node_modules/@testing-library/dom/dist/query-helpers.js:86:33
@perses-dev/plugin-system:test:       at Object.findByRole (src/components/PluginKindSelect/PluginKindSelect.test.tsx:77:33)

Passing case

When the aria-labelledby points to a separate element (with the name as the content), it will find the accessible name.

For example

<div
  aria-controls=":r3:"
  aria-expanded="false"
  aria-labelledby="anotherElement"
  id=":r2:"
  role="combobox"
></div>
<span id="anotherElement">Ernie Variable 1</span>

Example test code added to plugin-system\src\components\PluginKindSelect\PluginKindSelect.test.tsx, which PASSES
(I ran npm run test -- -- -t PluginKindSelect)

it('shows the correct selected value', async () => {
    render(
      <div>
        <div
          aria-controls="controlsEl"
          aria-expanded="false"
          aria-labelledby="myLabel"
          id="myDiv"
          role="combobox"
        ></div>
        <span id="myLabel">Ernie Variable 1</span>
      </div>
    );
    // Use findByRole to wait for loading to finish and selected value to appear
    const select = await screen.findByRole('combobox', { name: 'Ernie Variable 1' });
    expect(select).toBeInTheDocument();
  });

Alternatively setting aria-label (with no aria-labelledby) or wrapping it in a label element will also allow the element to be found.

In this particular case I also found setting the following got the test to pass by wrapping it in a label element.

renderComponent({
  pluginType: 'Variable',
  value: 'ErnieVariable1',
  label: 'Ernie Variable 1',
});

Note this resulted in the combobox div having aria-labelledby=":r2:-label :r2:" and assumes the :r2: element is empty.
https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby#benefits_and_drawbacks

@agentdylan
Copy link

agentdylan commented Dec 4, 2023

I dug in to this further and created several test cases below for @testing-library/dom to /src/__tests__/element-queries.js

Interestingly it was possible to get the element by labelText and then filter it to the role using the matching selector.
e.g. getAllByLabelText('myLabel', { selector: '[role="myRole"]' })

However, attempts to get by role with name failed with an error
e.g. getAllByRole('myRole', { name: 'myLabel' })

TypeError: root.getElementById is not a function

  271 |
  272 |       return matches(
> 273 |         computeAccessibleName(element, {
      |                              ^
  274 |           computedStyleSupportsPseudoElements:
  275 |             getConfig().computedStyleSupportsPseudoElements,
  276 |         }),

which points to https://github.com/eps1lon/dom-accessibility-api/blob/0f33d2d73af4bfcf7f3bc53e13e05dedd07ebc77/sources/util.ts#L101

Tests
test('can get elements labelled with aria-labelledby attribute referencing themselves', () => {
  const {getAllByLabelText} = render(`
    <div>
      <div id="cat" aria-labelledby="cat">Abyssinian</div>
    </div>
  `)

  const result = getAllByLabelText('Abyssinian')
  expect(result).toHaveLength(1)
  expect(result[0].id).toBe('cat')
})

test('can get elements with matching selector, labelled with aria-labelledby attribute referencing themselves', () => {
  const {getAllByLabelText} = render(`
    <div>
      <div id="cat" aria-labelledby="cat" role="term">Tonkinese</div>
    </div>
  `)

  const result = getAllByLabelText('Tonkinese', { selector: '[role="term"]' })
  expect(result).toHaveLength(1)
  expect(result[0].id).toBe('cat')
})

test('can get sibling elements with matching selector, labelled with aria-labelledby attribute', () => {
  const {getAllByLabelText} = render(`
    <div>
      <div id="cat" aria-labelledby="cat-label" role="term"></div>
      <span id="cat-label">Toyger</span>
    </div>
  `)

  const result = getAllByLabelText('Toyger', { selector: '[role="term"]' })
  expect(result).toHaveLength(1)
  expect(result[0].id).toBe('cat')
})

// Fails: TypeError: root.getElementById is not a function
test('can get elements by role labelled with aria-labelledby attribute referencing themselves', () => {
  const {getAllByRole} = render(`
    <div>
      <div id="cat" aria-labelledby="cat" role="term">Birman</div>
    </div>
  `)

  const result = getAllByRole('term', { name: 'Birman' })
  expect(result).toHaveLength(1)
  expect(result[0].id).toBe('cat')
})

// Fails: TypeError: root.getElementById is not a function
test('can get sibling elements by role labelled with aria-labelledby attribute', () => {
  const {getAllByRole} = render(`
    <div>
      <div id="cat" aria-labelledby="cat-label" role="term"></div>
      <span id="cat-label">Siamese</span>
    </div>
  `)

  const result = getAllByRole('term', { name: 'Siamese' })
  expect(result).toHaveLength(1)
  expect(result[0].id).toBe('cat')
})
Test Output
Summary of all failing tests
 FAIL  src/__tests__/element-queries.js (5.413 s)
  ● can get elements by role labelled with aria-labelledby attribute referencing themselves

    TypeError: root.getElementById is not a function

      271 |
      272 |       return matches(
    > 273 |         computeAccessibleName(element, {
          |                              ^
      274 |           computedStyleSupportsPseudoElements:
      275 |             getConfig().computedStyleSupportsPseudoElements,
      276 |         }),

      at getElementById (node_modules/dom-accessibility-api/sources/util.ts:99:22)
          at Array.map (<anonymous>)
      at map (node_modules/dom-accessibility-api/sources/util.ts:99:5)
      at computeTextAlternative (node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:584:18)
      at computeTextAlternative (node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:721:3)
      at computeAccessibleName (node_modules/dom-accessibility-api/sources/accessible-name.ts:40:31)
      at src/queries/role.ts:273:30
          at Array.filter (<anonymous>)
      at filter (src/queries/role.ts:266:6)
      at allQuery (src/query-helpers.ts:106:17)
      at query (src/query-helpers.ts:177:17)
      at Object.getAllByRole (src/__tests__/element-queries.js:307:18)

  ● can get sibling elements by role labelled with aria-labelledby attribute

    TypeError: root.getElementById is not a function

      271 |
      272 |       return matches(
    > 273 |         computeAccessibleName(element, {
          |                              ^
      274 |           computedStyleSupportsPseudoElements:
      275 |             getConfig().computedStyleSupportsPseudoElements,
      276 |         }),

      at getElementById (node_modules/dom-accessibility-api/sources/util.ts:99:22)
          at Array.map (<anonymous>)
      at map (node_modules/dom-accessibility-api/sources/util.ts:99:5)
      at computeTextAlternative (node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:584:18)
      at computeTextAlternative (node_modules/dom-accessibility-api/sources/accessible-name-and-description.ts:721:3)
      at computeAccessibleName (node_modules/dom-accessibility-api/sources/accessible-name.ts:40:31)
      at src/queries/role.ts:273:30
          at Array.filter (<anonymous>)
      at filter (src/queries/role.ts:266:6)
      at allQuery (src/query-helpers.ts:106:17)
      at query (src/query-helpers.ts:177:17)
      at Object.getAllByRole (src/__tests__/element-queries.js:320:18)

@Nexucis
Copy link
Author

Nexucis commented Dec 4, 2023

amazing investigation @agentdylan !! Thank you so much for taking time to find a workaround.
Hope it will help to fix the issue if there is a fix to do

@agentdylan
Copy link

Happy to help! yeah, not sure what the complete fix is exactly though and suspect the issue/fix may be in another dependency. Hopefully my investigation is useful and perhaps someone with more familiarity in this area will be able to figure it out 😄

@agentdylan
Copy link

I believe I've traced it to the root cause of the issue and filed an issue on the dependency.
eps1lon/dom-accessibility-api#1018

It is unique to the combobox role when using aria-labelledby to point to itself as in your example.

It is interesting there was also issues with my tests using the role term however, so there may be separate or additional fixes to be done.

make-github-pseudonymous-again pushed a commit to infoderm/patients that referenced this issue Mar 5, 2024
Fixes #752.
Had to upgrade react monorepo.
Had to apply workaround found at
testing-library/react-testing-library#1248 (comment).
github-merge-queue bot pushed a commit to infoderm/patients that referenced this issue Mar 5, 2024
Fixes #752.
Had to upgrade react monorepo.
Had to apply workaround found at
testing-library/react-testing-library#1248 (comment).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants