Simple and complete DOM testing utilities that encourage good testing practices.
npx @tessl/cli install tessl/npm-testing-library--dom@10.4.1Simple and complete DOM testing utilities that encourage good testing practices by querying the DOM in the way users find elements. Promotes accessibility-focused testing through queries based on ARIA roles, labels, text content, and other accessible attributes.
{
"name": "@testing-library/dom",
"version": "10.4.1",
"install": "npm install --save-dev @testing-library/dom"
}import {screen, within, waitFor, fireEvent} from '@testing-library/dom';
// All query methods
import {
getByRole, queryByRole, findByRole,
getByLabelText, queryByLabelText, findByLabelText,
getByPlaceholderText, queryByPlaceholderText, findByPlaceholderText,
getByText, queryByText, findByText,
getByAltText, queryByAltText, findByAltText,
getByTitle, queryByTitle, findByTitle,
getByDisplayValue, queryByDisplayValue, findByDisplayValue,
getByTestId, queryByTestId, findByTestId
} from '@testing-library/dom';
// Plural variants
import {
getAllBy*, queryAllBy*, findAllBy*
} from '@testing-library/dom';
// Namespace imports for grouped access
import {queries, queryHelpers} from '@testing-library/dom';
// Access queries through namespace
const element = queries.getByRole(container, 'button', {name: /submit/i});
// Access query helpers through namespace
const customQuery = queryHelpers.buildQueries(...);Every query has three variants with different error handling:
getBy* - Returns element, throws if 0 or 2+ matches foundqueryBy* - Returns element or null if not found, throws if 2+ foundfindBy* - Returns Promise, waits up to 1000ms (async)Plural variants return arrays and have different throwing behavior.
Use queries in this order (most accessible to least):
getByRole - Accessible to everyone (ARIA roles)getByLabelText - Form fieldsgetByPlaceholderText - Form alternativegetByText - Non-interactive contentgetByDisplayValue - Current form valuesgetByAltText - ImagesgetByTitle - Title attributegetByTestId - Last resort// Use screen - queries entire document
const button = screen.getByRole('button', {name: /submit/i});
// Use within for scoped queries
const form = screen.getByRole('form');
const emailInput = within(form).getByLabelText('Email');import {screen, fireEvent, waitFor} from '@testing-library/dom';
// Basic query
const button = screen.getByRole('button', {name: /submit/i});
fireEvent.click(button);
// Form interaction
const email = screen.getByLabelText('Email');
fireEvent.change(email, {target: {value: 'user@example.com'}});
// Async content
await waitFor(() => {
expect(screen.getByText('Success')).toBeInTheDocument();
});
// Or use findBy directly
const message = await screen.findByText('Success');waitFor: Poll with MutationObserver supportwaitForElementToBeRemoved: Wait for element removalfireEvent: Fire DOM events on elementscreateEvent: Create events without firingprettyDOM, logDOM, screen.debug, logTestingPlaygroundURLconfigure, getConfiggetRoles, logRoles, isInaccessible, computeHeadingLevelbuildQueries, queryByAttribute, queryAllByAttributegetNodeText, getDefaultNormalizergetSuggestedQuery for better query recommendationsExtract text content as users perceive it.
function getNodeText(node: HTMLElement): string;Usage:
import {getNodeText} from '@testing-library/dom';
const element = screen.getByRole('button');
const text = getNodeText(element); // Returns visible text contentText normalization for customizing query behavior.
function getDefaultNormalizer(
options?: DefaultNormalizerOptions
): NormalizerFn;
interface DefaultNormalizerOptions {
trim?: boolean; // Default: true
collapseWhitespace?: boolean; // Default: true
}
type NormalizerFn = (text: string) => string;Usage:
import {getDefaultNormalizer, getByText} from '@testing-library/dom';
// Custom normalizer
const normalizer = getDefaultNormalizer({trim: false});
const element = getByText(container, 'Text', {normalizer});
// Case-insensitive matching
const caseInsensitive = (text) => text.toLowerCase();
const element = getByText(container, 'submit', {normalizer: caseInsensitive});Get suggested better queries for improving test quality.
function getSuggestedQuery(
element: HTMLElement,
variant?: Variant,
method?: Method
): Suggestion | undefined;
interface Suggestion {
queryName: string;
queryMethod: string;
queryArgs: QueryArgs;
variant: string;
warning?: string;
toString(): string;
}
type Variant = 'find' | 'findAll' | 'get' | 'getAll' | 'query' | 'queryAll';
type Method = 'Role' | 'LabelText' | 'PlaceholderText' | 'Text' |
'DisplayValue' | 'AltText' | 'Title' | 'TestId';Usage:
import {getSuggestedQuery} from '@testing-library/dom';
const button = document.querySelector('button');
const suggestion = getSuggestedQuery(button, 'get');
if (suggestion) {
console.log(suggestion.toString());
// "getByRole('button', {name: /submit/i})"
}
// Use with throwSuggestions config
import {configure} from '@testing-library/dom';
configure({throwSuggestions: true});
// Errors now include better query suggestionsAll APIs are fully typed. The library provides TypeScript definitions with generic types for element narrowing.
import {screen} from '@testing-library/dom';
// Automatically typed as HTMLElement
const button = screen.getByRole('button');
// Narrow to specific element type
const button = screen.getByRole('button') as HTMLButtonElement;
// Or use generic
const button = screen.getByRole<HTMLButtonElement>('button');import {screen, fireEvent} from '@testing-library/dom';
// Fill form
fireEvent.change(screen.getByLabelText('Name'), {target: {value: 'John'}});
fireEvent.change(screen.getByLabelText('Email'), {target: {value: 'john@example.com'}});
fireEvent.click(screen.getByRole('button', {name: /submit/i}));
// Verify result
expect(screen.getByText('Thank you')).toBeInTheDocument();import {screen, within, fireEvent} from '@testing-library/dom';
fireEvent.click(screen.getByRole('button', {name: /open/i}));
const modal = screen.getByRole('dialog');
expect(within(modal).getByText('Modal Title')).toBeInTheDocument();
fireEvent.click(within(modal).getByRole('button', {name: /close/i}));import {screen, fireEvent, waitFor} from '@testing-library/dom';
fireEvent.click(screen.getByRole('button', {name: /load/i}));
// Wait for loading to complete
await waitFor(() => {
expect(screen.queryByText('Loading')).not.toBeInTheDocument();
});
// Verify loaded content
expect(screen.getByText('Data loaded')).toBeInTheDocument();All text-based queries accept a Matcher type:
type Matcher = string | RegExp | number | MatcherFunction;
type MatcherFunction = (content: string, element: Element | null) => boolean;
type ByRoleMatcher = ARIARole | string;
// Matcher options
interface MatcherOptions {
exact?: boolean; // Exact string match (default: false)
trim?: boolean; // Trim whitespace (default: true)
collapseWhitespace?: boolean; // Collapse whitespace (default: true)
normalizer?: NormalizerFn; // Custom text normalizer
suggest?: boolean; // Include query suggestions in errors
}
interface SelectorMatcherOptions extends MatcherOptions {
selector?: string; // CSS selector to narrow results
ignore?: boolean | string; // CSS selectors to ignore
}Examples:
// String (substring match)
getByText(container, 'Submit');
// Regex
getByText(container, /submit/i);
// Number
getByText(container, 42);
// Function
getByText(container, (content, el) => content.startsWith('Total:'));
// Exact match
getByText(container, 'Submit', {exact: true});
// With selector
getByText(container, 'Click me', {selector: 'button'});
// With normalizer
getByText(container, 'submit', {
normalizer: str => str.toLowerCase()
});Common type definitions used throughout the library:
// Query function types
type BoundFunction<T> = T extends (
container: HTMLElement,
...args: infer P
) => infer R
? (...args: P) => R
: never;
interface Queries {
[T: string]: Query;
}
type Query = (
container: HTMLElement,
...args: any[]
) => Error | HTMLElement | HTMLElement[] | Promise<HTMLElement[]> | Promise<HTMLElement> | null;
// Wait options for async utilities
interface waitForOptions {
container?: HTMLElement; // Element to observe (default: document)
timeout?: number; // Max wait in ms (default: 1000)
interval?: number; // Polling interval in ms (default: 50)
onTimeout?: (error: Error) => Error; // Custom timeout error
mutationObserverOptions?: MutationObserverInit;
}
// Query arguments
type QueryArgs = [string, QueryOptions?];
interface QueryOptions {
[key: string]: RegExp | boolean;
}