This rule raises an issue when a Testing Library synchronous query is awaited.
Testing Library provides three query families with different return behavior:
getBy* and getAllBy* return the matching element or elements immediately, or throw when the match fails.queryBy* and queryAllBy* return the matching element or elements immediately, or null / an empty array
when nothing matches.findBy* and findAllBy* return a promise and retry until the element appears or the timeout elapses.Awaiting a synchronous getBy*, getAllBy*, queryBy*, or queryAllBy* query does not wait for the
DOM to update. The await only wraps an already resolved value, so the test keeps going immediately while the code still reads as if the
query were asynchronous. That can hide the wrong API choice: when the element may appear later, use findBy* or findAllBy*
instead of awaiting a synchronous query.
Remove the unnecessary await when the element is expected to be present already. Use an asynchronous query when the test needs to wait
for the element to become available.
The rule targets awaited built-in synchronous queries that come from supported @testing-library/* imports, including calls on
screen, queries returned by render, and queries returned by within. Custom query helpers, renamed imports, and
same-named APIs from other libraries remain out of scope.
import { screen } from '@testing-library/react';
test('shows the submit button', async () => {
const button = await screen.getByRole('button', { name: 'Submit' }); // Noncompliant: "getByRole" is synchronous and does not wait for the element
expect(button).toBeVisible();
});
import { screen } from '@testing-library/react';
test('shows the submit button', () => {
const button = screen.getByRole('button', { name: 'Submit' }); // Compliant: remove the unnecessary "await" when the element is already present
expect(button).toBeVisible();
});
import { render } from '@testing-library/react';
test('lists the rows', async () => {
const { queryAllByRole } = render(<Table />);
const rows = await queryAllByRole('row'); // Noncompliant: "queryAllByRole" is synchronous and does not wait for the element
expect(rows).toHaveLength(2);
});
import { render } from '@testing-library/react';
test('lists the rows', () => {
const { queryAllByRole } = render(<Table />);
const rows = queryAllByRole('row'); // Compliant: call the synchronous query without "await"
expect(rows).toHaveLength(2);
});
import { screen, within } from '@testing-library/react';
test('shows the image in the reusable block', async () => {
const block = screen.getByTestId('reusable-block');
const image = await within(block).getByLabelText('Image Block. Row 1'); // Noncompliant: "getByLabelText" is synchronous and does not wait for the element
expect(image).toBeVisible();
});
import { screen, within } from '@testing-library/react';
test('shows the image in the reusable block', () => {
const block = screen.getByTestId('reusable-block');
const image = within(block).getByLabelText('Image Block. Row 1'); // Compliant: remove the unnecessary "await" on the "within" query
expect(image).toBeVisible();
});
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('shows the confirmation after save', async () => {
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
const status = await screen.getByRole('status', { name: 'Saved' }); // Noncompliant: "getByRole" is synchronous and does not wait for the element
expect(status).toBeVisible();
});
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('shows the confirmation after save', async () => {
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
const status = await screen.findByRole('status', { name: 'Saved' }); // Compliant: use "findByRole" when the element may appear later
expect(status).toBeVisible();
});