Instrumented version of Testing Library for Storybook Interactions addon
—
Configuration options and utility functions for customizing Testing Library behavior and building custom queries. These utilities provide advanced functionality for debugging, configuration, and extending the library's capabilities.
Global configuration options for customizing Testing Library behavior.
/**
* Configure global Testing Library options
* @param options - Configuration options to set
*/
function configure(options: ConfigureOptions): void;
/**
* Get current Testing Library configuration
* @returns Current configuration object
*/
function getConfig(): Config;
/**
* Configuration options for Testing Library
*/
interface ConfigureOptions {
/** Custom attribute to use for test IDs (default: 'data-testid') */
testIdAttribute?: string;
/** Default timeout for async utilities in milliseconds (default: 1000) */
asyncUtilTimeout?: number;
/** Whether computed styles support pseudo-elements (default: false) */
computedStyleSupportsPseudoElements?: boolean;
/** Whether hidden elements should be ignored by default (default: false) */
defaultHidden?: boolean;
/** Show original stack trace in error messages (default: false) */
showOriginalStackTrace?: boolean;
/** Custom normalizer function for text matching */
defaultNormalizer?: (text: string) => string;
/** Ignore specific elements in queries (default: 'script, style') */
defaultIgnore?: string;
/** Throw errors on multiple elements found (default: true) */
throwSuggestions?: boolean;
}
/**
* Current configuration object
*/
interface Config {
testIdAttribute: string;
asyncUtilTimeout: number;
computedStyleSupportsPseudoElements: boolean;
defaultHidden: boolean;
showOriginalStackTrace: boolean;
defaultNormalizer: (text: string) => string;
defaultIgnore: string;
throwSuggestions: boolean;
}Utilities for building custom query functions that follow Testing Library patterns.
/**
* Build a complete set of query functions from a base queryAllBy function
* @param queryAllBy - Function that returns all matching elements
* @param getMultipleError - Function to generate error when multiple elements found
* @param getMissingError - Function to generate error when no elements found
* @returns Complete set of query functions (getBy, getAllBy, queryBy, queryAllBy, findBy, findAllBy)
*/
function buildQueries<T extends (...args: any[]) => HTMLElement[]>(
queryAllBy: T,
getMultipleError: (container: HTMLElement, ...args: Parameters<T>) => string,
getMissingError: (container: HTMLElement, ...args: Parameters<T>) => string
): QueryHelpers<T>;
/**
* Generated query functions from buildQueries
*/
interface QueryHelpers<T extends (...args: any[]) => HTMLElement[]> {
queryBy: (...args: Parameters<T>) => HTMLElement | null;
queryAllBy: T;
getBy: (...args: Parameters<T>) => HTMLElement;
getAllBy: T;
findBy: (...args: Parameters<T>) => Promise<HTMLElement>;
findAllBy: (...args: Parameters<T>) => Promise<HTMLElement[]>;
}
/**
* Object containing query helper functions
*/
declare const queryHelpers: {
buildQueries: typeof buildQueries;
getElementError: (message: string, container: HTMLElement) => Error;
wrapAllByQueryWithSuggestion: <T extends (...args: any[]) => HTMLElement[]>(
query: T,
queryAllByName: string,
container: HTMLElement
) => T;
};
/**
* Pre-built queries object containing all standard query functions
*/
declare const queries: {
queryByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement | null;
queryAllByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement[];
getByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement;
getAllByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => HTMLElement[];
findByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => Promise<HTMLElement>;
findAllByRole: (container: HTMLElement, role: string, options?: ByRoleOptions) => Promise<HTMLElement[]>;
// ... similar patterns for Text, TestId, LabelText, PlaceholderText, AltText, Title, DisplayValue
};Utilities for working with element text content and normalization.
/**
* Get the text content of a node, including descendant text
* @param node - Node to get text from
* @returns Text content of the node
*/
function getNodeText(node: Node): string;
/**
* Get the default text normalizer function
* @param options - Normalization options
* @returns Normalizer function
*/
function getDefaultNormalizer(options?: NormalizerOptions): (text: string) => string;
/**
* Options for text normalization
*/
interface NormalizerOptions {
/** Remove leading and trailing whitespace (default: true) */
trim?: boolean;
/** Collapse consecutive whitespace to single spaces (default: true) */
collapseWhitespace?: boolean;
}Utilities for working with accessibility features and ARIA roles.
/**
* Get all available ARIA roles for an element
* @param element - Element to get roles for
* @returns Array of available roles
*/
function getRoles(element: HTMLElement): string[];
/**
* Check if an element is inaccessible (hidden from screen readers)
* @param element - Element to check
* @returns True if element is inaccessible
*/
function isInaccessible(element: HTMLElement): boolean;
/**
* Get suggested query method for finding an element
* @param element - Element to get suggestion for
* @param variant - Query variant to suggest (get, query, find)
* @param method - Specific method preference
* @returns Suggested query method as string
*/
function getSuggestedQuery(
element: HTMLElement,
variant?: 'get' | 'query' | 'find',
method?: string
): string;Utilities for debugging and inspecting DOM elements during testing.
/**
* Pretty-print DOM elements for debugging
* @param element - Element to pretty-print
* @param maxLength - Maximum length of output (default: 7000)
* @param options - Pretty printing options
* @returns Formatted string representation of element
*/
function prettyDOM(
element?: Element | HTMLDocument | null,
maxLength?: number,
options?: PrettyDOMOptions
): string;
/**
* Log DOM tree to console
* @param element - Element to log (defaults to document.body)
* @param maxLength - Maximum length of output
* @param options - Pretty printing options
*/
function logDOM(
element?: Element | HTMLDocument | null,
maxLength?: number,
options?: PrettyDOMOptions
): void;
/**
* Log available ARIA roles to console
* @param element - Container element (defaults to document.body)
*/
function logRoles(element?: Element | HTMLDocument): void;
/**
* Options for pretty DOM printing
*/
interface PrettyDOMOptions {
/** Highlight matching elements */
highlight?: boolean;
/** Filter out certain elements */
filterNode?: (node: Node) => boolean;
}
/**
* Pretty format values for display (from pretty-format library)
* @param value - Value to format
* @param options - Formatting options
* @returns Formatted string representation
*/
function prettyFormat(value: any, options?: PrettyFormatOptions): string;
/**
* Options for pretty formatting
*/
interface PrettyFormatOptions {
/** Maximum depth to traverse (default: Infinity) */
maxDepth?: number;
/** Minimum number of elements to trigger multi-line (default: Infinity) */
min?: boolean;
/** Include function names (default: false) */
printFunctionName?: boolean;
}Utilities for error handling and reporting.
/**
* Get detailed error message for element queries
* @param message - Base error message
* @param container - Container element that was searched
* @returns Enhanced error object with debugging information
*/
function getElementError(message: string, container: HTMLElement): Error;import { configure, getConfig } from "@storybook/testing-library";
export const ConfigurationExample = {
play: async () => {
// Configure Testing Library
configure({
testIdAttribute: 'data-test-id', // Use custom test ID attribute
asyncUtilTimeout: 2000, // Increase default timeout to 2 seconds
defaultHidden: true, // Include hidden elements by default
showOriginalStackTrace: true // Show full stack traces in errors
});
// Get current configuration
const config = getConfig();
console.log('Current timeout:', config.asyncUtilTimeout);
console.log('Test ID attribute:', config.testIdAttribute);
}
};import { configure, getDefaultNormalizer } from "@storybook/testing-library";
export const CustomNormalizerExample = {
play: async () => {
// Create custom normalizer that removes extra characters
const customNormalizer = getDefaultNormalizer({
trim: true,
collapseWhitespace: true
});
// Apply custom normalizer globally
configure({
defaultNormalizer: (text: string) => {
// First apply default normalization
const normalized = customNormalizer(text);
// Then apply custom logic
return normalized.replace(/[^\w\s]/gi, ''); // Remove special characters
}
});
}
};import { buildQueries, within } from "@storybook/testing-library";
// Build custom query for elements with specific CSS class
const queryAllByClass = (container: HTMLElement, className: string) => {
return Array.from(container.querySelectorAll(`.${className}`));
};
const getMultipleError = (container: HTMLElement, className: string) =>
`Found multiple elements with class: ${className}`;
const getMissingError = (container: HTMLElement, className: string) =>
`Unable to find element with class: ${className}`;
const [
queryByClass,
getAllByClass,
getByClass,
findAllByClass,
findByClass,
queryAllByClass
] = buildQueries(queryAllByClass, getMultipleError, getMissingError);
export const CustomQueriesExample = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Use custom queries (need to bind to container manually)
const errorElement = getByClass(canvasElement, 'error-message');
const warningElements = getAllByClass(canvasElement, 'warning');
const optionalElement = queryByClass(canvasElement, 'optional');
expect(errorElement).toBeInTheDocument();
expect(warningElements).toHaveLength(2);
expect(optionalElement).toBeNull();
}
};import { within, prettyDOM, logDOM, logRoles } from "@storybook/testing-library";
export const DebuggingExample = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Pretty print specific element
const form = canvas.getByRole('form');
console.log('Form HTML:', prettyDOM(form));
// Log entire DOM tree
logDOM(canvasElement);
// Log available roles
logRoles(canvasElement);
// Pretty print with options
const table = canvas.getByRole('table');
console.log('Table (limited):', prettyDOM(table, 1000, {
highlight: true,
filterNode: (node) => node.nodeType === Node.ELEMENT_NODE
}));
}
};import { within, getRoles, isInaccessible, getSuggestedQuery } from "@storybook/testing-library";
export const AccessibilityExample = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Check available roles
const button = canvas.getByRole('button');
const availableRoles = getRoles(button);
console.log('Available roles for button:', availableRoles);
// Check if element is accessible
const hiddenElement = canvas.getByTestId('hidden-element');
const isHidden = isInaccessible(hiddenElement);
console.log('Element is inaccessible:', isHidden);
// Get suggested query
const input = canvas.getByLabelText(/username/i);
const suggestion = getSuggestedQuery(input, 'get');
console.log('Suggested query:', suggestion);
}
};import { within, getNodeText, getDefaultNormalizer } from "@storybook/testing-library";
export const TextUtilitiesExample = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Get text content including nested elements
const container = canvas.getByTestId('text-container');
const fullText = getNodeText(container);
console.log('Full text content:', fullText);
// Use custom normalizer
const normalizer = getDefaultNormalizer({
trim: true,
collapseWhitespace: true
});
const rawText = ' Hello World \n\n ';
const normalizedText = normalizer(rawText);
console.log('Normalized text:', normalizedText); // "Hello World"
}
};import { within, getElementError } from "@storybook/testing-library";
export const ErrorHandlingExample = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
try {
// This will throw if element not found
canvas.getByText(/non-existent text/i);
} catch (error) {
// Enhance error with debugging info
const enhancedError = getElementError(
'Custom error message: Element not found',
canvasElement
);
console.error('Enhanced error:', enhancedError.message);
}
}
};import { configure } from "@storybook/testing-library";
export const AdvancedConfigExample = {
play: async () => {
configure({
// Use custom test ID attribute
testIdAttribute: 'data-cy',
// Increase timeout for slow operations
asyncUtilTimeout: 5000,
// Custom text normalizer
defaultNormalizer: (text: string) => {
return text
.toLowerCase()
.replace(/\s+/g, ' ')
.trim()
.replace(/[^\w\s]/g, '');
},
// Include hidden elements in queries
defaultHidden: false,
// Show full error stack traces
showOriginalStackTrace: true,
// Custom ignore pattern
defaultIgnore: 'script, style, [data-ignore]',
// Disable query suggestions
throwSuggestions: false
});
}
};import { configure, within } from "@storybook/testing-library";
export const PerformanceExample = {
play: async ({ canvasElement }) => {
// Configure for better performance
configure({
asyncUtilTimeout: 500, // Shorter timeout for fast tests
computedStyleSupportsPseudoElements: false, // Disable if not needed
throwSuggestions: false // Reduce error message computation
});
const canvas = within(canvasElement);
// Use more specific queries for better performance
const button = canvas.getByRole('button', { name: /exact text/i });
const input = canvas.getByLabelText(/specific label/i);
expect(button).toBeInTheDocument();
expect(input).toBeInTheDocument();
}
};Install with Tessl CLI
npx tessl i tessl/npm-storybook--testing-library