ESC

Type to search the knowledge base.

Testing Library Queries Priority

Query UI like a user — getByRole first, test ids last, and which variant (get/query/find) to use for presence and async.

beginner3 min read
  • testing
  • testing-library

Testing Library’s default recommendation is a priority list of queries. Follow it and your tests stay close to assistive tech and human users. Ignore it and you test CSS class soup.

Docs: About Queries, Cheatsheet.

The priority list

  1. Accessible queries
    • getByRole
    • getByLabelText
    • getByPlaceholderText
    • getByText
    • getByDisplayValue
  2. Semantic extras
    • getByAltText
    • getByTitle
  3. Test IDs
    • getByTestId
// Prefer
screen.getByRole('button', { name: /sign in/i });
screen.getByLabelText(/email/i);
screen.getByRole('heading', { level: 1, name: /settings/i });

// Avoid as default
screen.getByTestId('email-input');
container.querySelector('.btn-primary');

get vs query vs find

Prefix Throws if missing? Async? Use
getBy Yes No Element should already be there
queryBy No (null) No Assert absence
findBy Yes (after timeout) Yes Element appears after wait
// present
expect(screen.getByRole('alert')).toHaveTextContent(/error/i);

// absent
expect(screen.queryByRole('alert')).not.toBeInTheDocument();

// eventually present
expect(await screen.findByRole('alert')).toBeInTheDocument();

getAllBy / queryAllBy / findAllBy for lists.

Roles you should know

UI Role
Button button
Link link
Text field textbox, spinbutton, …
Checkbox checkbox
Dialog dialog
Nav navigation
Main main

If getByRole can’t find it, the control may lack an accessible name — fix the UI.

// Broken for AT and tests
<div onClick={save}>Save</div>

// Fixed
<button type="button" onClick={save}>Save</button>

Name option

screen.getByRole('button', { name: /continue/i });
screen.getByRole('textbox', { name: /full name/i });

Name comes from label, text content, aria-label, etc. — accessible names.

Within scopes

const dialog = screen.getByRole('dialog');
within(dialog).getByRole('button', { name: /confirm/i });

Avoid ambiguous matches in large pages.

Footguns

  1. getByText for buttons — prefer role + name.
  2. Regex too loose (/a/) matching noise.
  3. Using queryBy for elements that must exist — fails soft.
  4. findBy without await.

Interview out-loud answer

“I use Testing Library’s query priority: roles and labels first, test ids last. get for present, query for absent, find for async. If role queries fail, I treat it as a possible a11y bug.”

Hidden elements

By default, Testing Library queries ignore styles that hide elements. If you must assert a hidden accessible name (visually hidden labels), check docs for hidden: true options. Prefer visible labels when possible so users and tests share the same path.

Extra practice

Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.

Notes from real codebases

Teams that succeed here keep the rules mechanical: lint where possible, CI for the rest, and a short human checklist for what automation cannot see. Document exceptions with an owner name and a removal date so “temporary” escapes do not become permanent architecture.

Further reading

Related guides