Instrumented version of Testing Library for Storybook Interactions addon
—
Instrumented user-event functionality for simulating realistic user interactions with full keyboard, mouse, and input device support. All user interactions are captured by Storybook's instrumentation for display in the Interactions addon.
The main interface for simulating user interactions. All methods are asynchronous and return Promises.
/**
* Instrumented user-event object for simulating realistic user interactions
* All methods are wrapped with Storybook instrumentation for interaction tracking
*/
interface UserEvent {
/**
* Click on an element
* @param element - Element to click
* @param options - Click configuration options
*/
click(element: Element, options?: ClickOptions): Promise<void>;
/**
* Double-click on an element
* @param element - Element to double-click
* @param options - Click configuration options
*/
dblClick(element: Element, options?: ClickOptions): Promise<void>;
/**
* Type text into an element
* @param element - Input element to type into
* @param text - Text to type
* @param options - Typing configuration options
*/
type(element: Element, text: string, options?: TypeOptions): Promise<void>;
/**
* Clear the content of an input element
* @param element - Input element to clear
*/
clear(element: Element): Promise<void>;
/**
* Select options in a select element
* @param element - Select element
* @param values - Option values to select (string or array)
* @param options - Selection options
*/
selectOptions(element: Element, values: string | string[], options?: SelectOptions): Promise<void>;
/**
* Deselect options in a select element
* @param element - Select element
* @param values - Option values to deselect (string or array)
* @param options - Selection options
*/
deselectOptions(element: Element, values: string | string[], options?: SelectOptions): Promise<void>;
/**
* Upload files to a file input
* @param element - File input element
* @param file - File or array of 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 over
* @param options - Hover options
*/
hover(element: Element, options?: HoverOptions): Promise<void>;
/**
* Stop hovering over an element
* @param element - Element to unhover
* @param options - Hover options
*/
unhover(element: Element, options?: HoverOptions): Promise<void>;
/**
* Press and hold keyboard keys
* @param keys - Key or combination of keys to press
* @param options - Keyboard options
*/
keyboard(keys: string, options?: KeyboardOptions): Promise<void>;
/**
* Copy selected text to clipboard
*/
copy(): Promise<void>;
/**
* Cut selected text to clipboard
*/
cut(): Promise<void>;
/**
* Paste text from clipboard
*/
paste(): Promise<void>;
}
declare const userEvent: UserEvent;/**
* Options for click interactions
*/
interface ClickOptions {
/** Mouse button to use (0=left, 1=middle, 2=right) */
button?: number;
/** Control key modifier */
ctrlKey?: boolean;
/** Shift key modifier */
shiftKey?: boolean;
/** Alt key modifier */
altKey?: boolean;
/** Meta key modifier (Cmd on Mac, Windows key on PC) */
metaKey?: boolean;
/** Skip default behavior (e.g., don't focus element) */
skipDefaultPrevented?: boolean;
}Usage Examples:
import { within, userEvent } from "@storybook/testing-library";
export const ClickInteractions = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Basic click
const button = canvas.getByRole('button', { name: /submit/i });
await userEvent.click(button);
// Click with modifiers
const link = canvas.getByRole('link', { name: /external/i });
await userEvent.click(link, { ctrlKey: true }); // Ctrl+click
// Right-click (context menu)
await userEvent.click(button, { button: 2 });
// Double-click
const item = canvas.getByTestId('double-click-item');
await userEvent.dblClick(item);
}
};/**
* Options for typing interactions
*/
interface TypeOptions {
/** Delay between keystrokes in milliseconds */
delay?: number;
/** Skip clicking the element before typing */
skipClick?: boolean;
/** Skip auto-closing of characters like quotes and brackets */
skipAutoClose?: boolean;
/** Initial selection start position */
initialSelectionStart?: number;
/** Initial selection end position */
initialSelectionEnd?: number;
}Usage Examples:
import { within, userEvent } from "@storybook/testing-library";
export const TypingInteractions = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Basic typing
const input = canvas.getByLabelText(/username/i);
await userEvent.type(input, 'john.doe@example.com');
// Typing with delay
const slowInput = canvas.getByLabelText(/description/i);
await userEvent.type(slowInput, 'Slow typing...', { delay: 100 });
// Clear and type
const existingInput = canvas.getByDisplayValue('existing text');
await userEvent.clear(existingInput);
await userEvent.type(existingInput, 'new text');
// Type special characters
const passwordInput = canvas.getByLabelText(/password/i);
await userEvent.type(passwordInput, 'P@ssw0rd!23');
}
};/**
* Options for select interactions
*/
interface SelectOptions {
/** Skip clicking the element before selecting */
skipClick?: boolean;
}Usage Examples:
import { within, userEvent } from "@storybook/testing-library";
export const SelectionInteractions = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Select single option
const countrySelect = canvas.getByLabelText(/country/i);
await userEvent.selectOptions(countrySelect, 'USA');
// Select multiple options
const skillsSelect = canvas.getByLabelText(/skills/i);
await userEvent.selectOptions(skillsSelect, ['JavaScript', 'TypeScript', 'React']);
// Deselect options
await userEvent.deselectOptions(skillsSelect, 'JavaScript');
// Select by value
await userEvent.selectOptions(countrySelect, { target: { value: 'canada' } });
}
};Usage Examples:
import { within, userEvent } from "@storybook/testing-library";
export const FileUploadInteractions = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Upload single file
const fileInput = canvas.getByLabelText(/upload file/i);
const file = new File(['file content'], 'test.txt', { type: 'text/plain' });
await userEvent.upload(fileInput, file);
// Upload multiple files
const multiFileInput = canvas.getByLabelText(/upload multiple/i);
const files = [
new File(['image1'], 'image1.png', { type: 'image/png' }),
new File(['image2'], 'image2.jpg', { type: 'image/jpeg' })
];
await userEvent.upload(multiFileInput, files);
}
};/**
* Options for keyboard interactions
*/
interface KeyboardOptions {
/** Skip auto-releasing pressed keys */
skipAutoClose?: boolean;
}
/**
* Options for tab navigation
*/
interface TabOptions {
/** Tab backwards (Shift+Tab) */
shift?: boolean;
}Usage Examples:
import { within, userEvent } from "@storybook/testing-library";
export const KeyboardInteractions = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Tab navigation
await userEvent.tab(); // Tab forward
await userEvent.tab({ shift: true }); // Tab backward
// Keyboard shortcuts
await userEvent.keyboard('{ctrl}a'); // Select all
await userEvent.keyboard('{ctrl}c'); // Copy
await userEvent.keyboard('{ctrl}v'); // Paste
// Special keys
await userEvent.keyboard('{Enter}'); // Enter key
await userEvent.keyboard('{Escape}'); // Escape key
await userEvent.keyboard('{ArrowDown}'); // Arrow down
// Key combinations
await userEvent.keyboard('{ctrl}{shift}k'); // Ctrl+Shift+K
// Use convenience methods
await userEvent.copy();
await userEvent.cut();
await userEvent.paste();
}
};/**
* Options for hover interactions
*/
interface HoverOptions {
/** Skip pointer events */
skipPointerEvents?: boolean;
}Usage Examples:
import { within, userEvent, waitFor } from "@storybook/testing-library";
export const HoverInteractions = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Hover over element
const tooltip = canvas.getByTestId('tooltip-trigger');
await userEvent.hover(tooltip);
// Wait for tooltip to appear
await waitFor(() => {
expect(canvas.getByRole('tooltip')).toBeVisible();
});
// Stop hovering
await userEvent.unhover(tooltip);
// Tooltip should disappear
await waitFor(() => {
expect(canvas.queryByRole('tooltip')).not.toBeInTheDocument();
});
}
};import { within, userEvent, waitFor } from "@storybook/testing-library";
export const CompleteUserFlow = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Fill out a form realistically
const emailInput = canvas.getByLabelText(/email/i);
const passwordInput = canvas.getByLabelText(/password/i);
const submitButton = canvas.getByRole('button', { name: /sign in/i });
// Focus and type email
await userEvent.click(emailInput);
await userEvent.type(emailInput, 'user@example.com');
// Tab to password field
await userEvent.tab();
await userEvent.type(passwordInput, 'securepassword');
// Submit form
await userEvent.click(submitButton);
// Wait for success message
await waitFor(() => {
expect(canvas.getByText(/welcome back/i)).toBeInTheDocument();
});
}
};import { within, userEvent } from "@storybook/testing-library";
export const ErrorHandling = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
try {
// Attempt to interact with element that might not exist
const button = canvas.queryByRole('button', { name: /optional/i });
if (button) {
await userEvent.click(button);
}
} catch (error) {
console.error('User interaction failed:', error);
}
}
};Install with Tessl CLI
npx tessl i tessl/npm-storybook--testing-library