Перейти к содержимому

13. Queries и селекторы

RTL Queries

RTL рекомендует использовать queries в следующем порядке (от лучшего к худшему):

1. getByRole ← семантика, доступность
2. getByLabelText ← форма
3. getByPlaceholderText
4. getByText
5. getByDisplayValue
6. getByAltText ← изображения
7. getByTitle
8. getByTestId ← последний resort

Ищет по ARIA роли — соответствует тому, что видит screen reader.

// Пример разметки
<button>Submit</button>
<a href="/about">About</a>
<h1>Page Title</h1>
<input type="checkbox" />
<img alt="Cat photo" />
<nav>Navigation</nav>
<form>Form</form>
// Queries
screen.getByRole('button', { name: /submit/i });
screen.getByRole('link', { name: /about/i });
screen.getByRole('heading', { name: /page title/i, level: 1 });
screen.getByRole('checkbox');
screen.getByRole('img', { name: /cat photo/i });
screen.getByRole('navigation');
// Все роли: button, link, heading, textbox, checkbox, radio,
// combobox, listbox, option, img, navigation, dialog,
// alert, progressbar, tab, tabpanel, list, listitem...
// Разметка
<label htmlFor="email">Email</label>
<input id="email" type="email" />
// aria-label
<input aria-label="Search" type="search" />
// aria-labelledby
<span id="username-label">Username</span>
<input aria-labelledby="username-label" />
// Queries
screen.getByLabelText('Email');
screen.getByLabelText(/email/i);
screen.getByLabelText('Search');
screen.getByLabelText('Username');
<p>Welcome, Alice!</p>
<button>Click me</button>
screen.getByText('Welcome, Alice!');
screen.getByText(/welcome/i);
screen.getByText(/alice/i, { exact: false }); // частичное совпадение
// getBy*: выбрасывает если не найден / найдено несколько
screen.getByRole('button') // ✅ нашёл один, ❌ если 0 или 2+
// queryBy*: возвращает null если не найден
screen.queryByRole('dialog') // null если нет диалога
expect(screen.queryByText('Error')).not.toBeInTheDocument()
// findBy*: async, ждёт появления элемента
const element = await screen.findByRole('alert') // ждёт до 1000мс
// getAllBy* / queryAllBy* / findAllBy* — множественные результаты
const buttons = screen.getAllByRole('button')
expect(buttons).toHaveLength(3)
// getBy: элемент должен быть в DOM прямо сейчас
const submitBtn = screen.getByRole('button', { name: /submit/i })
// queryBy: элемент может отсутствовать
if (screen.queryByRole('dialog')) {
// диалог открыт
}
expect(screen.queryByText('Error')).not.toBeInTheDocument()
// findBy: элемент появится асинхронно (после загрузки данных)
const loadedContent = await screen.findByText('Loaded!')
// Используй только когда нет семантического способа
<div data-testid="custom-widget">...</div>
screen.getByTestId('custom-widget')
// Лучше добавь роль через aria
<div role="region" aria-label="Stats Widget">...</div>
screen.getByRole('region', { name: /stats widget/i })
<ul>
<li data-testid="user-1">
<span>Alice</span>
<button>Edit</button>
</li>
<li data-testid="user-2">
<span>Bob</span>
<button>Edit</button>
</li>
</ul>
import { within } from '@testing-library/react';
// Найти кнопку Edit только для Alice
const aliceRow = screen.getByTestId('user-1');
const editButton = within(aliceRow).getByRole('button', { name: /edit/i });
await userEvent.click(editButton);
test('debug example', () => {
render(<MyComponent />);
screen.debug(); // вывести весь DOM
screen.debug(screen.getByRole('button')); // только элемент
});
  1. Найди 5 разных элементов на странице используя getByRole
  2. Протестируй условное отображение с queryBy
  3. Протестируй динамически появляющийся элемент (setTimeout) с findBy
  • Приоритет: Role > LabelText > Text > TestId
  • getBy — синхронный, обязательный
  • queryBy — синхронный, опциональный
  • findBy — асинхронный, ждёт появления
  • within() — поиск в конкретном контейнере