How to Query Elements in React Testing Library

Find elements the way users do with getByRole and getByLabelText, and know when to reach for queryBy, findBy, within, and test IDs.

7 min read

This guide shows how to query elements with React Testing Library, the same way a user or a screen reader would. Queries find elements after a component renders, and choosing the right one keeps tests tied to real behavior instead of markup.

The three query families

Every query has a single form and a plural form, and the prefix changes what happens when nothing matches.

PrefixNo matchMore than one matchAsync
getBythrowsthrowsno
queryByreturns nullthrowsno
findBythrowsthrowsyes
getAllBythrowsreturns arrayno
queryAllByreturns empty arrayreturns arrayno
findAllBythrowsreturns arrayyes

Use getBy for elements that must be there, queryBy only when you are asserting absence, and findBy when the element appears after async work. If you are new to the library, start with the React Testing Library tutorial. The findBy family is covered with async component testing.

Query by role first

The getByRole query matches elements exposed in the accessibility tree. A name option filters by accessible name, which usually comes from text content, a label, or an aria label.

App.jsxApp.jsx
function Toolbar() {
  return <button type="button">Save changes</button>;
}

The test finds the button by its role and its visible name, which is exactly how a user and a screen reader locate it.

App.jsxApp.jsx
import { render, screen } from "@testing-library/react";
import Toolbar from "./Toolbar";
 
test("finds the save button by its role", () => {
  render(<Toolbar />);
 
  expect(screen.getByRole("button", { name: "Save changes" })).toBeEnabled();
});

If getByRole cannot find an element, the UI is often missing an accessible name. The better fix is to improve the component rather than switch to a weaker query.

Form fields use labels

Form controls should be found through their labels, because that is how users navigate forms.

App.jsxApp.jsx
function SignUp() {
  return (
    <form>
      <label htmlFor="email">Email</label>
      <input id="email" type="email" />
    </form>
  );
}

The getByLabelText query matches the label that is associated with the control through the htmlFor and id pair, exactly as a browser would resolve it.

App.jsxApp.jsx
import { render, screen } from "@testing-library/react";
import SignUp from "./SignUp";
 
test("finds the email input by label", () => {
  render(<SignUp />);
 
  expect(screen.getByLabelText("Email")).toBeRequired();
});

The visible result is a found input, and the assertion confirms the field is required.

Text and display value

Outside forms, visible text is the main way users locate content. The getByText query finds headings, paragraphs, and buttons by their text, while getByDisplayValue finds a control by its current value.

App.jsxApp.jsx
test("shows the current status", () => {
  render(<Status value="Signed in" />);
 
  expect(screen.getByText("Signed in")).toBeVisible();
});

Text matching is exact and case-sensitive by default. Pass a regular expression for partial matches, which expresses intent more clearly than a loose string match. The visible text is what a user reads, so it is the most direct target for an assertion.

Scope with within

The screen object queries the whole document. When a page has many repeated elements, scope the query to one section with the within helper.

App.jsxApp.jsx
import { render, screen, within } from "@testing-library/react";
 
test("reads the count inside one card", () => {
  render(<Dashboard />);
 
  const card = screen.getByRole("region", { name: "Revenue" });
  expect(within(card).getByText("$1,240")).toBeVisible();
});

The within helper binds queries to the Revenue region, so the text lookup only sees that card. The test does not collide with another card showing the same number.

Test IDs are the last resort

The getByTestId query matches data-testid attributes. Users cannot see or hear these, so use them only when no role, label, or text makes sense.

App.jsxApp.jsx
test("finds a loading spinner", () => {
  render(<Uploader />);
 
  expect(screen.getByTestId("upload-spinner")).toBeInTheDocument();
});

A spinner has no meaningful text, so a test ID is a reasonable contract here. Reserve it for cases like this, and prefer a real accessible status message when one exists.

Common mistakes

  • Using queryBy where getBy would fail louder and catch a real regression.
  • Using getByTestId for a button or input that already has an accessible name.
  • Forgetting the within helper and matching an element from the wrong section.
  • Waiting with getBy when the element arrives after a network response.

The next natural step is performing the interactions that follow a query. See how to test user events in React.

Rune AI

Rune AI

Key Insights

  • Prefer getByRole, then getByLabelText, then text and display value queries.
  • getBy throws on no match, queryBy returns null, findBy retries asynchronously.
  • Use queryBy only when asserting an element is absent.
  • Use within to scope queries to one section of the page.
  • Use getByTestId only as an escape hatch.
RunePowered by Rune AI

Frequently Asked Questions

Which query should I use first?

Use getByRole with a name option for almost everything. It matches the accessibility tree, so it reflects what users and assistive technology can find.

When should I use queryBy instead of getBy?

Use queryBy when you are asserting that an element is absent. It returns null instead of throwing, so the assertion can check for nothing.

Conclusion

Query the rendered DOM the way a user perceives it. Prefer roles and labels, scope with within, and reserve test IDs for cases no accessible query can reach.