Utilities for testing your stories inside play functions
—
Complete DOM testing utilities including queries, user interactions, and async utilities. All functions are instrumented for Storybook debugging and work seamlessly with the addon-interactions panel. Based on @testing-library/dom and @testing-library/user-event.
Query DOM elements using various strategies. Each query type comes in three variants: get (throws if not found), find (async, throws if not found), and query (returns null if not found).
/**
* Get element by ARIA role
* @param container - Container element to search within
* @param role - ARIA role to search for
* @param options - Additional query options
* @returns Found element (throws if not found)
*/
function getByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement;
function getAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];
function getByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
function getAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
function getByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
function getAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
function getByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
function getByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
function getByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
function getByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement[];
function getByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement;
function getAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement[];
interface ByRoleOptions extends MatcherOptions {
checked?: boolean;
selected?: boolean;
expanded?: boolean;
pressed?: boolean;
level?: number;
name?: string | RegExp;
description?: string | RegExp;
}
interface SelectorMatcherOptions extends MatcherOptions {
selector?: string;
}
interface MatcherOptions {
exact?: boolean;
normalizer?: (text: string) => string;
}/**
* Asynchronously find element by ARIA role
* @param container - Container element to search within
* @param role - ARIA role to search for
* @param options - Additional query options
* @param waitForOptions - Options for waiting
* @returns Promise resolving to found element
*/
function findByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
function findByTestId(container: HTMLElement, testId: string, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;/**
* Query element by ARIA role (returns null if not found)
* @param container - Container element to search within
* @param role - ARIA role to search for
* @param options - Additional query options
* @returns Found element or null
*/
function queryByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement | null;
function queryAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];
function queryByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
function queryAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
function queryByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
function queryAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
function queryByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
function queryAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
function queryByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
function queryAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
function queryByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
function queryAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
function queryByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement | null;
function queryAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement[];
function queryByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement | null;
function queryAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement[];
function queryByAttribute(container: HTMLElement, name: string, value?: string | RegExp, options?: MatcherOptions): HTMLElement | null;
function queryAllByAttribute(container: HTMLElement, name: string, value?: string | RegExp, options?: MatcherOptions): HTMLElement[];Query Usage Examples:
import { within, getByRole, findByText, queryByTestId } from '@storybook/test';
export const QueryStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Get queries (throw if not found)
const button = canvas.getByRole('button', { name: /submit/i });
const heading = canvas.getByText('Welcome');
const input = canvas.getByLabelText('Email');
// Find queries (async, wait for element)
const asyncContent = await canvas.findByText('Loaded content');
const asyncButton = await canvas.findByRole('button', { name: /save/i });
// Query queries (return null if not found)
const optionalElement = canvas.queryByTestId('optional-feature');
if (optionalElement) {
// Element exists, interact with it
expect(optionalElement).toBeInTheDocument();
}
// Multiple elements
const allButtons = canvas.getAllByRole('button');
const allInputs = await canvas.findAllByRole('textbox');
expect(allButtons.length).toBeGreaterThan(0);
expect(allInputs.length).toBeGreaterThan(0);
},
};Scope queries to specific parts of the DOM using the within function.
/**
* Create bound queries scoped to a specific element
* @param element - Element to scope queries within
* @returns Object with all query functions bound to the element
*/
function within(element: HTMLElement): BoundFunctions<typeof queries>;
type BoundFunctions<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any
? (...args: Parameters<T[K]>) => ReturnType<T[K]>
: T[K];
};
/**
* Global screen object for document-wide queries (deprecated in Storybook context)
* Use within(canvasElement) instead
*/
const screen: BoundFunctions<typeof queries>;Scoping Usage Examples:
import { within, expect } from '@storybook/test';
export const ScopingStory = {
play: async ({ canvasElement }) => {
// Scope queries to the story canvas
const canvas = within(canvasElement);
// Find a modal or specific section
const modal = canvas.getByRole('dialog');
const modalScope = within(modal);
// Queries within modal only
const modalButton = modalScope.getByRole('button', { name: /close/i });
const modalHeading = modalScope.getByRole('heading');
// This would find buttons anywhere in canvas
const allCanvasButtons = canvas.getAllByRole('button');
// This only finds buttons within the modal
const modalButtons = modalScope.getAllByRole('button');
expect(modalButtons.length).toBeLesserThanOrEqual(allCanvasButtons.length);
},
};Simulate user interactions with elements using the instrumented userEvent API.
interface UserEvent {
/**
* Click an element
* @param element - Element to click
* @param options - Click options
*/
click(element: Element, options?: ClickOptions): Promise<void>;
/**
* Double-click an element
* @param element - Element to double-click
* @param options - Click options
*/
dblClick(element: Element, options?: ClickOptions): Promise<void>;
/**
* Type text into an element
* @param element - Element to type into
* @param text - Text to type
* @param options - Typing options
*/
type(element: Element, text: string, options?: TypeOptions): Promise<void>;
/**
* Clear an input element
* @param element - Element to clear
*/
clear(element: Element): Promise<void>;
/**
* Select options in a select element
* @param element - Select element
* @param values - Values to select
*/
selectOptions(element: Element, values: string | string[]): Promise<void>;
/**
* Deselect options in a select element
* @param element - Select element
* @param values - Values to deselect
*/
deselectOptions(element: Element, values: string | string[]): Promise<void>;
/**
* Upload files to a file input
* @param element - File input element
* @param file - File or files to upload
*/
upload(element: Element, file: File | File[]): Promise<void>;
/**
* Tab to the next focusable element
* @param options - Tab options
*/
tab(options?: TabOptions): Promise<void>;
/**
* Hover over an element
* @param element - Element to hover
*/
hover(element: Element): Promise<void>;
/**
* Stop hovering over an element
* @param element - Element to stop hovering
*/
unhover(element: Element): Promise<void>;
/**
* Paste text into an element
* @param text - Text to paste
*/
paste(text: string): Promise<void>;
/**
* Press keyboard key(s)
* @param keys - Key or key combination to press
*/
keyboard(keys: string): Promise<void>;
}
interface ClickOptions {
button?: number;
detail?: number;
ctrlKey?: boolean;
shiftKey?: boolean;
altKey?: boolean;
metaKey?: boolean;
}
interface TypeOptions {
delay?: number;
skipClick?: boolean;
skipAutoClose?: boolean;
initialSelectionStart?: number;
initialSelectionEnd?: number;
}
interface TabOptions {
shift?: boolean;
}User Interaction Examples:
import { userEvent, within, expect } from '@storybook/test';
export const InteractionStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Basic interactions
const button = canvas.getByRole('button', { name: /submit/i });
await userEvent.click(button);
const input = canvas.getByLabelText('Username');
await userEvent.type(input, 'john.doe');
expect(input).toHaveValue('john.doe');
// Clear and retype
await userEvent.clear(input);
await userEvent.type(input, 'jane.doe');
expect(input).toHaveValue('jane.doe');
// Select dropdown
const select = canvas.getByLabelText('Country');
await userEvent.selectOptions(select, 'us');
expect(select).toHaveValue('us');
// Multiple selections
const multiSelect = canvas.getByLabelText('Skills');
await userEvent.selectOptions(multiSelect, ['javascript', 'typescript']);
// File upload
const fileInput = canvas.getByLabelText('Upload file');
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
await userEvent.upload(fileInput, file);
expect(fileInput.files![0]).toBe(file);
// Keyboard interactions
await userEvent.keyboard('{Enter}');
await userEvent.keyboard('{Escape}');
await userEvent.keyboard('{ctrl}a'); // Select all
// Hover effects
const tooltip = canvas.getByText('Hover me');
await userEvent.hover(tooltip);
await expect(canvas.findByText('Tooltip content')).resolves.toBeInTheDocument();
await userEvent.unhover(tooltip);
expect(canvas.queryByText('Tooltip content')).not.toBeInTheDocument();
},
};Wait for conditions and element changes in the DOM.
/**
* Wait for a condition to be met
* @param callback - Function to test condition
* @param options - Wait options
* @returns Promise resolving when condition is met
*/
function waitFor<T>(callback: () => T | Promise<T>, options?: WaitForOptions): Promise<T>;
/**
* Wait for elements to be removed from the DOM
* @param callback - Function returning elements to wait for removal
* @param options - Wait options
* @returns Promise resolving when elements are removed
*/
function waitForElementToBeRemoved(
callback: () => Element | Element[] | Promise<Element | Element[]>,
options?: WaitForOptions
): Promise<void>;
interface WaitForOptions {
container?: HTMLElement;
timeout?: number;
interval?: number;
onTimeout?: (error: Error) => Error;
mutationObserverOptions?: MutationObserverInit;
}Async Utility Examples:
import { waitFor, waitForElementToBeRemoved, within, userEvent, expect } from '@storybook/test';
export const AsyncStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Wait for async content to appear
await waitFor(() => {
expect(canvas.getByText('Loading complete')).toBeInTheDocument();
});
// Wait for API call to complete
const loadButton = canvas.getByRole('button', { name: /load data/i });
await userEvent.click(loadButton);
await waitFor(
async () => {
const dataList = canvas.getByRole('list');
const items = canvas.getAllByRole('listitem');
expect(items.length).toBeGreaterThan(0);
return items;
},
{ timeout: 5000 }
);
// Wait for element removal
const dismissButton = canvas.getByRole('button', { name: /dismiss/i });
await userEvent.click(dismissButton);
await waitForElementToBeRemoved(
() => canvas.queryByText('Notification message'),
{ timeout: 3000 }
);
// Custom timeout and interval
await waitFor(
() => {
const status = canvas.getByTestId('status');
expect(status).toHaveTextContent('Ready');
},
{
timeout: 10000,
interval: 500,
}
);
},
};Additional utilities for DOM interaction and debugging.
/**
* Create DOM events programmatically
* @param eventName - Name of the event
* @param node - Target node for the event
* @param init - Event initialization options
*/
function createEvent(eventName: string, node: Element, init?: EventInit): Event;
/**
* Fire events on DOM elements
*/
const fireEvent: FireFunction & FireObject;
interface FireFunction {
(element: Element, event: Event): boolean;
}
interface FireObject {
click(element: Element, options?: EventInit): boolean;
change(element: Element, options?: EventInit): boolean;
input(element: Element, options?: EventInit): boolean;
keyDown(element: Element, options?: KeyboardEventInit): boolean;
keyPress(element: Element, options?: KeyboardEventInit): boolean;
keyUp(element: Element, options?: KeyboardEventInit): boolean;
mouseDown(element: Element, options?: MouseEventInit): boolean;
mouseEnter(element: Element, options?: MouseEventInit): boolean;
mouseLeave(element: Element, options?: MouseEventInit): boolean;
mouseMove(element: Element, options?: MouseEventInit): boolean;
mouseOut(element: Element, options?: MouseEventInit): boolean;
mouseOver(element: Element, options?: MouseEventInit): boolean;
mouseUp(element: Element, options?: MouseEventInit): boolean;
focus(element: Element, options?: FocusEventInit): boolean;
blur(element: Element, options?: FocusEventInit): boolean;
submit(element: Element, options?: EventInit): boolean;
}
/**
* Pretty print DOM structure for debugging
* @param element - Element to print
* @param maxLength - Maximum length of output
* @param options - Pretty printing options
*/
function prettyDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): string;
/**
* Log DOM structure to console
* @param element - Element to log
* @param maxLength - Maximum length of output
* @param options - Pretty printing options
*/
function logDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): void;
/**
* Log ARIA roles for debugging accessibility
* @param element - Element to analyze
*/
function logRoles(element: Element): void;
/**
* Get suggested query for an element
* @param element - Element to analyze
* @param variant - Query variant to suggest
*/
function getSuggestedQuery(element: Element, variant?: 'get' | 'find' | 'query'): string;
/**
* Get default text normalizer function
* @returns Default normalizer function
*/
function getDefaultNormalizer(): (text: string) => string;
/**
* Get error message for element not found
* @param message - Error message
* @param container - Container element
* @returns Error instance
*/
function getElementError(message: string, container: HTMLElement): Error;
/**
* Get text content from a DOM node
* @param node - DOM node to extract text from
* @returns Text content
*/
function getNodeText(node: Node): string;
/**
* Get bound query functions for a specific element
* @param element - Element to bind queries to
* @returns Object with bound query functions
*/
function getQueriesForElement(element: HTMLElement): BoundFunctions<typeof queries>;
/**
* Get all ARIA roles for an element
* @param element - Element to analyze
* @returns Object with role information
*/
function getRoles(element: Element): { [role: string]: HTMLElement[] };
/**
* Check if element is accessible
* @param element - Element to check
* @returns True if element is accessible
*/
function isInaccessible(element: Element): boolean;
/**
* Pretty format values for debugging
* @param value - Value to format
* @param options - Formatting options
* @returns Formatted string
*/
function prettyFormat(value: any, options?: any): string;
/**
* Query helper utilities
*/
const queryHelpers: {
queryByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement | null;
queryAllByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement[];
getElementError: (message: string, container: HTMLElement) => Error;
};
/**
* All available query functions
*/
const queries: {
queryByRole: typeof queryByRole;
queryAllByRole: typeof queryAllByRole;
getByRole: typeof getByRole;
getAllByRole: typeof getAllByRole;
findByRole: typeof findByRole;
findAllByRole: typeof findAllByRole;
// ... (all other query functions)
};
interface PrettyDOMOptions {
highlight?: boolean;
filterNode?: (node: Node) => boolean;
}DOM Utility Examples:
import {
fireEvent,
createEvent,
prettyDOM,
logDOM,
logRoles,
getSuggestedQuery,
within
} from '@storybook/test';
export const UtilityStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Manual event firing
const button = canvas.getByRole('button');
fireEvent.click(button);
fireEvent.mouseEnter(button);
fireEvent.mouseLeave(button);
// Custom events
const customEvent = createEvent('customEvent', button, {
bubbles: true,
cancelable: true
});
fireEvent(button, customEvent);
// Debugging utilities
const form = canvas.getByRole('form');
// Log DOM structure
console.log('Form DOM:', prettyDOM(form));
logDOM(form); // Logs to console with nice formatting
// Analyze accessibility
logRoles(form); // Shows all ARIA roles
// Get suggested queries
const input = canvas.getByLabelText('Email');
console.log('Suggested query:', getSuggestedQuery(input));
// Output: getByLabelText(/email/i)
// Additional utility functions
console.log('Node text:', getNodeText(input));
console.log('Is accessible:', !isInaccessible(input));
console.log('Element roles:', getRoles(form));
// Get bound queries for specific element
const boundQueries = getQueriesForElement(form);
const formButton = boundQueries.getByRole('button');
// Pretty formatting for debugging
console.log('Pretty formatted value:', prettyFormat({ key: 'value' }));
},
};Configure testing-library behavior globally.
/**
* Configure testing-library options
* @param options - Configuration options
*/
function configure(options: ConfigureOptions): void;
/**
* Get current configuration
* @returns Current configuration object
*/
function getConfig(): Config;
interface ConfigureOptions {
testIdAttribute?: string;
asyncUtilTimeout?: number;
computedStyleSupportsPseudoElements?: boolean;
defaultHidden?: boolean;
showOriginalStackTrace?: boolean;
throwSuggestions?: boolean;
getElementError?: (message: string, container: HTMLElement) => Error;
}
interface Config extends ConfigureOptions {
testIdAttribute: string;
asyncUtilTimeout: number;
computedStyleSupportsPseudoElements: boolean;
defaultHidden: boolean;
showOriginalStackTrace: boolean;
throwSuggestions: boolean;
}Configuration Examples:
import { configure, getConfig } from '@storybook/test';
// Configure at story level or in preview.js
configure({
testIdAttribute: 'data-cy', // Use Cypress test IDs
asyncUtilTimeout: 5000, // Increase timeout
throwSuggestions: false, // Disable query suggestions
});
export const ConfigStory = {
play: async () => {
const config = getConfig();
console.log('Current timeout:', config.asyncUtilTimeout);
// Now getByTestId uses 'data-cy' instead of 'data-testid'
},
};Generic attribute-based queries for custom attributes not covered by other query types.
/**
* Query elements by any attribute value
* @param container - Container element to search within
* @param attribute - Attribute name to search for
* @param value - Attribute value to match
* @param options - Additional query options
* @returns Found element or null
*/
function queryByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement | null;
function queryAllByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement[];Usage Examples:
import { queryByAttribute, queryAllByAttribute, within } from '@storybook/test';
export const AttributeQueryStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Query by custom attribute
const element = queryByAttribute(canvasElement, 'data-custom', 'value');
if (element) {
console.log('Found element with data-custom="value"');
}
// Query all elements with specific attribute
const allElements = queryAllByAttribute(canvasElement, 'aria-expanded', 'true');
console.log(`Found ${allElements.length} expanded elements`);
},
};Manually trigger DOM events for advanced testing scenarios.
interface FireEvent {
(element: Element, event: Event): boolean;
// Event-specific methods
abort(element: Element, eventProperties?: {}): boolean;
animationEnd(element: Element, eventProperties?: {}): boolean;
animationIteration(element: Element, eventProperties?: {}): boolean;
animationStart(element: Element, eventProperties?: {}): boolean;
blur(element: Element, eventProperties?: {}): boolean;
canPlay(element: Element, eventProperties?: {}): boolean;
canPlayThrough(element: Element, eventProperties?: {}): boolean;
change(element: Element, eventProperties?: {}): boolean;
click(element: Element, eventProperties?: {}): boolean;
contextMenu(element: Element, eventProperties?: {}): boolean;
copy(element: Element, eventProperties?: {}): boolean;
cut(element: Element, eventProperties?: {}): boolean;
doubleClick(element: Element, eventProperties?: {}): boolean;
drag(element: Element, eventProperties?: {}): boolean;
dragEnd(element: Element, eventProperties?: {}): boolean;
dragEnter(element: Element, eventProperties?: {}): boolean;
dragExit(element: Element, eventProperties?: {}): boolean;
dragLeave(element: Element, eventProperties?: {}): boolean;
dragOver(element: Element, eventProperties?: {}): boolean;
dragStart(element: Element, eventProperties?: {}): boolean;
drop(element: Element, eventProperties?: {}): boolean;
durationChange(element: Element, eventProperties?: {}): boolean;
emptied(element: Element, eventProperties?: {}): boolean;
encrypted(element: Element, eventProperties?: {}): boolean;
ended(element: Element, eventProperties?: {}): boolean;
error(element: Element, eventProperties?: {}): boolean;
focus(element: Element, eventProperties?: {}): boolean;
focusIn(element: Element, eventProperties?: {}): boolean;
focusOut(element: Element, eventProperties?: {}): boolean;
input(element: Element, eventProperties?: {}): boolean;
invalid(element: Element, eventProperties?: {}): boolean;
keyDown(element: Element, eventProperties?: {}): boolean;
keyPress(element: Element, eventProperties?: {}): boolean;
keyUp(element: Element, eventProperties?: {}): boolean;
load(element: Element, eventProperties?: {}): boolean;
loadStart(element: Element, eventProperties?: {}): boolean;
loadedData(element: Element, eventProperties?: {}): boolean;
loadedMetadata(element: Element, eventProperties?: {}): boolean;
mouseDown(element: Element, eventProperties?: {}): boolean;
mouseEnter(element: Element, eventProperties?: {}): boolean;
mouseLeave(element: Element, eventProperties?: {}): boolean;
mouseMove(element: Element, eventProperties?: {}): boolean;
mouseOut(element: Element, eventProperties?: {}): boolean;
mouseOver(element: Element, eventProperties?: {}): boolean;
mouseUp(element: Element, eventProperties?: {}): boolean;
paste(element: Element, eventProperties?: {}): boolean;
pause(element: Element, eventProperties?: {}): boolean;
play(element: Element, eventProperties?: {}): boolean;
playing(element: Element, eventProperties?: {}): boolean;
pointerCancel(element: Element, eventProperties?: {}): boolean;
pointerDown(element: Element, eventProperties?: {}): boolean;
pointerEnter(element: Element, eventProperties?: {}): boolean;
pointerLeave(element: Element, eventProperties?: {}): boolean;
pointerMove(element: Element, eventProperties?: {}): boolean;
pointerOut(element: Element, eventProperties?: {}): boolean;
pointerOver(element: Element, eventProperties?: {}): boolean;
pointerUp(element: Element, eventProperties?: {}): boolean;
progress(element: Element, eventProperties?: {}): boolean;
rateChange(element: Element, eventProperties?: {}): boolean;
scroll(element: Element, eventProperties?: {}): boolean;
seeked(element: Element, eventProperties?: {}): boolean;
seeking(element: Element, eventProperties?: {}): boolean;
select(element: Element, eventProperties?: {}): boolean;
stalled(element: Element, eventProperties?: {}): boolean;
submit(element: Element, eventProperties?: {}): boolean;
suspend(element: Element, eventProperties?: {}): boolean;
timeUpdate(element: Element, eventProperties?: {}): boolean;
touchCancel(element: Element, eventProperties?: {}): boolean;
touchEnd(element: Element, eventProperties?: {}): boolean;
touchMove(element: Element, eventProperties?: {}): boolean;
touchStart(element: Element, eventProperties?: {}): boolean;
transitionEnd(element: Element, eventProperties?: {}): boolean;
volumeChange(element: Element, eventProperties?: {}): boolean;
waiting(element: Element, eventProperties?: {}): boolean;
wheel(element: Element, eventProperties?: {}): boolean;
}
/**
* Create custom DOM events
* @param eventName - Name of the event to create
* @param node - Target element for the event
* @param init - Event initialization options
* @returns Created event object
*/
function createEvent(eventName: string, node: Element, init?: {}): Event;
const fireEvent: FireEvent;Usage Examples:
import { fireEvent, createEvent, within } from '@storybook/test';
export const FireEventStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
// Fire common events
fireEvent.click(button);
fireEvent.mouseOver(button);
fireEvent.focus(button);
// Fire events with custom properties
fireEvent.keyDown(button, { key: 'Enter', code: 'Enter' });
// Create and fire custom events
const customEvent = createEvent('custom-event', button, { detail: { custom: 'data' } });
fireEvent(button, customEvent);
},
};Helper functions for debugging, introspection, and DOM manipulation.
/**
* Pretty-print DOM element structure for debugging
* @param element - Element to print (defaults to document.body)
* @param maxLength - Maximum string length (default: 7000)
* @param options - Formatting options
* @returns Formatted string representation of DOM
*/
function prettyDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): string;
/**
* Log DOM structure to console
* @param element - Element to log (defaults to document.body)
* @param maxLength - Maximum string length
* @param options - Formatting options
*/
function logDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): void;
/**
* Log all ARIA roles found in the DOM
* @param element - Container element (defaults to document.body)
*/
function logRoles(element?: Element | HTMLDocument): void;
/**
* Get all ARIA roles present in an element
* @param element - Container element
* @returns Array of role information objects
*/
function getRoles(element: Element): Array<{ role: string; elements: Element[] }>;
/**
* Get text content of a node including hidden text
* @param node - DOM node to extract text from
* @returns Text content string
*/
function getNodeText(node: Node): string;
/**
* Check if an element is inaccessible to screen readers
* @param element - Element to check
* @returns True if element is inaccessible
*/
function isInaccessible(element: Element): boolean;
/**
* Get suggested query for finding an element
* @param element - Element to analyze
* @param variant - Query variant preference ('get' | 'find' | 'query')
* @param method - Query method preference
* @returns Suggested query string
*/
function getSuggestedQuery(element: Element, variant?: string, method?: string): string;
/**
* Create an error with element information
* @param message - Error message
* @param container - Container element for context
* @returns Error with DOM context
*/
function getElementError(message: string, container: Element): Error;
/**
* Get default text normalizer function
* @returns Default normalizer function
*/
function getDefaultNormalizer(): (text: string) => string;
/**
* Pretty format any value for display
* @param value - Value to format
* @returns Formatted string representation
*/
function prettyFormat(value: any): string;Usage Examples:
import {
prettyDOM, logDOM, logRoles, getRoles, getNodeText,
isInaccessible, getSuggestedQuery, within
} from '@storybook/test';
export const DebugUtilsStory = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Debug DOM structure
console.log('DOM structure:', prettyDOM(canvasElement));
logDOM(canvasElement, 1000); // Log to console with max length
// Analyze accessibility
logRoles(canvasElement); // Log all ARIA roles
const roles = getRoles(canvasElement);
console.log('Available roles:', roles.map(r => r.role));
// Get element text content
const button = canvas.getByRole('button');
console.log('Button text:', getNodeText(button));
// Check accessibility
if (isInaccessible(button)) {
console.warn('Button is not accessible to screen readers');
}
// Get suggested query for element
const suggestion = getSuggestedQuery(button, 'get');
console.log('Suggested query:', suggestion);
},
};Advanced utilities for building custom queries and extending testing-library functionality.
/**
* Build custom query functions from base query logic
* @param queryName - Name for the query type
* @param queryAllByAttribute - Function to find all matching elements
* @param getMultipleError - Error function for multiple matches
* @param getMissingError - Error function for no matches
* @returns Object with get, getAll, query, queryAll, find, findAll functions
*/
function buildQueries(
queryName: string,
queryAllByAttribute: (container: Element, ...args: any[]) => Element[],
getMultipleError: (container: Element, ...args: any[]) => string,
getMissingError: (container: Element, ...args: any[]) => string
): {
[key: string]: (...args: any[]) => Element | Element[] | null | Promise<Element | Element[]>;
};
/**
* Get bound query functions for a specific element
* @param element - Element to bind queries to
* @returns Object with all query functions pre-bound to the element
*/
function getQueriesForElement(element: Element): BoundFunctions<typeof queries>;
/**
* Collection of all base query functions
*/
const queries: {
queryByRole: typeof queryByRole;
queryAllByRole: typeof queryAllByRole;
getByRole: typeof getByRole;
getAllByRole: typeof getAllByRole;
findByRole: typeof findByRole;
findAllByRole: typeof findAllByRole;
// ... all other query variants
};
/**
* Helpers for building custom queries
*/
const queryHelpers: {
queryByAttribute: typeof queryByAttribute;
queryAllByAttribute: typeof queryAllByAttribute;
buildQueries: typeof buildQueries;
getMultipleElementsFoundError: (message: string, container: Element) => Error;
getElementError: typeof getElementError;
};Usage Examples:
import { buildQueries, getQueriesForElement, queryHelpers } from '@storybook/test';
// Build custom query for data-cy attributes (Cypress style)
const [
queryByCy,
queryAllByCy,
getByCy,
getAllByCy,
findByCy,
findAllByCy,
] = buildQueries(
'Cy',
(container, id) => queryHelpers.queryAllByAttribute(container, 'data-cy', id),
() => 'Found multiple elements with the same data-cy attribute',
() => 'Unable to find an element with the data-cy attribute'
);
export const CustomQueryStory = {
play: async ({ canvasElement }) => {
// Use custom query
const element = getByCy(canvasElement, 'submit-button');
// Get all queries bound to an element
const boundQueries = getQueriesForElement(canvasElement);
const sameElement = boundQueries.getByCy('submit-button');
},
};The screen object provides global queries but is discouraged in Storybook. Use within(canvasElement) instead.
/**
* Global query object (deprecated in Storybook context)
* @deprecated Use within(canvasElement) instead for Storybook stories
*/
const screen: BoundFunctions<typeof queries>;Migration Example:
import { screen, within } from '@storybook/test';
export const MigrationStory = {
play: async ({ canvasElement }) => {
// ❌ Don't use screen in Storybook (will show warning)
// const button = screen.getByRole('button');
// ✅ Use within() instead
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
},
};Install with Tessl CLI
npx tessl i tessl/npm-storybook--test