This error occurs in React Testing Library when you pass a string instead of a DOM element as the container argument to query functions. It happens when using incorrect syntax with screen queries, within() helper, or container-based queries. The fix involves understanding the different query patterns and ensuring you pass actual DOM elements, not CSS selector strings.
The "Container type is not a DOM element, string provided instead" error is thrown by React Testing Library when you pass a string (like a CSS selector) where a DOM element is expected. Testing Library's query functions are designed to work with actual DOM nodes, not selector strings, to encourage testing from the user's perspective. This error typically occurs in three scenarios: 1. **Screen queries with container argument**: Trying to pass a container to screen.* queries (which are already bound to document.body) 2. **Container-based queries with strings**: Passing a CSS selector string instead of a DOM element to getByText(container, ...) style queries 3. **within() helper with strings**: Using within() with a string selector instead of a DOM element The philosophy behind Testing Library is to test components as users interact with them - users don't see CSS selectors, they see text, labels, and roles. By requiring DOM elements, the library encourages semantic, accessible queries.
When querying the entire document, use the screen object which is pre-bound to document.body. Don't pass a container argument:
// ✅ Correct - screen queries are already bound to document.body
const element = screen.getByText('Submit');
const button = screen.getByRole('button', { name: 'Save' });
// ❌ Incorrect - don't pass container to screen queries
const element = screen.getByText(document.body, 'Submit');
const button = screen.getByRole('#app', 'button', { name: 'Save' });The screen object is imported from @testing-library/react and provides all queries pre-bound to document.body.
If you need to query within a specific container, first get the DOM element, then pass it to the query:
import { getByText } from '@testing-library/dom';
// ✅ Correct - pass DOM element
const container = document.querySelector('#app');
if (container) {
const element = getByText(container, 'Submit');
}
// ❌ Incorrect - passing string selector
const element = getByText('#app', 'Submit'); // Error: string instead of DOM element
// ✅ Also correct with render() return value
const { container } = render(<MyComponent />);
const element = getByText(container, 'Submit');Always ensure you have a valid DOM element before passing it to container-based queries.
The within() helper requires a DOM element, not a string:
import { within } from '@testing-library/dom';
// ✅ Correct - pass DOM element to within()
const container = document.querySelector('#app');
const { getByText } = within(container);
const element = getByText('Submit');
// ✅ Also correct with render() result
const { getByText } = within(container); // container from render()
// ❌ Incorrect - passing string to within()
const { getByText } = within('#app'); // Error: string passed
// ✅ Using within with screen for scoped queries
const modal = screen.getByRole('dialog');
const { getByText: getByTextInModal } = within(modal);
const button = getByTextInModal('Close');within() creates a scoped query object bound to a specific container.
When using render() from React Testing Library, it returns an object with container and other utilities. Use them properly:
import { render } from '@testing-library/react';
// ✅ Correct usage
const { container, getByText } = render(<MyComponent />);
// Option 1: Use bound queries (container is already bound)
const element = getByText('Submit');
// Option 2: Use container for scoped queries
const innerElement = getByText(container, 'Submit');
// Option 3: Use screen for document queries
const screenElement = screen.getByText('Global element');
// ❌ Common mistake - passing container string
const wrong = getByText('#root', 'Submit'); // Error
// ✅ Proper pattern with multiple containers
const { container: container1 } = render(<Component1 />);
const { container: container2 } = render(<Component2 />);
const element1 = getByText(container1, 'Text 1');
const element2 = getByText(container2, 'Text 2');Testing Library provides three types of queries with different patterns:
1. **screen.* queries** - Bound to document.body, no container needed
import { screen } from '@testing-library/react';
const element = screen.getByText('Text');2. **getBy*(container, ...) queries** - Require container as first argument
import { getByText } from '@testing-library/dom';
const element = getByText(container, 'Text');3. **within(container).* queries** - Create scoped query object
import { within } from '@testing-library/dom';
const { getByText } = within(container);
const element = getByText('Text');Key differences:
- screen.*: No container argument, searches entire document
- getBy*(container, ...): First argument must be DOM element
- within(container).*: Creates new query object bound to container
Always ensure you're using the right pattern for your use case.
Here are common problematic patterns and their fixes:
// ❌ Problem: String selector with getByText
const element = getByText('#app', 'Submit');
// ✅ Fix: Get DOM element first
const container = document.querySelector('#app');
const element = getByText(container, 'Submit');
// ❌ Problem: Passing container to screen query
const element = screen.getByText(container, 'Submit');
// ✅ Fix: Use either screen or container-based query
const element = screen.getByText('Submit'); // OR
const element = getByText(container, 'Submit');
// ❌ Problem: within() with string
const { getByText } = within('#modal');
// ✅ Fix: Get DOM element first
const modal = document.querySelector('#modal');
const { getByText } = within(modal);
// ❌ Problem: Null container from querySelector
const container = document.querySelector('.not-exist');
const element = getByText(container, 'Text'); // container is null!
// ✅ Fix: Check for null
const container = document.querySelector('.not-exist');
if (container) {
const element = getByText(container, 'Text');
}### Testing Library Philosophy
This error stems from Testing Library's core philosophy: test from the user's perspective. Users don't interact with CSS selectors, class names, or IDs - they interact with text, labels, roles, and visible UI elements. By requiring DOM elements instead of selector strings, Testing Library encourages:
1. Semantic queries: Find elements by their accessible names (getByRole, getByLabelText)
2. Accessibility-focused testing: Ensure your UI is accessible to all users
3. Implementation-agnostic tests: Tests don't break when CSS classes change
4. User-centric verification: Verify what users actually see and interact with
### When to Use Different Query Patterns
**Use screen.* when:**
- Testing the entire rendered output
- Elements are rendered directly to document.body
- You don't need to scope queries to a specific container
Use container-based queries when:
- You need to query within a specific DOM element
- Testing portal content or nested components
- Working with multiple independent render calls
Use within() when:
- You need multiple queries within the same container
- Creating reusable test utilities for specific components
- Improving test readability with scoped queries
### Migration from Other Libraries
If migrating from enzyme or libraries that use selector strings:
1. Replace wrapper.find('.selector') with screen.getByText('Text') or getByRole
2. Replace container.querySelector('.class') with getByText(container, 'Text')
3. Use getByTestId for stable element references when needed
4. Consider whether you're testing implementation details vs user behavior
### Performance Considerations
- screen queries: Search entire document, can be slower for large pages
- container queries: Limited to specific element, faster for scoped searches
- within(): Creates new query object, useful for repeated queries on same container
### Common Related Errors
- "Unable to find an element by: [text]" - Element doesn't exist in the container
- "Found multiple elements with the text" - Use more specific queries
- "TestingLibraryElementError" - General query error, check query syntax
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