How to find elements (other than findByText)
ByText
getByText, queryByText, getAllByText, queryAllByText, findByText, findAllByText
The byText
queries accepts a few arguments. Here are some common ways it is used.
byText.test.ts✅ copiedimport {render, screen} from '@testing-library/react' // exact match for text within a DOM element, e.g. // <div>Hello world</div> expect(screen.getByText('Hello world')).toBeInTheDocument() // partial match, or case insensitive expect(screen.getByText('HELLO WORLD', {exact: false})).toBeInTheDocument() expect(screen.getByText('world', {exact: false})).toBeInTheDocument()
ByRole
getByRole, queryByRole, getAllByRole, queryAllByRole, findByRole, findAllByRole
Queries the DOM for elements which match a role. These are the ARIA roles. You can find a list of them here
Here is an example, where we are trying to get the element with a button
role:
role.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <button>hello</button> </div> } test('the title exists', async () => { render(<SomeComponent />) expect(screen.getByRole('button')).toBeInTheDocument() })
You might be asking **why can't we just do something like document.querySelector('button')
(the same as we could in a browser to get a <button>
component.
The reason is that React Testing Library encourages you to write tests with accessibility in mind. Selecting elements in this way is more realistic and encourages better practices when it comes to writing your HTML (JSX) in a proper semantic way.
The same test would also match a <div>
which has the button role, e.g.:
role-aria.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { // Note this is no longer a <button> return <div> <div role="button" onClick={() => console.log("say hi")}> hello </div> </div> } test('the title exists', async () => { render(<SomeComponent />) expect(screen.getByRole('button')).toBeInTheDocument() })
Two important caveats:
- do not go and set
role="something"
in your JSX/HTML just so you can easily select it in tests. This is the completely wrong approach! Write good semantic HTML, and use that in your tests to select an element. - if you have
hidden
(oraria-hidden
) on an element, thengetByRole()
will not select it. You can add the{hidden: true}
argument to find those hidden elements. Here is an example:
hidden-role-query.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <button aria-hidden>Show</button> <button>Hide</button> </div> } test('the title exists', async () => { render(<SomeComponent />) // or as there is only 1, you could do `screen.getByRole('button')` const buttons = screen.getAllByRole('button') expect(buttons).toHaveLength(1) expect(buttons[0].textContent).toBe('Hide') expect(buttons[0]).not.toHaveAttribute('aria-hidden') const buttonsIncludingHidden = screen.getAllByRole('button', {hidden: true}) expect(buttonsIncludingHidden).toHaveLength(2) expect(buttonsIncludingHidden[0].textContent).toBe('Show') expect(buttonsIncludingHidden[0]).toHaveAttribute('aria-hidden') expect(buttonsIncludingHidden[1].textContent).toBe('Hide') expect(buttonsIncludingHidden[1]).not.toHaveAttribute('aria-hidden') })
You can also use the {selected: true}
to only include selected elements (with aria-selected
attribute)
role-selected.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div role="tablist"> <button role="tab" aria-selected="true">Tab1</button> <button role="tab" aria-selected="false">Tab2</button> </div> } test('the title exists', async () => { render(<SomeComponent/>) expect(screen.getByRole('tab', {selected: true}).textContent).toBe('Tab1') expect(screen.getByRole('tab', {selected: false}).textContent).toBe('Tab2') })
There are other similar arguments:
- busy -
getByRole('alert', { busy: false })
for aria-busy{busy: true}
to select elements witharia-busy="true"
attribute{busy: false}
to select elements witharia-busy="false"
attribute
- checked -
getByRole('checkbox', { checked: true })
to get checked checkboxes - current -
getByRole('link', { current: 'page' })
to get an element with thataria-current
value- example:
<a href="page/3" aria-current="page">3</a>
- example:
- pressed -
getByRole('button', { pressed: true })
for elements with aria-pressed<button aria-pressed="true">Vote</button>
getByRole('button', { pressed: true })
- heading - used to select specific h1/h2/h3/h4/h5 tags
getByRole('heading', {level: 1})
- Can also be used to select other elements with the heading role, such as
<span role="heading" aria-level="1">
(equivalent to h1)
ByLabelText
getByLabelText, queryByLabelText, getAllByLabelText, queryAllByLabelText, findByLabelText, findAllByLabelText
This will return elements (form elements) which are labelled by a specific text string. This will select form elements wrapped in a <label>...</label>
, labelled by <label for="...">
(htmlFor
in JSX), or aria labels:
labelled-by-text.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <label> Your First Name <input defaultValue="Fred" /> </label> <label htmlFor="last_name">Your Last Name</label> <input defaultValue="Flintstone" id="last_name" /> <div id="age">Your Age</div> <input aria-labelledby="age" defaultValue="99" /> <input aria-label="Your Occupation" defaultValue="Crane operator" /> </div> } test('selecting via labels', async () => { render(<SomeComponent/>) const firstNameInput = screen.getByLabelText('Your First Name') expect(firstNameInput).toHaveAttribute('value', 'Fred') const lastNameInput = screen.getByLabelText('Your Last Name') expect(lastNameInput).toHaveAttribute('value', 'Flintstone') const ageInput = screen.getByLabelText('Your Age') expect(ageInput).toHaveAttribute('value', '99') const occupationInput = screen.getByLabelText('Your Occupation') expect(occupationInput).toHaveAttribute('value', 'Crane operator') })
ByPlaceholderText
getByPlaceholderText, queryByPlaceholderText, getAllByPlaceholderText, queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText
Use these queries to find inputs with a placeholder="..."
value.
by-placeholder.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <input placeholder="Enter your name" /> </div> } test('selecting via placeholder', async () => { render(<SomeComponent/>) expect(screen.getByPlaceholderText('Enter your name')).toBeInTheDocument() })
Using placeholders is not as accessible as using proper labels, so this should not be your default way to select form elements (see the ByLabelText section for a better alternative).
ByDisplayValue
getByDisplayValue, queryByDisplayValue, getAllByDisplayValue, queryAllByDisplayValue, findByDisplayValue, findAllByDisplayValue
Use this to query for inputs which have their value attribute set to a specific thing.
I find this useful to check a selected value for a dropdown (and haven't ever used it for normal <input>
or <textarea>
)
Here is an example:
by-display-value.test.jsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <input defaultValue="Fred" /> <input value="Fred2" onChange={() => /* not implemented */ undefined}/> <textarea defaultValue="Fred3" /> <select> <option value="H1">Harry potter</option> <option selected value="V1">Voldemort</option> </select> </div> } test('selecting via display value', async () => { render(<SomeComponent/>) // normal inputs: expect(screen.getByDisplayValue('Fred')).toBeInTheDocument() expect(screen.getByDisplayValue('Fred2')).toBeInTheDocument() // textarea: expect(screen.getByDisplayValue('Fred3')).toBeInTheDocument() // select dropdowns: expect(screen.getByDisplayValue('Voldemort')).toBeInTheDocument() expect(screen.queryByDisplayValue('Harry potter')).toBeNull() })
ByAltText
getByAltText, queryByAltText, getAllByAltText, queryAllByAltText, findByAltText, findAllByAltText
This can be used to select <img>
tags with a certain alt tag. You can use {exact: false}
in a similar way to getByText()
for non-exact matches.
by-alt-text.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <img alt="Cat" src="/cat.jpg" /> <img alt="Dog" src="/dog.jpg" /> <img alt="Cats and Dogs" src="/dog.jpg" /> </div> } test('selecting via alt attribute', async () => { render(<SomeComponent/>) expect(screen.getByAltText('Cat')).toBeInTheDocument() expect(screen.getByAltText('Dog')).toBeInTheDocument() const allWithCatsAsAlt = screen.getAllByAltText('cat', {exact: false}) expect(allWithCatsAsAlt).toHaveLength(2) expect(allWithCatsAsAlt[0]).toHaveAttribute('alt', 'Cat') expect(allWithCatsAsAlt[1]).toHaveAttribute('alt', 'Cats and Dogs') })
ByTitle
getByTitle, queryByTitle, getAllByTitle, queryAllByTitle, findByTitle, findAllByTitle
This works similarly to getByAltText
, except it will look for any element with a specific title
attribute.
You can also use this to select SVGs with those titles.
by-title.test.tsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <div title="Number of votes">4</div> <svg> <title>Locked</title> <g><path/></g> </svg> </div> } test('selecting via title attribute', async () => { render(<SomeComponent/>) expect(screen.getByTitle('Number of votes').textContent).toBe('4') expect(screen.getByTitle('Locked')).toBeInTheDocument() })
ByTestId
Sometimes selecting elements can be difficult or ambiguous - especially if you have a complex DOM or multiple elements labelled in the same way. I don't recommend going for this route often, but in those awkward cases, you can add a data-testid="something"
to your JSX and use that to select the element in your tests.
by-test-id.test.jsx✅ copiedimport {render, screen} from '@testing-library/react' function SomeComponent() { return <div> <div data-testid="price">$1.00</div> <div data-testid="discount">$0.50</div> </div> } test('selecting via testid attribute', async () => { render(<SomeComponent/>) expect(screen.getByTestId('price').textContent).toBe('$1.00') expect(screen.getByTestId('discount').textContent).toBe('$0.50') })
Which query to use?
There is a priority guide on the RTL site
Here is that guide, it is worth a read (but make sure to carry on and read what the paragraph I write after this!)
- Queries Accessible to Everyone Queries that reflect the experience of visual/mouse users as well as those that use assistive technology.
getByRole
: This can be used to query every element that is exposed in the accessibility tree. With thename
option you can filter the returned elements by their accessible name. This should be your top preference for just about everything. There's not much you can't get with this (if you can't, it's possible your UI is inaccessible). Most often, this will be used with thename
option like so:getByRole('button', {name: /submit/i})
.getByLabelText
: This method is really good for form fields. When navigating through a website form, users find elements using label text. This method emulates that behavior, so it should be your top preference.getByPlaceholderText
: A placeholder is not a substitute for a label. But if that's all you have, then it's better than alternatives.getByText
: Outside of forms, text content is the main way users find elements. This method can be used to find non-interactive elements (like divs, spans, and paragraphs).getByDisplayValue
: The current value of a form element can be useful when navigating a page with filled-in values.- Semantic Queries HTML5 and ARIA compliant selectors. Note that the user experience of interacting with these attributes varies greatly across browsers and assistive technology.
getByAltText
: If your element is one which supportsalt
text (img
,area
,input
, and any custom element), then you can use this to find that element.getByTitle
: The title attribute is not consistently read by screenreaders, and is not visible by default for sighted users- Test IDs
getByTestId
: The user cannot see (or hear) these, so this is only recommended for cases where you can't match by role or text or it doesn't make sense (e.g. the text is dynamic).
But why shouldn't you always go for getByRole
if it is at the top of the list??
It is slow! Getting an element with getByRole on complex components with a lot of elements can be much slower than a simpler one like getByText
. Getting by text is quite an easy comparison (especially when it is in strict exact match). Get by role has to parse every element, parse the CSS styles (it needs to know if an element is hidden/visible), and then it can figure out what each role is.
You might not notice it - especially on small components. But if you are running integration tests on the top level 'page' components it is something that can come up.
There is an interesting Github discussion here.