id | title |
---|---|
api-queries |
Queries |
import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem'
:::info
getBy
queries are shown by default in the query documentation
below.
:::
getBy*
queries return the first matching node for a query, and throw an error
if no elements match or if more than one match is found (use getAllBy
instead).
getAllBy*
queries return an array of all matching nodes for a query, and throw
an error if no elements match.
queryBy*
queries return the first matching node for a query, and return null
if no elements match. This is useful for asserting an element that is not
present. This throws an error if more than one match is found (use queryAllBy
instead).
queryAllBy*
queries return an array of all matching nodes for a query, and
return an empty array ([]
) if no elements match.
findBy*
queries return 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 1000
ms. If you need
to find more than one element, then use findAllBy
.
:::info
this is a simple combination of getBy*
queries and
waitFor
. The findBy*
queries accept the waitFor
options as the last argument. (i.e.
screen.findByText('text', queryOptions, waitForOptions)
)
:::
findAllBy*
queries return 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 of 1000
ms.
The 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.
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 the
within
functionality).
Here's how you use it:
import { screen } from '@testing-library/dom'
// NOTE: many framework-implementations of Testing Library
// re-export everything from `@testing-library/dom` so you
// may be able to import screen from your framework-implementation:
// import {render, screen} from '@testing-library/react'
const exampleHTML = `
<label for="example">Example</label>
<input id="example" />
`
document.body.innerHTML = exampleHTML
const exampleInput = screen.getByLabelText(/example/i)
:::caution
You need a global DOM environment to use screen
. If you're using jest, with
the
testEnvironment
set to jsdom
, a global DOM environment will be available for you.
If you're loading your test with a script
tag, make sure it comes after the
body
. An example can be seen
here.
:::
For convenience screen also exposes a debug
method in addition to the queries.
This method is essentially a shortcut for console.log(prettyDOM())
. It
supports debugging the document, a single element, or an array of elements.
import { screen } from '@testing-library/dom'
document.body.innerHTML = `
<button>test</button>
<span>multi-test</span>
<div>multi-test</div>
`
// debug document
screen.debug()
// debug single element
screen.debug(screen.getByText('test'))
// debug multiple elements
screen.debug(screen.getAllByText('multi-test'))
For debugging using testing-playground, screen exposes this convenient method which logs a URL that can be opened in a browser.
import { screen } from '@testing-library/dom'
document.body.innerHTML = `
<button>test</button>
<span>multi-test</span>
<div>multi-test</div>
`
// log entire document to testing-playground
screen.logTestingPlaygroundURL()
// log a single element
screen.logTestingPlaygroundURL(screen.getByText('test'))
:::info
These queries are the base queries and 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 above (using screen
is recommended).
:::
getByLabelText, queryByLabelText, getAllByLabelText, queryAllByLabelText, findByLabelText, findAllByLabelText
getByLabelText(
container: HTMLElement, // if you're using `screen`, then skip this argument
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement
This will search for the label that matches the given TextMatch
,
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>
<input id="username-input" />
// The aria-labelledby attribute with form elements
<label id="username-label">Username</label>
<input aria-labelledby="username-label" />
// Wrapper labels
<label>Username <input /></label>
// Wrapper labels where the label text is in another child element
<label>
<span>Username</span>
<input />
</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.
<input aria-label="username" />
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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')
cy.findByLabelText('username').should('exist')
It will 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
.
If it is important that you query an actual <label>
element you can provide a
selector
in the options:
// Multiple elements labelled via aria-labelledby
<label id="username">Username</label>
<input aria-labelledby="username" />
<span aria-labelledby="username">Please enter your username</span>
// Multiple labels with the same text
<label>
Username
<input />
</label>
<label>
Username
<textarea></textarea>
</label>
const inputNode = screen.getByLabelText('Username', { selector: 'input' })
:::danger
getByLabelText
will not work in the case where a for
attribute on a
<label>
element matches an id
attribute 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>
getByPlaceholderText, queryByPlaceholderText, getAllByPlaceholderText, queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText
getByPlaceholderText(
container: HTMLElement, // if you're using `screen`, then skip this argument
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement
This will search for all elements with a placeholder attribute and find one that
matches the given TextMatch
.
<input placeholder="Username" />
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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')
cy.findByPlaceholderText('Username').should('exist')
:::tip
A placeholder is not a good substitute for a label so you should generally use
getByLabelText
instead.
:::
getByText, queryByText, getAllByText, queryAllByText, findByText, findAllByText
getByText(
container: HTMLElement, // if you're using `screen`, then skip this argument
text: TextMatch,
options?: {
selector?: string = '*',
exact?: boolean = true,
ignore?: string|boolean = 'script, style',
normalizer?: NormalizerFn,
}): HTMLElement
This will search for all elements that have a text node with textContent
matching the given TextMatch
.
<a href="/about">About βΉοΈ</a>
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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)
cy.findByText(/about/i).should('exist')
It also works with input
s whose type
attribute is either submit
or
button
:
<input type="submit" value="Send data" />
:::info
See getByLabelText
for more details on how and when to use
the selector
option
:::
The ignore
option accepts a query selector. If the
node.matches
returns true for that selector, the node will be ignored. This defaults to
'script'
because generally you don't want to select script 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
.
getByAltText, queryByAltText, getAllByAltText, queryAllByAltText, findByAltText, findAllByAltText
getByAltText(
container: HTMLElement, // if you're using `screen`, then skip this argument
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement
This will return the element (normally an <img>
) that has the given alt
text. Note that it only supports elements which accept an alt
attribute:
<img>
,
<input>
,
and <area>
(intentionally excluding
<applet>
as it's deprecated).
<img alt="Incredibles 2 Poster" src="/incredibles-2.png" />
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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)
cy.findByAltText(/incredibles.*? poster/i).should('exist')
getByTitle, queryByTitle, getAllByTitle, queryAllByTitle, findByTitle, findAllByTitle
getByTitle(
container: HTMLElement, // if you're using `screen`, then skip this argument
title: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement
Returns the element that has the matching title
attribute.
Will also find a title
element within an SVG.
<span title="Delete" id="2"></span>
<svg>
<title>Close</title>
<g><path /></g>
</svg>
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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')
cy.findByTitle('Delete').should('exist')
cy.findByTitle('Close').should('exist')
getByDisplayValue, queryByDisplayValue, getAllByDisplayValue, queryAllByDisplayValue, findByDisplayValue, findAllByDisplayValue
getByDisplayValue(
container: HTMLElement, // if you're using `screen`, then skip this argument
value: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement
Returns the input
, textarea
, or select
element that has the matching
display value.
<input type="text" id="lastName" />
document.getElementById('lastName').value = 'Norris'
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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')
cy.findByDisplayValue('Norris').should('exist')
<textarea id="messageTextArea" />
document.getElementById('messageTextArea').value = 'Hello World'
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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')
cy.findByDisplayValue('Hello World').should('exist')
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: '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')
cy.findByDisplayValue('Alaska').should('exist')
getByRole, queryByRole, getAllByRole, queryAllByRole, findByRole, findAllByRole
getByRole(
container: HTMLElement, // if you're using `screen`, then skip this argument
role: TextMatch,
options?: {
exact?: boolean = true,
hidden?: boolean = false,
name?: TextMatch,
normalizer?: NormalizerFn,
selected?: boolean,
checked?: boolean,
pressed?: boolean,
expanded?: boolean,
queryFallbacks?: boolean,
level?: number,
}): HTMLElement
Queries for elements with the given role (and it also accepts a
TextMatch
). Default roles are taken into consideration e.g.
<button />
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
and
aria-*
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 the
heading
role.
:::caution
Roles are matched literally by string equality, without inheriting from the
ARIA role hierarchy. As a result, querying a superclass role like checkbox
will not include elements with a subclass role like switch
.
:::
You can query the returned element(s) by their
accessible name. 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 ThePacielloGroup.
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
<img aria-label="fancy image" src="fancy.jpg" />
will be returned for both
getByAltText('fancy image')
and getByRole('image', { name: 'fancy image' })
.
However, the image will not display its description if fancy.jpg
could not be
loaded. Whether you want assert this functionality in your test or not is up to
you.
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 of
role="none"
and role="presentation"
which are considered in the query in any
case. For example in
<body>
<main aria-hidden="true">
<button>Open dialog</button>
</main>
<div role="dialog">
<button>Close dialog</button>
</div>
</body>
getByRole('button')
would only return the Close dialog
-button. To make
assertions about the Open dialog
-button you would need to use
getAllByRole('button', { hidden: true })
.
The default value for hidden
can
be configured.
You can filter the returned elements by their selected state by setting
selected: true
or selected: false
.
For example in
<body>
<div role="tablist">
<button role="tab" aria-selected="true">Native</button>
<button role="tab" aria-selected="false">React</button>
<button role="tab" aria-selected="false">Cypress</button>
</div>
</body>
you 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
.
You can filter the returned elements by their checked state by setting
checked: true
or checked: false
.
For example in
<body>
<section>
<button role="checkbox" aria-checked="true">Sugar</button>
<button role="checkbox" aria-checked="false">Gummy bears</button>
<button role="checkbox" aria-checked="false">Whipped cream</button>
</section>
</body>
you can get the "Sugar" option by calling
getByRole('checkbox', { checked: true })
. To learn more about the checked
state and which elements can have this state see
ARIA aria-checked
.
:::info Checkboxes have a "mixed" state, which is considered neither checked nor unchecked (details here). :::
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
<body>
<section>
<button aria-pressed="true">π</button>
<button aria-pressed="false">π</button>
</section>
</body>
you can get the "π" button by calling getByRole('button', { pressed: true })
.
To learn more about the pressed state see
ARIA aria-pressed
.
You can filter the returned elements by their expanded state by setting
expanded: true
or expanded: false
.
For example in
<body>
<nav>
<ul>
<li>
<a aria-expanded="false" aria-haspopup="true" href="..."
>Expandable Menu Item</a
>
<ul>
<li><a href="#">Submenu Item 1</a></li>
<li><a href="#">Submenu Item 1</a></li>
</ul>
</li>
<li><a href="#">Regular Menu Item</a></li>
</ul>
</nav>
</body>
you can get the "Expandable Menu Item" link by calling
getByRole('link', { expanded: false })
. To learn more about the checked state
and which elements can have this state see
ARIA aria-checked
.
<div role="dialog">...</div>
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { 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')
cy.findByRole('dialog').should('exist')
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
<div role="switch checkbox" />
because it's the first role, while
getByRole('checkbox')
would not. However,
getByRole('checkbox', { queryFallbacks: true })
would enable all fallback
roles and therefore match the same element.
:::info 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). :::
An element with the heading
role can be queried by any heading level
getByRole('heading')
or by a specific heading level using the level
option
getByRole('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,
<body>
<section>
<h1>Heading Level One</h1>
<h2>First Heading Level Two</h2>
<h3>Heading Level Three</h3>
<div role="heading" aria-level="2">Second Heading Level Two</div>
</section>
</body>
you can query the Heading Level Three
heading using
getByRole('heading', { level: 3 })
.
getByRole('heading', { level: 1 })
// <h1>Heading Level One</h1>
getAllByRole('heading', { level: 2 })
// [
// <h2>First Heading Level Two</h2>,
// <div role="heading" aria-level="2">Second Heading Level Two</div>
// ]
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
.
:::danger
The level
option is only applicable to the heading
role. An error will
be thrown when used with any other role.
:::
getByTestId, queryByTestId, getAllByTestId, queryAllByTestId, findByTestId, findAllByTestId
getByTestId(
container: HTMLElement, // if you're using `screen`, then skip this argument
text: TextMatch,
options?: {
exact?: boolean = true,
normalizer?: NormalizerFn,
}): HTMLElement
A shortcut to container.querySelector(`[data-testid="${yourId}"]`)
(and it
also accepts a TextMatch
).
<div data-testid="custom-element" />
<Tabs defaultValue="native" values={[ { label: 'Native', value: 'native', }, { label: 'React', value: 'react', }, { label: 'Cypress', value: 'cypress', }, ] }>
import { screen } from '@testing-library/dom'
const element = screen.getByTestId('custom-element')
import { render, screen } from '@testing-library/react'
render(<MyComponent />)
const element = screen.getByTestId('custom-element')
cy.findByTestId('custom-element').should('exist')
:::caution
In the spirit of the guiding principles, it is
recommended to use this only after the other queries don't work for your use
case. Using data-testid attributes do not resemble how your software is used
and should be avoided if possible. That said, they are way better than
querying based on DOM structure or styling css class names. Learn more about
data-testid
s from the blog post
"Making your UI tests resilient to change"
:::
The ...ByTestId
functions in DOM Testing Library
use the attribute
data-testid
by default, following the precedent set by
React Native Web
which uses a testID
prop to emit a data-testid
attribute on the element, and
we recommend you adopt that attribute where possible. But if you already have an
existing codebase that uses a different attribute for this purpose, you can
override this value via
configure({testIdAttribute: 'data-my-test-attribute'})
.
Several APIs accept a TextMatch
which can be a string
, regex
or a
function
which returns true
for a match and false
for a mismatch.
Some APIs 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.exact
has no effect onregex
orfunction
arguments.- In most cases using a regex instead of a string gives you more control over
fuzzy matching and should be preferred over
{ exact: false }
.
normalizer
: An optional function which overrides normalization behavior. SeeNormalization
.
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 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.
:::info
Specifying a value for normalizer
replaces the built-in normalization, but
you can call getDefaultNormalizer
to 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.
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, ''),
})
Given the following HTML:
<div>Hello World</div>
Will 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) // advanced regex
// 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')
})