Introduction
Rust Testing Library is a Rust port of Testing Library.
Testing Library is a library of simple and complete testing utilities that encourage good testing practices.
Frameworks
Rust Testing Library is available for these Rust frameworks:
The following frameworks are under consideration:
Support
TODO
License
This project is available under the MIT license.
Rust for Web
The Rust Testing Library project is part of Rust for Web.
Rust for Web creates and ports web libraries for Rust. All projects are free and open source.
Core API
Queries
- About Queries
- By Role
- By Label Text
- By Placeholder Text
- By Text
- By Display Value
- By Alt Text
- By Title
- By Test ID
About Queries
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 return an error if no element is found or if it will return a future 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 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 like wait_for or find_by 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 test ID attribute.
Example
TODO
Types of Queries
- Single Elements
get_by...: Returns the matching node for a query, and returns a descriptive error if no elements match or if more than one match is found (useget_all_byinstead if more than one element is expected).query_by...: Returns the matching node for a query, and returnsNoneif no elements match. This is useful for asserting an element that is not present. Returns an error if more than one match is found (usequery_all_byinstead if this is OK).find_by...: Returns a future which resolves when an element is found which matches the given query. The future is rejected if no element is found or if more than one element is found after a default timeout of 1000ms (usefind_all_byif you need to find more than one element).find_bymethods are a combination ofget_byqueries andwait_for. They accept thewait_foroptions as the last argument (i.e.screen.find_by_text("text", query_options, wait_for_options).await).
- Multiple Elements
get_all_by...: Returns a vector of all matching nodes for a query, and returns an error if no elements match.query_all_by...: Returns a vector of all matching nodes for a query, and returns an empty vector (vec![]) if no elements match.find_all_by...: Returns a future which resolves to a vector of elements when any elements are found which match the given query. The future is rejected if no elements are found after a default timeout of 1000ms.
| Type of Query | 0 Matches | 1 Match | >1 Matches | Retry |
|---|---|---|---|---|
| Single Element | ||||
get_by | Error | Element | Error | No |
query_by | None | Element | Error | No |
find_by | Error | Element | Error | Yes |
| Multiple Elements | ||||
get_all_by | Error | Vector | Vector | No |
query_all_by | vec![] | Vector | Vector | No |
find_all_by | Error | Vector | Vector | 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.
get_by_role: 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:get_by_role(AriaRole::Button, ByRoleOptions::default().name("submit")). Check the list of roles.get_by_label_text: 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.get_by_placeholder_text: A placeholder is not a substitute for a label. But if that’s all you have, then it’s better than alternatives.get_by_text: 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).get_by_display_value: 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.
get_by_alt_text: If your element is one which supportsalttext (img,area,input, and any custom element), then you can use this to find that element.get_by_title: The title attribute is not consistently read by screenreaders, and is not visible by default for sighted users
- Test IDs
get_by_test_id: 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 Matcher for documentation on what can be passed to a query.
Given the following DOM elements (which can be rendered by Dioxus, Leptos, Yew, or plain HTML code):
<body>
<div id="app">
<label for="username-input">Username</label>
<input id="username-input" />
</div>
</body>
You can use a query to find an element (by label text, in this case):
use testing_library_dom::{get_by_label_text, Screen, SelectorMatcherOptions};
// With screen:
let input_node_1 = Screen::get_by_label_text("Username", SelectorMatcherOptions::default()).expect("Get should succeed.");
// Without screen, you need to provide a container:
let container = document.query_selector("#app").expect("Query should succeed.").expect("Element should exist.");
let input_node_2 = get_by_label_text(&container, "Username", SelectorMatcherOptions::default()).expect("Get should succeed.");
Options
You can pass an Options struct instance to the query. See the docs for each query to see available options, e.g. By Role 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 function which returns a struct that has every query that is pre-bound to document.body (using the within functionality).
Here’s how you use it:
use testing_library_dom::screen;
let screen = screen();
let example_input = screen.get_by_label_text("Example").expect("Get should succeed.");
Matcher
Most of the query APIs take a Matcher as an argument, which means the argument can be either a string, regex, or a function of signature Fn(String, Option<&Element>) -> bool which returns true for a match and false for a mismatch.
Examples
Given the following HTML:
<div>Hello World</div>
Will find the div:
// Matching a string:
screen.get_by_text("Hello World"); // Full string match
screen.get_by_text("llo Worl", SelectorMatcherOptions::default().exact(false)); // Substring match
screen.get_by_text("hello world", SelectorMatcherOptions::default().exact(false)); // Ignore case
// Matching a regex:
screen.get_by_text(Regex::new(r"World")?); // Substring match
screen.get_by_text(Regex::new(r"(?i)world")?); // Substring match, ignore case
screen.get_by_text(Regex::new(r"(?i)^hello world$")?); // Full string match, ignore case
screen.get_by_text(Regex::new(r"(?i)Hello W?oRlD")?); // Substring match, ignore case, searches for "hello world" or "hello orld"
// Matching with a custom function:
screen.get_by_text(|content, element| => content.starts_with("Hello"));
Will not find the div:
// Full string does not match
screen.get_by_text("Goodbye World");
// Case-sensitive regex with different case
screen.get_by_text(Regex::new("hello world")?);
// Function looking for a span when it's actually a div
screen.get_by_text(|content, element| {
element.is_some_and(|element| element.tag_name().to_lower_case() == "span") && content.starts_with("Hello")
});
Precision
Queries that take a Matcher also accept a struct instance 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 regex or function arguments.
- 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.
normalizer: An optional function which overrides normalization behavior. See Normalization.
Normalization
Before running any matching logic against text in the 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 callget_default_normalizerto obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer.
get_default_normalizer takes an options object which allows the selection of behaviour:
trim: Defaults totrue. Trims leading and trailing whitespace.collapse_whitespace: Defaults totrue. Collapses inner whitespace (newlines, tabs, repeated spaces) into a single space.
Normalization Examples
To perform a match against text without trimming:
screen.get_by_text(
"text",
SelectorMatcherOptions::default().normalizer(get_default_normalizer(
DefaultNormalizerOptions::default().trim(false),
)),
);
To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:
screen.get_by_text(
"text",
SelectorMatcherOptions::default().normalizer({
let regex = Regex::new(r"[\u200E-\u200F]*")?;
let normalizer =
get_default_normalizer(DefaultNormalizerOptions::default().trim(false));
Rc::new(move |text| regex.replace_all(&normalizer(text), "").to_string())
}),
);
Manual Queries
On top of the queries provided by the testing library, you can use the regular query_selector 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.
let foo = document.query_selector('[data-foo="bar"]').expect("Query should succeed.").expect("Element should exist.");
By Role
get_by_role,query_by_role,get_all_by_role,query_all_by_role,find_by_role,find_all_by_role
API
use aria_query::AriaRole;
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_role(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
role: AriaRole,
options: ByRoleOptions,
) -> Result<HtmlElement, QueryError>;
struct ByRoleOptions {
hidden: Option<bool>,
name: Option<Matcher>,
description: Option<Matcher>,
selected: Option<bool>,
busy: Option<bool>,
checked: Option<bool>,
pressed: Option<bool>,
suggest: Option<bool>,
current: Option<ByRoleOptionsCurrent>,
expanded: Option<bool>,
query_fallbacks: Option<bool>,
level: Option<usize>,
value: Option<ByRoleOptionsValue>,
}
enum ByRoleOptionsCurrent {
Bool(bool),
String(String),
}
struct ByRoleOptionsValue {
now: Option<f64>,
min: Option<f64>,
max: Option<f64>,
text: Option<Matcher>,
}
Queries for elements with the given role. 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.
Roles are matched literally by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like
checkboxwill 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 get_by_text("The name") it’s oftentimes better to use get_by_role(expected_role, ByRoleOptions::default().name("The name")). The accessible name query does not replace other queries such as *_by_alt or *_by_title. 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 get_by_role(AriaRole::Img, ByRoleOptions::default().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.
Input type password
Unfortunately, the spec defines that <input type="password" /> 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 By Label Text.
The example below will find the dialog container by the dialog role:
<div role="dialog">...</div>
use aria_query::AriaRole;
use testing_library_dom::screen;
let screen = screen();
let dialog_container = screen.get_by_role(AriaRole::Dialog);
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 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>
get_by_role(AriaRole::Button) would only return the “Close dialog”-button. To make assertions about the “Open dialog”-button you would need to use get_all_by_role(AriaRole::Button, ByRoleOptions::default().hidden(true)).
The default value for hidden can be configured.
selected
You can filter the returned elements by their selected state by setting selected to true or 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 get_by_role(AriaRole::Tab, ByRoleOptions::default().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 to true or false.
For example in
<body>
<section>
<div role="alert" aria-busy="false">Login failed</div>
<div role="alert" aria-busy="true">Error: Loading message...</div>
</section>
</body>
you can get the “Login failed” alert by calling get_by_role(AriaRole::Alert, ByRoleOptions::default().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 setting checked to true or 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 get_by_role(AriaRole::Checkbox, ByRoleOptions::default().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 setting current to a boolean or string. Note that no aria-current attribute will match false since false is the default value for aria-current.
For example in
<body>
<nav>
<a href="current/page" aria-current="page">👍</a>
<a href="another/page">👎</a>
</nav>
</body>
you can get the “👍” link by calling get_by_role(AriaRole::Link, ByRoleOptions::default().current("page")) and the “👎” by calling get_by_role(AriaRole::Link, ByRoleOptions::default().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 to true or 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 get_by_role(AriaRole::Button, ByRoleOptions::default().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 setting expanded to true or 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 get_by_role(AriaRole::Link, ByRoleOptions::default().expanded(false)). To learn more about the expanded state and which elements can have this state see ARIA aria-expanded.
query_fallbacks
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 set query_fallbacks to true.
For example, get_by_role(AriaRole::Switch) would always match <div role="switch checkbox" /> because it’s the first role, while get_by_role(AriaRole::Checkbox) would not. However, get_by_role(AriaRole::Checkbox, ByRoleOptions::default().query_fallbacks(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 level get_by_role(AriaRole::Heading) or by a specific heading level using the level option get_by_role(AriaRole::Heading, ByRoleOptions::default().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 get_by_role(AriaRole::Heading, ByRoleOptions::default().level(3)).
get_by_role(AriaRole::Heading, ByRoleOptions::default().level(1))
// <h1>Heading Level One</h1>
get_all_by_role(AriaRole::Heading, ByRoleOptions::default().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.
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 get_by_role(AriaRole::Spinbutton) or by a specific value using the value option get_by_role(AriaRole::Spinbutton, ByRoleOptions::default().value(ByRoleOptionsValue::default().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. ByRoleOptionsValue::default().now(5).text("medium").
Given the example below,
<body>
<section>
<button role="spinbutton" aria-valuenow="5" aria-valuemin="0" aria-valuemax="10" aria-valuetext="medium">
Volume
</button>
<button role="spinbutton" aria-valuenow="3" aria-valuemin="0" aria-valuemax="10" aria-valuetext="medium">
Pitch
</button>
</section>
</body>
you can query specific spinbutton(s) with the following queries,
get_by_role(AriaRole::Spinbutton, ByRoleOptions::default().value(
ByRoleOptionsValue::default().now(5)
))
// <button>Volume</button>
get_all_by_role(AriaRole::Spinbutton, ByRoleOptions::default().value(
ByRoleOptionsValue::default().min(0)
))
// [
// <button>Volume</button>,
// <button>Pitch</button>
// ]
Every specified property in value must match. For example, if you query for
ByRoleOptionsValue::default().min(0).now(3),aria-valueminmust be equal to 0 ANDaria-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 the aria-describedby attribute is used to describe the element’s content.
For example in
<body>
<ul>
<li role="alertdialog" aria-describedby="notification-id-1">
<div><button>Close</button></div>
<div id="notification-id-1">You have unread emails</div>
</li>
<li role="alertdialog" aria-describedby="notification-id-2">
<div><button>Close</button></div>
<div id="notification-id-2">Your session is about to expire</div>
</li>
</ul>
</body>
You can query a specific element like this
get_by_role(AriaRole::Alertdialog, ByRoleOptions::default().description(
"Your session is about to expire"
))
Performance
get_by_role 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.
get_by_role performance can be improved by setting the option hidden 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 get_by_role for simpler get_by_label_text and get_by_text queries which can be significantly faster though less robust alternatives.
By Label Text
get_by_label_text,query_by_label_text,get_all_by_label_text,query_all_by_label_text,find_by_label_text,find_all_by_label_text
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_label_text<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: SelectorMatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct SelectorMatcherOptions {
selector: Option<String>,
exact: Option<bool>,
normalizer: Option<Rc<NormalizerFn>>,
}
type NormalizerFn = dyn Fn(String) -> String;
This will search for the label that matches the given Matcher, then find the element associated with that label.
The example below will find the input node for the following DOM structures:
<!-- for 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" />
use testing_library_dom::screen;
let screen = screen();
let input_node = screen.get_by_label_text("Username");
The example above does NOT find the input node for label text broken up by elements. You can use get_by_role(AriaRole::Textbox, ByRoleOptions::default().name("Username")) instead which is robust against switching to aria-label or aria-labelledby.
Options
Matcher options, plus the following:
selector
If it is important that you query a specific element (e.g. an <input>) 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>
let input_node = screen.get_by_label_text("Username", SelectorMatcherOptions::default().selector("input"));
**Note
get_by_label_textwill not work in the case where aforattribute on a<label>element matches anidattribute on a non-form element.<!-- This case is not valid --> <!-- for between label and an element that is not a form element --> <section id="photos-section"> <label for="photos-section">Photos</label> </section>
By Placeholder Text
get_by_placeholder_text,query_by_placeholder_text,get_all_by_placeholder_text,query_all_by_placeholder_text,find_by_placeholder_text,find_all_by_placeholder_text
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_placeholder_text<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: MatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct MatcherOptions {
exact: Option<bool>,
normalizer: Option<Rc<NormalizerFn>>,
}
type NormalizerFn = dyn Fn(String) -> String;
This will search for all elements with a placeholder attribute and find one that matches the given Matcher.
<input placeholder="Username" />
use testing_library_dom::screen;
let screen = screen();
let input_node = screen.get_by_placeholder_text("Username");
Note
A placeholder is not a good substitute for a label so you should generally use
get_by_label_textinstead.
Options
By Text
get_by_text,query_by_text,get_all_by_text,query_all_by_text,find_by_text,find_all_by_text
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_text<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: SelectorMatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct SelectorMatcherOptions {
selector: Option<String>,
exact: Option<bool>,
ignore: Option<Ignore>,
normalizer: Option<Rc<NormalizerFn>>,
}
enum Ignore {
False,
String(String),
}
type NormalizerFn = dyn Fn(String) -> String;
This will search for all elements that have a text node with textContent matching the given Matcher.
<a href="/about">About ℹ️</a>
use testing_library_dom::screen;
let screen = screen();
let about_anchor_node = screen.get_by_text(Regex::new(r"(?i)about")?);
It also works with inputs whose type attribute is either submit or button:
<input type="submit" value="Send data" />
Options
Matcher options, plus the following:
selector
See By Label Text for more details on how and when to use the selector option.
ignore
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, 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 Ignore::False.
By Display Value
get_by_display_value,query_by_display_value,get_all_by_display_value,query_all_by_display_value,find_by_display_value,find_all_by_display_value
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_display_value<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: MatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct MatcherOptions {
exact: Option<bool>,
normalizer: Option<Rc<NormalizerFn>>,
}
type NormalizerFn = dyn Fn(String) -> String;
Returns the input, textarea, or select element that has the matching display value.
input tags
<input type="text" id="lastName" />
document.get_element_by_id("lastName")?.set_value("Norris");
use testing_library_dom::screen;
let screen = screen();
let last_name_input = screen.get_by_display_value("Norris");
textarea tags
<textarea id="messageTextArea" />
document.get_element_by_id("messageTextArea")?.set_value("Hello World");
use testing_library_dom::screen;
let screen = screen();
let message_text_area = screen.get_by_display_value("Hello World");
select tags
In case of select, this will search for a <select> whose selected <option> matches the given Matcher.
<select>
<option value="">State</option>
<option value="AL">Alabama</option>
<option selected value="AK">Alaska</option>
<option value="AZ">Arizona</option>
</select>
use testing_library_dom::screen;
let screen = screen();
let select_element = screen.get_by_display_value("Alaska");
Options
By Alt Text
get_by_alt_text,query_by_alt_text,get_all_by_alt_text,query_all_by_alt_text,find_by_alt_text,find_all_by_alt_text
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_alt_text<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: MatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct MatcherOptions {
exact: Option<bool>,
normalizer: Option<Rc<NormalizerFn>>,
}
type NormalizerFn = dyn Fn(String) -> String;
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 or custom elements (since we don’t know if a custom element implements alt or not): <img>, <input>, and <area> (intentionally excluding <applet> as it’s deprecated).
<img alt="Incredibles 2 Poster" src="/incredibles-2.png" />
use testing_library_dom::screen;
let screen = screen();
let incredibles_poster_img = screen.get_by_alt_text(Regex::new(r"(?i)incredibles.*? poster")?);
Options
By Title
get_by_title,query_by_title,get_all_by_title,query_all_by_title,find_by_title,find_all_by_title
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_title<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: MatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct MatcherOptions {
exact: Option<bool>,
normalizer: Option<Rc<NormalizerFn>>,
}
type NormalizerFn = dyn Fn(String) -> String;
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>
use testing_library_dom::screen;
let screen = screen();
let delete_element = screen.get_by_title("Delete");
let close_element = screen.get_by_title("Close");
Options
By Test ID
get_by_test_id,query_by_test_id,get_all_by_test_id,query_all_by_test_id,find_by_test_id,find_all_by_test_id
API
use testing_library_dom::{Matcher, QueryError};
use web_sys::HtmlElement;
fn get_by_test_id<M: Into<Matcher>>(
// If you're using `screen`, then skip the container argument:
container: &HtmlElement,
matcher: M,
options: MatcherOptions,
) -> Result<HtmlElement, QueryError>;
struct MatcherOptions {
exact: Option<bool>,
normalizer: Option<Rc<NormalizerFn>>,
}
type NormalizerFn = dyn Fn(String) -> String;
A shortcut to container.query_selector(format!("[data-testid=\"{your_id}\"]")) (and it also accepts a Matcher).
<div data-testid="custom-element" />
use testing_library_dom::screen;
let screen = screen();
let element = screen.get_by_test_id("custom-element");
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-testidattributes 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 aboutdata-testidfrom the blog post “Making your UI tests resilient to change”.
Options
Overriding data-testid
The *_by_test_id 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:
use testing_library_dom::{configure, ConfigFnOrPartial, PartialConfig};
configure(ConfigFnOrPartial::Partial(
PartialConfig::default().test_id_attribute("data-my-test-attribute")
));
User Actions
- Firing Events
- Async Methods
- Appearance and Disappearance
- Considerations
Firing Events
fire_event
use testing_library_dom::{EventType, FireEventError};
use web_sys::EventTarget;
fn fire_event<E: EventType>(node: &EventTarget, event: &E) -> Result<bool, FireEventError>;
Fire DOM events.
// HTML: <button>Submit</button>
use testing_library_dom::{get_by_text, fire_event};
use web_sys::MouseEvent;
let event = MouseEvent::new("click");
event.set_bubbles(true);
event.set_cancelable(true);
fire_event(
get_by_text(&container, "Submit").expect("Get should succeed."),
&event,
).expect("Event should be fired.");
FireEvent::[<event_name>]
fn [<event_name>](node: &EventTarget) -> Result<bool, CreateOrFireEventError>;
fn [<event_name>]_with_init(node: &EventTarget, init: &[<EventInit>]) -> Result<bool, CreateOrFireEventError>;
Convenience methods for firing DOM events. Check out src/events.rs for a full list as well as default event proprties.
Keyboard events
There are three event types related to keyboard input - keyPress, keyDown, and keyUp. When firing these you need to reference an element in the DOM and the key you want to fire.
use testing_library_dom::FireEvent;
use web_sys::KeyboardEventInit;
let init = KeyboardEventInit::new();
init.set_key("Enter");
init.set_code("Enter");
init.set_char_code(13);
FireEvent::key_down_with_init(&dom_node, &init).expect("Event should be fired.");
let init = KeyboardEventInit::new();
init.set_key("A");
init.set_code("KeyA");
FireEvent::key_down_with_init(&dom_node, &init).expect("Event should be fired.");
You can find out which key code to use at https://www.toptal.com/developers/keycode.
CreateEvent::[<event_name>]
fn [<event_name>](node: &EventTarget) -> Result<[<Event>], CreateOrFireEventError>;
fn [<event_name>]_with_init(node: &EventTarget, init: &[<EventInit>]) -> Result<[<Event>], CreateOrFireEventError>;
Convenience methods for creating DOM events that can then be fired by fire_event, allowing you to have a reference to the event created: this might be useful if you need to access event properties that cannot be initiated programmatically (such as time_stamp).
use testing_library_dom::{CreateEvent, fire_event};
use web_sys::MouseEventInit;
let init = MouseEventInit::new();
init.set_button(2);
let my_event = CreateEvent::click_with_init(&node, &init).expect("Event should be created.");
fire_event(&node, &my_event).expect("Event should be fired.");
// `my_event.time_stamp()` can be accessed just like any other properties from `my_event`.
// Note: The access to the events created by `create_event` is based on the native event API.
// Therefore, native properties of HTML Event object (e.g. `timeStamp`, `cancelable`, `type`) should be set using `Object.defineProperty`.
// For more info see: https://developer.mozilla.org/en-US/docs/Web/API/Event.
create_event
use testing_library_dom::{CreateEventError, EventType};
use web_sys::EventTarget;
fn create_event<E: EventType>(
event_name: &str,
node: &EventTarget,
init: Option<&E::Init>,
options: CreateEventOptions<E>,
) -> Result<E, CreateEventError>;
struct CreateEventOptions<'a, E: EventType> {
default_init: Option<&'a DefaultInitFn<E>>,
}
type DefaultInitFn<E> = dyn Fn(&<E as EventType>::Init);
Create DOM events.
use testing_library_dom::{CreateEventOptions, create_event};
use web_sys::{InputEvent, InputEventInit};
let init = InputEventInit::new();
init.set_data(Some("a"));
let event = create_event::<InputEvent>(
"input",
&input,
Some(&init),
CreateEventOptions::default()
).expect("Event should be created.");
Advanced
- Accessibility
- Custom Queries
- Debugging
- Querying Within Elements
- Configuration Options
Accessibility
Testing for Accessibility
One of the guiding principles of the Testing Library APIs is that they should enable you to test your app the way your users use it, including through accessibility interfaces like screen readers.
See the page on queries for details on how using a semantic HTML query can make sure your app works with browser accessibility APIs.
get_roles
use testing_library_dom::{get_roles, GetRolesOptions};
use wasm_bindgen_test::console_log;
use web_sys::window;
let document = window()
.expect("Window should exist.")
.document()
.expect("Document should exist.");
let nav = document.create_element("nav")?;
nav.set_inner_html("\
<ul>\
<li>Item 1</li>\
<li>Item 2</li>\
</ul>\
");
console_log!("{:#?}", get_roles(nav, GetRolesOptions::default()));
// {
// Navigation: [
// Element {
// obj: Node {
// obj: EventTarget {
// obj: Object {
// obj: JsValue(HTMLElement),
// },
// },
// },
// },
// ],
// List: [
// Element {
// obj: Node {
// obj: EventTarget {
// obj: Object {
// obj: JsValue(HTMLUListElement),
// },
// },
// },
// },
// ],
// Listitem: [
// Element {
// obj: Node {
// obj: EventTarget {
// obj: Object {
// obj: JsValue(HTMLLIElement),
// },
// },
// },
// },
// Element {
// obj: Node {
// obj: EventTarget {
// obj: Object {
// obj: JsValue(HTMLLIElement),
// },
// },
// },
// },
// ],
// }
is_inaccessible
This function will compute if the given element should be excluded from the accessibility API by the browser. It implements every MUST criteria from the Excluding Elements from the Accessibility Tree section in WAI-ARIA 1.2 with the exception of checking the role attribute.
It is defined as:
fn is_inaccessible(element: &Element) -> bool;
log_roles
This helper function can be used to print out a list of all the implicit ARIA roles within a tree of DOM nodes, each role containing a list of all of the nodes which match that role. This can be helpful for finding ways to query the DOM under test with By Role.
use testing_library_dom::{log_roles, PrettyRolesOptions};
use web_sys::window;
let document = window()
.expect("Window should exist.")
.document()
.expect("Document should exist.");
let nav = document.create_element("nav")?;
nav.set_inner_html("\
<ul>\
<li>Item 1</li>\
<li>Item 2</li>\
</ul>\
");
log_roles(nav, PrettyRolesOptions::default());
Configuration Options
Introduction
Options
default_hidden
The default value for the hidden option used by get_by_role. Defaults to false.
default_ignore
The default value for the ignore option used by get_by_text. Also determines the nodes that are being ignored when errors are printed.
Defaults to script, style.
throw_suggestions (experimental)
When enabled, if better queries are available, the test will fail and provide a suggested query to use instead. Defaults to false.
To disable a suggestion for a single query just add .suggest(false) as an option.
// Will not throw a suggestion.
screen.get_by_test_id("foo", MatcherOptions::default().suggest(false))
Note
When this option is enabled, it may provide suggestions that lack an intuitive implementation. Typically this happens for roles which cannot be named, most notably paragraphs. For instance, if you attempt to use
get_by_text, you may encounter the following error:A better query is available, try this: get_by_role(AriaRole::Paragraph)However, there is no direct way to query paragraphs using the config parameter, such as in
get_by_role(AriaRole::Paragraph, ByRoleOptions::default().name("Hello World")).To address this issue, you can leverage a custom function to validate the element’s structure, as shown in the example below. More information can be found in the GitHub issue.
get_by_role( AriaRole::Paragraph, ByRoleOptions::default().name(Rc::new(|_, element| { element.map(|element| element.text_content()).is_some_and(|content| content == "Hello World") })) )
test_id_attribute
The attribute used by get_by_test_id and related queries. Defaults to data-testid.
get_element_error
A function that returns the error used when get or find queries fail. Takes the error message and container as arguments.
async_util_timeout
The global timeout value in milliseconds used by wait_for utilities. Defaults to 1000ms.
Frameworks
DOM Testing Library
Install
This module is distributed via crates.io and should be installed as one of your project’s dev-dependencies:
cargo add --dev testing-library-dom
Wrappers
If you are using a framework or library, you will likely want to install the wrapper:
- TODO
Ecosystem
TODO
API
DOM Testing Library is the “core API” of the Testing Library family, so please refer to the Core API section for details. The other libraries (with a few exceptions) re-export the full DOM Testing Library API, with additional methods built on top.