This rule raises an issue when a Testing Library synchronous query is awaited.

Why is this an issue?

Testing Library provides three query families with different return behavior:

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.

Code examples

Noncompliant code example

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();
});

Compliant solution

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();
});

Noncompliant code example

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);
});

Compliant solution

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);
});

Noncompliant code example

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();
});

Compliant solution

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();
});

Noncompliant code example

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();
});

Compliant solution

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();
});

Resources

Documentation