This error occurs when React Testing Library's waitFor utility times out while waiting for an element to appear in the DOM. The default timeout is 1000ms, and this error means the element didn't appear within that time. Common causes include async actions not being awaited, fake timers interfering, or elements not rendering as expected.
The `waitFor` utility in React Testing Library is used to wait for asynchronous changes in your component. It repeatedly runs a callback function until either the callback succeeds (doesn't throw) or the timeout is reached (default: 1000ms). When you see "waitFor timeout: timed out waiting for element to appear", it means: 1. Your test expected an element to appear in the DOM 2. The `waitFor` utility checked repeatedly for the element 3. After 1000ms (or your custom timeout), the element still wasn't found 4. The test fails with this timeout error This is a common integration testing issue where the component's state changes aren't happening as expected, or the test isn't properly waiting for asynchronous operations to complete.
With newer versions of @testing-library/user-event, all actions must be awaited:
// ❌ Wrong - not awaited
userEvent.click(button);
// ✅ Correct - awaited
await userEvent.click(button);Update all user-event calls in your test:
// Before
userEvent.type(input, "test");
userEvent.click(button);
// After
await userEvent.type(input, "test");
await userEvent.click(button);If you're using jest.useFakeTimers(), it can interfere with waitFor. The waitFor utility uses real timers internally.
Solution 1: Use modern fake timers API:
// Use modern timers
jest.useFakeTimers({ doNotFake: ['setImmediate'] });
// Or use legacy timers
jest.useFakeTimers('legacy');Solution 2: Restore real timers for waitFor tests:
beforeEach(() => {
jest.useRealTimers(); // Use real timers for waitFor tests
});
// Or switch back to real timers before waitFor
jest.useRealTimers();
await waitFor(() => {
expect(getByText('Hello')).toBeInTheDocument();
});
jest.useFakeTimers(); // Switch back if neededIf your element legitimately takes longer to appear, increase the timeout:
// Increase to 3000ms (3 seconds)
await waitFor(() => {
expect(getByText('Loading complete')).toBeInTheDocument();
}, { timeout: 3000 });
// Or globally configure waitFor options
import { configure } from '@testing-library/dom';
configure({ asyncUtilTimeout: 3000 });Note: Increasing timeout should be a last resort. First fix the underlying issue causing the delay.
The error message includes the container's state. You can also manually debug:
// Log what's actually in the DOM
console.log(container.innerHTML);
// Or use screen.debug()
import { screen } from '@testing-library/react';
screen.debug();
// Check if element exists with different query
const element = queryByText('Some text');
if (!element) {
console.log('Element not found. DOM contains:', container.innerHTML);
}This helps identify if:
- The element renders with different text
- The element is in a different container
- The element never renders at all
Ensure all async operations complete before checking for elements:
// Mock API calls
jest.spyOn(api, 'fetchData').mockResolvedValue({ data: 'test' });
// Wait for API call to complete
await waitFor(() => {
expect(api.fetchData).toHaveBeenCalled();
});
// Then check for element
await waitFor(() => {
expect(getByText('Data loaded')).toBeInTheDocument();
});
// For React Query, wait for queries to settle
await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});Make sure you're using the right query and the element actually exists:
// Common mistakes:
// 1. Using getBy when element might not exist yet
// ❌ getByText throws immediately if not found
expect(getByText('Hello')).toBeInTheDocument();
// ✅ queryByText returns null, works with waitFor
await waitFor(() => {
expect(queryByText('Hello')).toBeInTheDocument();
});
// 2. Wrong text matching
// Element has "Hello World" but you query for "Hello"
await waitFor(() => {
expect(getByText('Hello World')).toBeInTheDocument();
});
// 3. Case sensitivity
await waitFor(() => {
expect(getByText('hello', { exact: false })).toBeInTheDocument();
});### Understanding waitFor Behavior
waitFor runs your callback function repeatedly until it succeeds (doesn't throw) or times out. Key behaviors:
1. Default timeout: 1000ms
2. Interval between retries: 50ms
3. Callback must throw: Returning false or a falsy value won't trigger retries
4. Container state in error: The error message includes what was actually rendered
### Common Pitfalls
setImmediate conflicts: If you have global.setImmediate = jest.useRealTimers; in your Jest config, remove it as it can break waitFor.
Act warnings: If you see "Warning: An update to Component inside a test was not wrapped in act(...)", you may need to use waitFor differently:
// Instead of this causing act warnings
fireEvent.click(button);
await waitFor(() => {
expect(getByText('Updated')).toBeInTheDocument();
});
// Use findBy which wraps waitFor
fireEvent.click(button);
const element = await findByText('Updated');Custom waitFor implementations: Avoid creating custom waiting logic. Use the built-in utilities:
// ❌ Custom sleep
await new Promise(resolve => setTimeout(resolve, 100));
// ✅ Built-in waitFor
await waitFor(() => {
expect(getByText('Ready')).toBeInTheDocument();
});### Performance Considerations
If tests are consistently timing out:
1. Check for infinite loops or state updates in components
2. Ensure mocked APIs resolve promptly
3. Consider if the component is doing too much work synchronously
4. Use jest.setTimeout() for overall test timeout if needed
### Version-Specific Notes
- @testing-library/user-event v14+: All actions must be awaited
- React 18: Ensure you're using @testing-library/react v13+ for proper concurrent mode support
- Jest 27+: Fake timers behavior changed, use jest.useFakeTimers('legacy') if needed
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite