This rule raises an issue when a Testing Library waitFor callback either runs a synchronous query for an element that is expected to
appear, checks that query’s presence, or unnecessarily wraps an asynchronous query that already waits.
waitFor is a general retry utility. It reruns its callback until the callback stops throwing or the timeout elapses. Testing Library’s
findBy* and findAllBy* queries already combine that waiting behavior with the corresponding query, so they express an
element-appearance wait directly.
Wrapping a synchronous getBy* or getAllBy* call in waitFor duplicates that behavior. A standalone
queryBy* or queryAllBy* callback has the opposite problem: those queries return null or an empty array when the
element is absent instead of throwing, so the waitFor callback can resolve before the element appears. In both cases, the asynchronous
query is the appropriate API when the test expects an element to become available.
Using an asynchronous query directly makes the test’s intent clearer: the test is waiting for an element to become available. It also produces the query-specific error message when the element does not appear.
The rule applies to concise arrow callbacks containing a single synchronous query or a presence assertion around one, and to asynchronous arrow
callbacks that await a findBy* or findAllBy* query. Assertions that check another condition, such as whether an element is
disabled, and utilities such as waitForElementToBeRemoved remain valid uses of waitFor.
import { render, screen, waitFor } from '@testing-library/react';
test('shows the confirmation', async () => {
render(<Form />);
await waitFor( // Noncompliant: prefer "findByRole" query over using "waitFor" + "getByRole"
() => screen.getByRole('status', { name: 'Saved' }),
);
});
import { render, screen } from '@testing-library/react';
test('shows the confirmation', async () => {
render(<Form />);
await screen.findByRole('status', { name: 'Saved' });
});
import { screen, waitFor } from '@testing-library/react';
test('shows the confirmation', async () => {
await waitFor(async () => { // Noncompliant: prefer "findByRole" query over using "waitFor" + "findByRole"
const status = await screen.findByRole('status', { name: 'Saved' });
expect(status).toBeVisible();
});
});
import { screen } from '@testing-library/react';
test('shows the confirmation', async () => {
const status = await screen.findByRole('status', { name: 'Saved' });
expect(status).toBeVisible();
});
import { render, screen, waitFor } from '@testing-library/react';
test('shows the confirmation', async () => {
render(<Form />);
await waitFor( // Noncompliant: prefer "findByRole" query over using "waitFor" + "queryByRole"
() => screen.queryByRole('status', { name: 'Saved' }),
);
});
import { render, screen } from '@testing-library/react';
test('shows the confirmation', async () => {
render(<Form />);
await screen.findByRole('status', { name: 'Saved' });
});
findBy
queries