1. Project Overview & Quickstart (testing-library/react-testing-library)
Table of Contents
- The problem
- The solution
- Installation
- Examples
- Hooks
- Guiding Principles
- Docs
- Issues
- Contributors
- LICENSE
The problem
You want to write maintainable tests for your React components. As a part of
this goal, you want your tests to avoid including implementation details of your
components and rather focus on making your tests give you the confidence for
which they are intended. As part of this, you want your testbase to be
maintainable in the long run so refactors of your components (changes to
implementation but not functionality) don't break your tests and slow you and
your team down.
The solution
The React Testing Library is a very lightweight solution for testing React
components. It provides light utility functions on top of react-dom andreact-dom/test-utils, in a way that encourages better testing practices. Its
primary guiding principle is:
The more your tests resemble the way your software is used, the more
confidence they can give you.
Installation
This module is distributed via npm which is bundled with node and
should be installed as one of your project's devDependencies.
Starting from RTL version 16, you'll also need to install@testing-library/dom:
npm install --save-dev @testing-library/react @testing-library/domor
for installation via yarn
yarn add --dev @testing-library/react @testing-library/domThis library has peerDependencies listings for react, react-dom and
starting from RTL version 16 also @testing-library/dom.
React Testing Library versions 13+ require React v18. If your project uses an
older version of React, be sure to install version 12:
npm install --save-dev @testing-library/react@12
yarn add --dev @testing-library/react@12You may also be interested in installing @testing-library/jest-dom so you can
use the custom jest matchers.
Suppressing unnecessary warnings on React DOM 16.8
There is a known compatibility issue with React DOM 16.8 where you will see the
following warning:
Warning: An update to ComponentName inside a test was not wrapped in act(...).If you cannot upgrade to React DOM 16.9, you may suppress the warnings by adding
the following snippet to your test configuration
(learn more):
// this is just a little hack to silence a warning that we'll get until we
// upgrade to 16.9. See also: https://github.com/facebook/react/pull/14853
const originalError = console.error
beforeAll(() => {
console.error = (...args) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
return
}
originalError.call(console, ...args)
}
})
afterAll(() => {
console.error = originalError
})Examples
Basic Example
// hidden-message.js
import * as React from 'react'
// NOTE: React Testing Library works well with React Hooks and classes.
// Your tests will be the same regardless of how you write your components.
function HiddenMessage({children}) {
const [showMessage, setShowMessage] = React.useState(false)
return (
<label htmlFor="toggle">Show Message</label>
setShowMessage(e.target.checked)}
checked={showMessage}
/>
{showMessage ? children : null}
)
}
export default HiddenMessage// __tests__/hidden-message.js
// these imports are something you'd normally configure Jest to import for you
// automatically. Learn more in the setup docs: https://testing-library.com/docs/react-testing-library/setup#cleanup
import '@testing-library/jest-dom'
// NOTE: jest-dom adds handy assertions to Jest and is recommended, but not required
import * as React from 'react'
import {render, fireEvent, screen} from '@testing-library/react'
import HiddenMessage from '../hidden-message'
test('shows the children when the checkbox is checked', () => {
const testMessage = 'Test Message'
render(<HiddenMessage>{testMessage}</HiddenMessage>)
// query* functions will return the element or null if it cannot be found
// get* functions will return the element or throw an error if it cannot be found
expect(screen.queryByText(testMessage)).toBeNull()
// the queries can accept a regex to make your selectors more resilient to content tweaks and changes.
fireEvent.click(screen.getByLabelText(/show/i))
// .toBeInTheDocument() is an assertion that comes from jest-dom
// otherwise you could use .toBeDefined()
expect(screen.getByText(testMessage)).toBeInTheDocument()
})Complex Example
// login.js
import * as React from 'react'
function Login() {
const [state, setState] = React.useReducer((s, a) => ({...s, ...a}), {
resolved: false,
loading: false,
error: null,
})
function handleSubmit(event) {
event.preventDefault()
const {usernameInput, passwordInput} = event.target.elements
setState({loading: true, resolved: false, error: null})
window
.fetch('/api/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: usernameInput.value,
password: passwordInput.value,
}),
})
.then(r => r.json().then(data => (r.ok ? data : Promise.reject(data))))
.then(
user => {
setState({loading: false, resolved: true, error: null})
window.localStorage.setItem('token', user.token)
},
error => {
setState({loading: false, resolved: false, error: error.message})
},
)
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="usernameInput">Username</label>
<label htmlFor="passwordInput">Password</label>
Submit{state.loading ? '...' : null}
</form>
{state.error ? {state.error} : null}
{state.resolved ? (
Congrats! You're signed in!
) : null}
)
}
export default Login/* Detailed source-code truncated for AI context efficiency. */We recommend using Mock Service Worker library
to declaratively mock API communication in your tests instead of stubbingwindow.fetch, or relying on third-party adapters.
More Examples
We're in the process of moving examples to the
docs site
You'll find runnable examples of testing with different libraries in
the react-testing-library-examples codesandbox.
Some included are:
Hooks
If you are interested in testing a custom hook, check out React Hooks Testing
Library.
NOTE: it is not recommended to test single-use custom hooks in isolation from
the components where it's being used. It's better to test the component that's
using the hook rather than the hook itself. TheReact Hooks Testing Library
is intended to be used for reusable hooks/libraries.
Guiding Principles
The more your tests resemble the way your software is used, the more
confidence they can give you.
We try to only expose methods and utilities that encourage you to write tests
that closely resemble how your React components are used.
Utilities are included in this project based on the following guiding
principles:
- If it relates to rendering components, it deals with DOM nodes rather than
component instances, nor should it encourage dealing with component
instances. - It should be generally useful for testing individual React components or
full React applications. While this library is focused onreact-dom,
utilities could be included even if they don't directly relate toreact-dom. - Utility implementations and APIs should be simple and flexible.
Most importantly, we want React Testing Library to be pretty light-weight,
simple, and easy to understand.
Docs
Issues
Looking to contribute? Look for the Good First Issue label.
๐ Bugs
Please file an issue for bugs, missing documentation, or unexpected behavior.
๐ก Feature Requests
Please file an issue to suggest new features. Vote on feature requests by adding
a ๐. This helps maintainers prioritize what to work on.
โ Questions
For questions related to using the library, please visit a support community
instead of filing an issue on GitHub.
Contributors
Thanks goes to these people (emoji key):
This project follows the all-contributors specification.
Contributions of any kind welcome!
LICENSE
2. Official Technical Reference & Guides (testing-library/testing-library-docs)
File: README.md
testing-library-docs
Documentation site for
React Testing Library,
DOM Testing Library,
Angular Testing Library,
and related projects
Build Status:
This website was created with Docusaurus.
What's In This Document
Get Started in 5 Minutes
- Make sure all the dependencies for the website are installed:
# Install dependencies
$ npm install- Run your dev server:
# Start the site
$ npm startEditing Content
Editing an existing docs page
Edit docs by navigating to docs/ and editing the corresponding document:
docs/doc-to-be-edited.mdx
---
id: page-needs-edit
title: This Doc Needs To Be Edited
---
Edit me...For more information about docs, click here
Editing an existing blog post
Edit blog posts by navigating to blog and editing the corresponding post:
blog/post-to-be-edited.mdx
---
id: post-needs-edit
title: This Blog Post Needs To Be Edited
---
Edit me...For more information about blog posts, click
here
Adding Content
Adding a new docs page to an existing sidebar
- Create the doc as a new markdown file in
/docs, exampledocs/newly-created-doc.mdx:
---
id: newly-created-doc
title: This Doc Needs To Be Edited
---
My new content here..Note: Ensure the file name and the id value do not include non-url safe
characters i.e. '*'.
- Refer to that doc's ID in an existing sidebar in
sidebar.json:
// Add newly-created-doc to the Getting Started category of docs
{
"docs": {
"Getting Started": [
"quick-start",
"newly-created-doc" // new doc here
],
...
},
...
}For more information about adding new docs, click
here
Contributors
Thanks goes to these wonderful people
(emoji key):
This project follows the
all-contributors
specification. Contributions of any kind welcome!
File: docs/queries/about.mdx
id: about title: About Queries
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Overview
Queries are the methods that Testing Library gives you to find elements on the
page. There are several types of queries ("get", "find",
"query"); the difference between them is whether the query will throw an error
if no element is found or if it will return a Promise and retry. Depending on
what page content you are selecting, different queries may be more or less
appropriate. See the priority guide for recommendations on how to
make use of semantic queries to test your page in the most accessible way.
After selecting an element, you can use the
Events API or
user-event to fire events and simulate user interactions
with the page, or use Jest and jest-dom to make
assertions about the element.
There are Testing Library helper methods that work with queries. As elements
appear and disappear in response to actions,
Async APIs likewaitFor orfindBy queries can be used
to await the changes in the DOM. To find only elements that are children of a
specific element, you can use within. If
necessary, there are also a few options you can
configure, like the timeout for
retries and the default testID attribute.
Example
import {render, screen} from '@testing-library/react' // (or /dom, /vue, ...)
test('should show login form', () => {
render(<Login />)
const input = screen.getByLabelText('Username')
// Events and assertions...
})Types of Queries
- Single Elements
getBy...: Returns the matching node for a query, and throw a descriptive
error if no elements match or if more than one match is found (usegetAllByinstead if more than one element is expected).queryBy...: Returns the matching node for a query, and returnnullif no
elements match. This is useful for asserting an element that is not present.
Throws an error if more than one match is found (usequeryAllByinstead if
this is OK).findBy...: Returns a Promise which resolves when an element is found which
matches the given query. The promise is rejected if no element is found or
if more than one element is found after a default timeout of 1000ms. If you
need to find more than one element, usefindAllBy.
- Multiple Elements
getAllBy...: Returns an array of all matching nodes for a query, and
throws an error if no elements match.queryAllBy...: Returns an array of all matching nodes for a query, and
return an empty array ([]) if no elements match.findAllBy...: Returns a promise which resolves to an array of elements
when any elements are found which match the given query. The promise is
rejected if no elements are found after a default timeout of1000ms.findBymethods are a combination ofgetBy*queries andwaitFor. They accept thewaitForoptions as the last argument (i.e.await screen.findByText('text', queryOptions, waitForOptions))
Summary Table
| Type of Query | 0 Matches | 1 Match | >1 Matches | Retry (Async/Await) |
|---|---|---|---|---|
| Single Element | ||||
getBy... |
Throw error | Return element | Throw error | No |
queryBy... |
Return null |
Return element | Throw error | No |
findBy... |
Throw error | Return element | Throw error | Yes |
| Multiple Elements | ||||
getAllBy... |
Throw error | Return array | Return array | No |
queryAllBy... |
Return [] |
Return array | Return array | No |
findAllBy... |
Throw error | Return array | Return array | Yes |
Priority
Based on the Guiding Principles, your test should
resemble how users interact with your code (component, page, etc.) as much as
possible. With this in mind, we recommend this order of priority:
- 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 thenameoption 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 thenameoption like so:getByRole('button', {name: /submit/i}). Check the
list of roles.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 supportsalttext (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).
Using Queries
The base queries from DOM Testing Library require you to pass a container as
the first argument. Most framework-implementations of Testing Library provide a
pre-bound version of these queries when you render your components with them
which means you do not have to provide a container. In addition, if you just
want to query document.body then you can use the screen export as
demonstrated below (using screen is recommended).
The primary argument to a query can be a string, regular expression, or
function. There are also options to adjust how node text is parsed. See
TextMatch for documentation on what can be passed to a query.
Given the following DOM elements (which can be rendered by React, Vue, Angular,
or plain HTML code):
<label for="username-input">Username</label>You can use a query to find an element (byLabelText, in this case):
import {screen, getByLabelText} from '@testing-library/dom'
// With screen:
const inputNode1 = screen.getByLabelText('Username')
// Without screen, you need to provide a container:
const container = document.querySelector('#app')
const inputNode2 = getByLabelText(container, 'Username')`queryOptions`
You can pass a queryOptions object with the query type. See the docs for each
query type to see available options, e.g. byRole API.
`screen`
All of the queries exported by DOM Testing Library accept a container as the
first argument. Because querying the entire document.body is very common, DOM
Testing Library also exports a screen object which has every query that is
pre-bound to document.body (using thewithin functionality). Wrappers such as
React Testing Library re-export screen so you can use it the same way.
Here's how you use it:
<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
document.body.innerHTML = `
<label for="example">Example</label>
`
const exampleInput = screen.getByLabelText('Example')import {render, screen} from '@testing-library/react'
render(
<label htmlFor="example">Example</label>
,
)
const exampleInput = screen.getByLabelText('Example')import {render, screen} from '@testing-library/angular'
await render(`
<label for="example">Example</label>
`)
const exampleInput = screen.getByLabelText('Example')cy.findByLabelText('Example').should('exist')Note
You need a global DOM environment to use
screen. If you're using jest, with
the
testEnvironment
set tojsdom, a global DOM environment will be available for you.If you're loading your test with a
scripttag, make sure it comes after thebody. An example can be seen
here.
`TextMatch`
Most of the query APIs take a TextMatch as an argument, which means the
argument can be either a string, regex, or a function of signature(content?: string, element?: Element | null) => boolean which returns true
for a match and false for a mismatch.
TextMatch Examples
Given the following HTML:
Hello WorldWill find the div:
// Matching a string:
screen.getByText('Hello World') // full string match
screen.getByText('llo Worl', {exact: false}) // substring match
screen.getByText('hello world', {exact: false}) // ignore case
// Matching a regex:
screen.getByText(/World/) // substring match
screen.getByText(/world/i) // substring match, ignore case
screen.getByText(/^hello world$/i) // full string match, ignore case
screen.getByText(/Hello W?oRlD/i) // substring match, ignore case, searches for "hello world" or "hello orld"
// Matching with a custom function:
screen.getByText((content, element) => content.startsWith('Hello'))Will not find the div:
// full string does not match
screen.getByText('Goodbye World')
// case-sensitive regex with different case
screen.getByText(/hello world/)
// function looking for a span when it's actually a div:
screen.getByText((content, element) => {
return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello')
})Precision
Queries that take a TextMatch also accept an object as the final argument that
can contain options that affect the precision of string matching:
exact: Defaults totrue; matches full strings, case-sensitive. When false,
matches substrings and is not case-sensitive.- it has no effect when used together with
regexorfunctionarguments. - in most cases, using a regex instead of a string combined with
{ exact: false }
gives you more control over fuzzy matching so it should be preferred.
- it has no effect when used together with
normalizer: An optional function which overrides normalization behavior. SeeNormalization.
Normalization
Before running any matching logic against text in the DOM, DOM Testing Library
automatically normalizes that text. By default, normalization consists of
trimming whitespace from the start and end of text, and collapsing multiple
adjacent whitespace characters within the string into a single space.
If you want to prevent that normalization, or provide alternative normalization
(e.g. to remove Unicode control characters), you can provide a normalizer
function in the options object. This function will be given a string and is
expected to return a normalized version of that string.
Note
Specifying a value for
normalizerreplaces the built-in normalization, but
you can callgetDefaultNormalizerto obtain a built-in normalizer, either to
adjust that normalization or to call it from your own normalizer.
getDefaultNormalizer takes an options object which allows the selection of
behaviour:
trim: Defaults totrue. Trims leading and trailing whitespacecollapseWhitespace: Defaults totrue. Collapses inner whitespace
(newlines, tabs, repeated spaces) into a single space.
Normalization Examples
To perform a match against text without trimming:
screen.getByText('text', {
normalizer: getDefaultNormalizer({trim: false}),
})To override normalization to remove some Unicode characters whilst keeping some
(but not all) of the built-in normalization behavior:
screen.getByText('text', {
normalizer: str =>
getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''),
})Manual Queries
On top of the queries provided by the testing library, you can use the regularquerySelector DOM API
to query elements. Note that using this as an escape hatch to query by class or
id is not recommended because they are invisible to the user. Use a testid if
you have to, to make your intention to fall back to non-semantic queries clear
and establish a stable API contract in the HTML.
// @testing-library/react
const {container} = render(<MyComponent />)
const foo = container.querySelector('[data-foo="bar"]')Browser extension
Do you still have problems knowing how to use Testing Library queries?
There is a very cool Browser extension for Chrome named
Testing Playground,
and it helps you find the best queries to select elements. It allows you to
inspect the element hierarchies in the Browser's Developer Tools, and provides
you with suggestions on how to select them, while encouraging good testing
practices.
Playground
If you want to get more familiar with these queries, you can try them out on
testing-playground.com. Testing Playground is
an interactive sandbox where you can run different queries against your own
html, and get visual feedback matching the rules mentioned above.
File: docs/queries/byalttext.mdx
id: byalttext title: ByAltText
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByAltText, queryByAltText, getAllByAltText, queryAllByAltText,
findByAltText, findAllByAltText
API
getByAltText(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElementThis will return the element (normally an ) that has the given `alt` text. Note that it only supports elements which accept an `alt` attribute or [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) (since we don't know if a custom element implements `alt` or not): [](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img),
``,
and <area>
(intentionally excluding<applet>
as it's deprecated).
<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const incrediblesPosterImg = screen.getByAltText(/incredibles.*? poster/i)import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const incrediblesPosterImg = screen.getByAltText(/incredibles.*? poster/i)import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const incrediblesPosterImg = screen.getByAltText(/incredibles.*? poster/i)cy.findByAltText(/incredibles.*? poster/i).should('exist')Options
TextMatch options
File: docs/queries/bydisplayvalue.mdx
id: bydisplayvalue title: ByDisplayValue
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByDisplayValue, queryByDisplayValue, getAllByDisplayValue,
queryAllByDisplayValue, findByDisplayValue, findAllByDisplayValue
API
getByDisplayValue(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
value: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElementReturns the input, textarea, or select element that has the matching
display value.
`input` tags
document.getElementById('lastName').value = 'Norris'<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const lastNameInput = screen.getByDisplayValue('Norris')import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const lastNameInput = screen.getByDisplayValue('Norris')import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const lastNameInput = screen.getByDisplayValue('Norris')cy.findByDisplayValue('Norris').should('exist')`textarea` tags
<textarea id="messageTextArea" />document.getElementById('messageTextArea').value = 'Hello World'<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const messageTextArea = screen.getByDisplayValue('Hello World')import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const messageTextArea = screen.getByDisplayValue('Hello World')import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const messageTextArea = screen.getByDisplayValue('Hello World')cy.findByDisplayValue('Hello World').should('exist')`select` tags
In case of select, this will search for a <select> whose selected <option>
matches the given TextMatch.
<select>
<option value="">State</option>
<option value="AL">Alabama</option>
<option selected value="AK">Alaska</option>
<option value="AZ">Arizona</option>
</select><Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const selectElement = screen.getByDisplayValue('Alaska')import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const selectElement = screen.getByDisplayValue('Alaska')import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const selectElement = screen.getByDisplayValue('Alaska')cy.findByDisplayValue('Alaska').should('exist')Options
TextMatch options
File: docs/queries/bylabeltext.mdx
id: bylabeltext title: ByLabelText
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByLabelText, queryByLabelText, getAllByLabelText, queryAllByLabelText,
findByLabelText, findAllByLabelText
API
getByLabelText(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElementThis will search for the label that matches the givenTextMatch, then find the element associated
with that label.
The example below will find the input node for the following DOM structures:
// for/htmlFor relationship between label and form element id
<label for="username-input">Username</label>
// The aria-labelledby attribute with form elements
<label id="username-label">Username</label>
// Wrapper labels
<label>Username </label>
// Wrapper labels where the label text is in another child element
<label>
Username
</label>
// aria-label attributes
// Take care because this is not a label that users can see on the page,
// so the purpose of your input must be obvious to visual users.<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const inputNode = screen.getByLabelText('Username')import {render, screen} from '@testing-library/react'
render(<Login />)
const inputNode = screen.getByLabelText('Username')import {render, screen} from '@testing-library/angular'
await render(Login)
const inputNode = screen.getByLabelText('Username')cy.findByLabelText('Username').should('exist')Options
`name`
The example above does NOT find the input node for label text broken up by
elements. You can use getByRole('textbox', { name: 'Username' }) instead which
is robust against switching to aria-label or aria-labelledby.
`selector`
If it is important that you query a specific element (e.g. an ``) you can
provide a selector in the options:
// Multiple elements labelled via aria-labelledby
<label id="username">Username</label>
Please enter your username
// Multiple labels with the same text
<label>
Username
</label>
<label>
Username
<textarea></textarea>
</label>const inputNode = screen.getByLabelText('Username', {selector: 'input'})Note
getByLabelTextwill not work in the case where aforattribute on a<label>element matches anidattribute on a non-form element.
// This case is not valid
// for/htmlFor between label and an element that is not a form element
<section id="photos-section">
<label for="photos-section">Photos</label>
</section>File: docs/queries/byplaceholdertext.mdx
id: byplaceholdertext title: ByPlaceholderText
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByPlaceholderText, queryByPlaceholderText, getAllByPlaceholderText,
queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText
API
getByPlaceholderText(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElementThis will search for all elements with a placeholder attribute and find one that
matches the given TextMatch.
<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const inputNode = screen.getByPlaceholderText('Username')import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const inputNode = screen.getByPlaceholderText('Username')import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const inputNode = screen.getByPlaceholderText('Username')cy.findByPlaceholderText('Username').should('exist')Note
A placeholder is not a good substitute for a label so you should generally use
getByLabelTextinstead.
Options
TextMatch options
File: docs/queries/byrole.mdx
id: byrole title: ByRole
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByRole, queryByRole, getAllByRole, queryAllByRole, findByRole,
findAllByRole
API
getByRole(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
role: string,
options?: {
hidden?: boolean = false,
name?: TextMatch,
description?: TextMatch,
selected?: boolean,
busy?: boolean,
checked?: boolean,
pressed?: boolean,
suggest?: boolean,
current?: boolean | string,
expanded?: boolean,
queryFallbacks?: boolean,
level?: number,
value?: {
min?: number,
max?: number,
now?: number,
text?: TextMatch,
}
}): HTMLElementQueries for elements with the given role (and it also accepts aTextMatch). Default roles are taken into
consideration e.g. `` has the button role without explicitly setting
the role attribute. Here you can see
a table of HTML elements with their default and desired roles.
Please note that setting a role and/or aria-* attribute that matches the
implicit ARIA semantics is unnecessary and is not recommended as these
properties are already set by the browser, and we must not use the role andaria-* attributes in a manner that conflicts with the semantics described. For
example, a button element can't have the role attribute of heading,
because the button element has default characteristics that conflict with theheading role.
Roles are matched literally by string equality, without inheriting from the
ARIA role hierarchy. As a result, querying a superclass role likecheckbox
will not include elements with a subclass role likeswitch.
You can query the returned element(s) by their
accessible name or description. The
accessible name is for simple cases equal to e.g. the label of a form element,
or the text content of a button, or the value of the aria-label attribute. It
can be used to query a specific element if multiple elements with the same role
are present on the rendered content. For an in-depth guide check out
"What is an accessible name?" from TPGi.
If you only query for a single element with getByText('The name') it's
oftentimes better to use getByRole(expectedRole, { name: 'The name' }). The
accessible name query does not replace other queries such as *ByAlt or*ByTitle. While the accessible name can be equal to these attributes, it does
not replace the functionality of these attributes. For example
`` will be returned forgetByRole('img', { name: 'fancy image' }). However, the image will not display
its description if fancy.jpg could not be loaded. Whether you want to assert
this functionality in your test or not is up to you.
:::tip input type password
Unfortunately, the spec defines that `` has no implicit
role. This means that in order to query this type of element we must fallback to
a less powerful query such as ByLabelText.
:::
Options
`hidden`
If you set hidden to true elements that are normally excluded from the
accessibility tree are considered for the query as well. The default behavior
follows https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion with the exception ofrole="none" and role="presentation" which are considered in the query in any
case. For example in
<main aria-hidden="true">
Open dialog
</main>
Close dialoggetByRole('button') would only return the Close dialog-button. To make
assertions about the Open dialog-button you would need to usegetAllByRole('button', { hidden: true }).
The default value for hidden can
be configured.
`selected`
You can filter the returned elements by their selected state by settingselected: true or selected: false.
For example in
Native
React
Cypressyou can get the "Native"-tab by calling getByRole('tab', { selected: true }).
To learn more about the selected state and which elements can have this state
see ARIA aria-selected.
`busy`
You can filter the returned elements by their busy state by setting busy: true
or busy: false.
For example in
<section>
Login failed
Error: Loading message...
</section>you can get the "Login failed" alert by callinggetByRole('alert', { busy: false }). To learn more about the busy state see
ARIA aria-busy and
MDN aria-busy attribute.
`checked`
You can filter the returned elements by their checked state by settingchecked: true or checked: false.
For example in
<section>
Sugar
Gummy bears
Whipped cream
</section>you can get the "Sugar" option by callinggetByRole('checkbox', { checked: true }). To learn more about the checked
state and which elements can have this state see
ARIA aria-checked.
Note
Checkboxes have a "mixed" state, which is considered neither checked nor
unchecked (details here).
`current`
You can filter the returned elements by their current state by settingcurrent: boolean | string. Note that no aria-current attribute will matchcurrent: false since false is the default value for aria-current.
For example in
<nav>
[๐](current/page)
[๐](another/page)
</nav>you can get the "๐" link by calling getByRole('link', { current: 'page' })
and the "๐" by calling getByRole('link', { current: false }). To learn more
about the current state see
ARIA aria-current.
`pressed`
Buttons can have a pressed state. You can filter the returned elements by their
pressed state by setting pressed: true or pressed: false.
For example in
<section>
๐
๐
</section>you can get the "๐" button by calling getByRole('button', { pressed: true }).
To learn more about the pressed state see
ARIA aria-pressed.
`suggest`
You can disable the ability to
throw suggestions
for a specific query by setting this value to false.
Setting this value to true will throw suggestions for the specific query.
`expanded`
You can filter the returned elements by their expanded state by settingexpanded: true or expanded: false.
For example in
<nav>
<li>
<a aria-expanded="false" aria-haspopup="true" href="..."
>Expandable Menu Item</a
>
<li>[Submenu Item 1](#)</li>
<li>[Submenu Item 1](#)</li>
</li>
<li>[Regular Menu Item](#)</li>
</nav>you can get the "Expandable Menu Item" link by callinggetByRole('link', { expanded: false }). To learn more about the expanded state
and which elements can have this state see
ARIA aria-expanded.
...<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const dialogContainer = screen.getByRole('dialog')import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const dialogContainer = screen.getByRole('dialog')import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const dialogContainer = screen.getByRole('dialog')cy.findByRole('dialog').should('exist')`queryFallbacks`
By default, it's assumed that the first role of each element is supported, so
only the first role can be queried. If you need to query an element by any of
its fallback roles instead, you can use queryFallbacks: true.
For example, getByRole('switch') would always match
`` because it's the first role, whilegetByRole('checkbox') would not. However,getByRole('checkbox', { queryFallbacks: true }) would enable all fallback
roles and therefore match the same element.
An element doesn't have multiple roles in a given environment. It has a single
one. Multiple roles in the attribute are evaluated from left to right until
the environment finds the first role it understands. This is useful when new
roles get introduced and you want to start supporting those as well as older
environments that don't understand that role (yet).
`level`
An element with the heading role can be queried by any heading levelgetByRole('heading') or by a specific heading level using the level optiongetByRole('heading', { level: 2 }).
The level option queries the element(s) with the heading role matching the
indicated level determined by the semantic HTML heading elements <h1>-<h6> or
matching the aria-level attribute.
Given the example below,
<section>
<h1>Heading Level One</h1>
<h2>First Heading Level Two</h2>
<h3>Heading Level Three</h3>
Second Heading Level Two
</section>you can query the Heading Level Three heading usinggetByRole('heading', { level: 3 }).
getByRole('heading', {level: 1})
// <h1>Heading Level One</h1>
getAllByRole('heading', {level: 2})
// [
// <h2>First Heading Level Two</h2>,
// Second Heading Level Two
// ]While it is possible to explicitly set role="heading" and aria-level
attribute on an element, it is strongly encouraged to use the semantic HTML
headings <h1>-<h6>.
To learn more about the aria-level property, see
ARIA aria-level.
The
leveloption is only applicable to theheadingrole. An error will
be thrown when used with any other role.
`value`
A range widget can be queried by any value getByRole('spinbutton') or by a
specific value using the level optiongetByRole('spinbutton', { value: { now: 5, min: 0, max: 10, text: 'medium' } }).
Note that you don't have to specify all properties in value. A subset is
sufficient e.g.getByRole('spinbutton', { value: { now: 5, text: 'medium' } }).
Given the example below,
<section>
Volume
Pitch
</section>you can query specific spinbutton(s) with the following queries,
getByRole('spinbutton', {value: {now: 5}})
// Volume
getAllByRole('spinbutton', {value: {min: 0}})
// [
// Volume,
// Pitch
// ]Every specified property in
valuemust match. For example, if you query for{value: {min: 0, now: 3}}aria-valueminmust be equal to 0 AND >aria-valuenowmust be equal to 3
The
valueoption is only applicable to certain roles (check the linked MDN
pages below for applicable roles). An error will be thrown when used with any
other role.
To learn more about the aria-value* properties, see
MDN aria-valuemin,
MDN aria-valuemax,
MDN aria-valuenow,
MDN aria-valuetext.
`description`
You can filter the returned elements by their
accessible description
for those cases where you have several elements with the same role and they
don't have an accessible name but they do have a description. This would
be the case for elements with
alertdialog role, where thearia-describedby attribute is used to describe the element's content.
For example in
<li role="alertdialog" aria-describedby="notification-id-1">
Close
You have unread emails
</li>
<li role="alertdialog" aria-describedby="notification-id-2">
Close
Your session is about to expire
</li>You can query a specific element like this
getByRole('alertdialog', {description: 'Your session is about to expire'})Performance
getByRole is the most preferred query to use as it most closely resembles the
user experience, however the calculations it must perform to provide this
confidence can be expensive (particularly with large DOM trees).
Where test performance is a concern it may be desirable to trade some of this
confidence for improved performance.
getByRole performance can be improved by setting the optionhidden to true and thereby avoid expensive visibility checks.
Note that in doing so inaccessible elements will now be included in the result.
Another option may be to substitute getByRole for simpler getByLabelText andgetByText queries which can be significantly faster though less robust
alternatives.
File: docs/queries/bytext.mdx
id: bytext title: ByText
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByText, queryByText, getAllByText, queryAllByText, findByText,
findAllByText
API
getByText(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
ignore?: string|boolean = 'script, style',
normalizer?: NormalizerFn,
}): HTMLElementThis will search for all elements that have a text node with textContent
matching the given TextMatch.
[About โน๏ธ](/about)<Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const aboutAnchorNode = screen.getByText(/about/i)import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const aboutAnchorNode = screen.getByText(/about/i)import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const aboutAnchorNode = screen.getByText(/about/i)cy.findByText(/about/i).should('exist')It also works with inputs whose type attribute is either submit orbutton:
Options
TextMatch options, plus the following:
`selector`
Note
See
getByLabelTextfor more details on
how and when to use theselectoroption
`ignore`
The ignore option accepts a query selector. If thenode.matches
returns true for that selector, the node will be ignored. This defaults to'script, style' because generally you don't want to select these tags, but if
your content is in an inline script file, then the script tag could be returned.
If you'd rather disable this behavior, set ignore to false.
File: docs/queries/bytitle.mdx
id: bytitle title: ByTitle
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
getByTitle, queryByTitle, getAllByTitle, queryAllByTitle, findByTitle,
findAllByTitle
API
getByTitle(
// If you're using `screen`, then skip the container argument:
container: HTMLElement,
title: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElementReturns the element that has the matching title attribute.
Will also find a title element within an SVG.
<svg>
<title>Close</title>
<g></g>
</svg><Tabs
defaultValue="native"
values={[
{ label: 'Native', value: 'native', },
{ label: 'React', value: 'react', },
{ label: 'Angular', value: 'angular', },
{ label: 'Cypress', value: 'cypress', },
]
}>
import {screen} from '@testing-library/dom'
const deleteElement = screen.getByTitle('Delete')
const closeElement = screen.getByTitle('Close')import {render, screen} from '@testing-library/react'
render(<MyComponent />)
const deleteElement = screen.getByTitle('Delete')
const closeElement = screen.getByTitle('Close')import {render, screen} from '@testing-library/angular'
await render(MyComponent)
const deleteElement = screen.getByTitle('Delete')
const closeElement = screen.getByTitle('Close')cy.findByTitle('Delete').should('exist')
cy.findByTitle('Close').should('exist')Options
TextMatch options
File: docs/user-event/api-clipboard.mdx
id: clipboard title: Clipboard
Note that the
Clipboard API is
usually not available outside of secure context.
To enable testing of workflows involving the clipboard,userEvent.setup() replaces window.navigator.clipboard with a
stub.
copy()
copy(): Promise<DataTransfer|undefined>Copy the current selection.
If writeToClipboard is true, this will also
write the data to the Clipboard.
cut()
cut(): Promise<DataTransfer|undefined>Cut the current selection.
If writeToClipboard is true, this will also
write the data to the Clipboard.
When performed in editable context, it removes the selected content from the
document.
paste()
paste(clipboardData?: DataTransfer|string): Promise<void>Paste data into the document.
When called without clipboardData, the content to be pasted is read from theClipboard.
When performed in editable context, the pasted content is inserted into the
document.