This rule raises an issue when a call to fireEvent, userEvent, or render is placed directly inside the
callback of waitFor.
waitFor retries its callback on an interval and on every DOM mutation observed in its container, until the callback stops throwing or
a timeout is reached; the callback runs immediately and then repeats, so it can execute an unknown number of times before resolving.
Placing a side effect such as fireEvent, userEvent, or render directly inside that callback means the side
effect reruns on every retry, not just once. A click, a key press, a form submission, or a re-render happening several times instead of once can
change what the test actually exercises, hide a real bug behind a passing assertion, or make the test intermittently fail depending on how many
retries occur.
Move the side effect out of the callback to fix this: call it once before waitFor, or run it inside a .then() chained
after waitFor() if it must happen only once the wait resolves. A side effect placed inside such a .then() is not affected by
this issue, since it only runs once after the wait resolves.
Retrying a side effect this way is rarely intentional, so there is no accepted trade-off to weigh here. If a click, a submission, or a render
genuinely needs to happen more than once, issue those calls explicitly outside waitFor instead of relying on its retry behavior, so the
number of times they run stays visible and does not depend on retry timing.
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
test('submits the form', async () => {
render(<Form />);
await waitFor(() => {
fireEvent.click(screen.getByRole('button', { name: 'Submit' })); // Noncompliant: the click can be repeated every time this callback is retried
});
expect(screen.getByText('Success')).toBeInTheDocument();
});
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
test('submits the form', async () => {
render(<Form />);
fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
await waitFor(() => {
expect(screen.getByText('Success')).toBeInTheDocument();
});
});