0
# User Interactions
1
2
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.
3
4
## Capabilities
5
6
### User Event Object
7
8
The main interface for simulating user interactions. All methods are asynchronous and return Promises.
9
10
```typescript { .api }
11
/**
12
* Instrumented user-event object for simulating realistic user interactions
13
* All methods are wrapped with Storybook instrumentation for interaction tracking
14
*/
15
interface UserEvent {
16
/**
17
* Click on an element
18
* @param element - Element to click
19
* @param options - Click configuration options
20
*/
21
click(element: Element, options?: ClickOptions): Promise<void>;
22
23
/**
24
* Double-click on an element
25
* @param element - Element to double-click
26
* @param options - Click configuration options
27
*/
28
dblClick(element: Element, options?: ClickOptions): Promise<void>;
29
30
/**
31
* Type text into an element
32
* @param element - Input element to type into
33
* @param text - Text to type
34
* @param options - Typing configuration options
35
*/
36
type(element: Element, text: string, options?: TypeOptions): Promise<void>;
37
38
/**
39
* Clear the content of an input element
40
* @param element - Input element to clear
41
*/
42
clear(element: Element): Promise<void>;
43
44
/**
45
* Select options in a select element
46
* @param element - Select element
47
* @param values - Option values to select (string or array)
48
* @param options - Selection options
49
*/
50
selectOptions(element: Element, values: string | string[], options?: SelectOptions): Promise<void>;
51
52
/**
53
* Deselect options in a select element
54
* @param element - Select element
55
* @param values - Option values to deselect (string or array)
56
* @param options - Selection options
57
*/
58
deselectOptions(element: Element, values: string | string[], options?: SelectOptions): Promise<void>;
59
60
/**
61
* Upload files to a file input
62
* @param element - File input element
63
* @param file - File or array of files to upload
64
*/
65
upload(element: Element, file: File | File[]): Promise<void>;
66
67
/**
68
* Tab to the next focusable element
69
* @param options - Tab options
70
*/
71
tab(options?: TabOptions): Promise<void>;
72
73
/**
74
* Hover over an element
75
* @param element - Element to hover over
76
* @param options - Hover options
77
*/
78
hover(element: Element, options?: HoverOptions): Promise<void>;
79
80
/**
81
* Stop hovering over an element
82
* @param element - Element to unhover
83
* @param options - Hover options
84
*/
85
unhover(element: Element, options?: HoverOptions): Promise<void>;
86
87
/**
88
* Press and hold keyboard keys
89
* @param keys - Key or combination of keys to press
90
* @param options - Keyboard options
91
*/
92
keyboard(keys: string, options?: KeyboardOptions): Promise<void>;
93
94
/**
95
* Copy selected text to clipboard
96
*/
97
copy(): Promise<void>;
98
99
/**
100
* Cut selected text to clipboard
101
*/
102
cut(): Promise<void>;
103
104
/**
105
* Paste text from clipboard
106
*/
107
paste(): Promise<void>;
108
}
109
110
declare const userEvent: UserEvent;
111
```
112
113
### Click Interactions
114
115
```typescript { .api }
116
/**
117
* Options for click interactions
118
*/
119
interface ClickOptions {
120
/** Mouse button to use (0=left, 1=middle, 2=right) */
121
button?: number;
122
/** Control key modifier */
123
ctrlKey?: boolean;
124
/** Shift key modifier */
125
shiftKey?: boolean;
126
/** Alt key modifier */
127
altKey?: boolean;
128
/** Meta key modifier (Cmd on Mac, Windows key on PC) */
129
metaKey?: boolean;
130
/** Skip default behavior (e.g., don't focus element) */
131
skipDefaultPrevented?: boolean;
132
}
133
```
134
135
**Usage Examples:**
136
137
```typescript
138
import { within, userEvent } from "@storybook/testing-library";
139
140
export const ClickInteractions = {
141
play: async ({ canvasElement }) => {
142
const canvas = within(canvasElement);
143
144
// Basic click
145
const button = canvas.getByRole('button', { name: /submit/i });
146
await userEvent.click(button);
147
148
// Click with modifiers
149
const link = canvas.getByRole('link', { name: /external/i });
150
await userEvent.click(link, { ctrlKey: true }); // Ctrl+click
151
152
// Right-click (context menu)
153
await userEvent.click(button, { button: 2 });
154
155
// Double-click
156
const item = canvas.getByTestId('double-click-item');
157
await userEvent.dblClick(item);
158
}
159
};
160
```
161
162
### Typing Interactions
163
164
```typescript { .api }
165
/**
166
* Options for typing interactions
167
*/
168
interface TypeOptions {
169
/** Delay between keystrokes in milliseconds */
170
delay?: number;
171
/** Skip clicking the element before typing */
172
skipClick?: boolean;
173
/** Skip auto-closing of characters like quotes and brackets */
174
skipAutoClose?: boolean;
175
/** Initial selection start position */
176
initialSelectionStart?: number;
177
/** Initial selection end position */
178
initialSelectionEnd?: number;
179
}
180
```
181
182
**Usage Examples:**
183
184
```typescript
185
import { within, userEvent } from "@storybook/testing-library";
186
187
export const TypingInteractions = {
188
play: async ({ canvasElement }) => {
189
const canvas = within(canvasElement);
190
191
// Basic typing
192
const input = canvas.getByLabelText(/username/i);
193
await userEvent.type(input, 'john.doe@example.com');
194
195
// Typing with delay
196
const slowInput = canvas.getByLabelText(/description/i);
197
await userEvent.type(slowInput, 'Slow typing...', { delay: 100 });
198
199
// Clear and type
200
const existingInput = canvas.getByDisplayValue('existing text');
201
await userEvent.clear(existingInput);
202
await userEvent.type(existingInput, 'new text');
203
204
// Type special characters
205
const passwordInput = canvas.getByLabelText(/password/i);
206
await userEvent.type(passwordInput, 'P@ssw0rd!23');
207
}
208
};
209
```
210
211
### Selection Interactions
212
213
```typescript { .api }
214
/**
215
* Options for select interactions
216
*/
217
interface SelectOptions {
218
/** Skip clicking the element before selecting */
219
skipClick?: boolean;
220
}
221
```
222
223
**Usage Examples:**
224
225
```typescript
226
import { within, userEvent } from "@storybook/testing-library";
227
228
export const SelectionInteractions = {
229
play: async ({ canvasElement }) => {
230
const canvas = within(canvasElement);
231
232
// Select single option
233
const countrySelect = canvas.getByLabelText(/country/i);
234
await userEvent.selectOptions(countrySelect, 'USA');
235
236
// Select multiple options
237
const skillsSelect = canvas.getByLabelText(/skills/i);
238
await userEvent.selectOptions(skillsSelect, ['JavaScript', 'TypeScript', 'React']);
239
240
// Deselect options
241
await userEvent.deselectOptions(skillsSelect, 'JavaScript');
242
243
// Select by value
244
await userEvent.selectOptions(countrySelect, { target: { value: 'canada' } });
245
}
246
};
247
```
248
249
### File Upload Interactions
250
251
**Usage Examples:**
252
253
```typescript
254
import { within, userEvent } from "@storybook/testing-library";
255
256
export const FileUploadInteractions = {
257
play: async ({ canvasElement }) => {
258
const canvas = within(canvasElement);
259
260
// Upload single file
261
const fileInput = canvas.getByLabelText(/upload file/i);
262
const file = new File(['file content'], 'test.txt', { type: 'text/plain' });
263
await userEvent.upload(fileInput, file);
264
265
// Upload multiple files
266
const multiFileInput = canvas.getByLabelText(/upload multiple/i);
267
const files = [
268
new File(['image1'], 'image1.png', { type: 'image/png' }),
269
new File(['image2'], 'image2.jpg', { type: 'image/jpeg' })
270
];
271
await userEvent.upload(multiFileInput, files);
272
}
273
};
274
```
275
276
### Keyboard Interactions
277
278
```typescript { .api }
279
/**
280
* Options for keyboard interactions
281
*/
282
interface KeyboardOptions {
283
/** Skip auto-releasing pressed keys */
284
skipAutoClose?: boolean;
285
}
286
287
/**
288
* Options for tab navigation
289
*/
290
interface TabOptions {
291
/** Tab backwards (Shift+Tab) */
292
shift?: boolean;
293
}
294
```
295
296
**Usage Examples:**
297
298
```typescript
299
import { within, userEvent } from "@storybook/testing-library";
300
301
export const KeyboardInteractions = {
302
play: async ({ canvasElement }) => {
303
const canvas = within(canvasElement);
304
305
// Tab navigation
306
await userEvent.tab(); // Tab forward
307
await userEvent.tab({ shift: true }); // Tab backward
308
309
// Keyboard shortcuts
310
await userEvent.keyboard('{ctrl}a'); // Select all
311
await userEvent.keyboard('{ctrl}c'); // Copy
312
await userEvent.keyboard('{ctrl}v'); // Paste
313
314
// Special keys
315
await userEvent.keyboard('{Enter}'); // Enter key
316
await userEvent.keyboard('{Escape}'); // Escape key
317
await userEvent.keyboard('{ArrowDown}'); // Arrow down
318
319
// Key combinations
320
await userEvent.keyboard('{ctrl}{shift}k'); // Ctrl+Shift+K
321
322
// Use convenience methods
323
await userEvent.copy();
324
await userEvent.cut();
325
await userEvent.paste();
326
}
327
};
328
```
329
330
### Hover Interactions
331
332
```typescript { .api }
333
/**
334
* Options for hover interactions
335
*/
336
interface HoverOptions {
337
/** Skip pointer events */
338
skipPointerEvents?: boolean;
339
}
340
```
341
342
**Usage Examples:**
343
344
```typescript
345
import { within, userEvent, waitFor } from "@storybook/testing-library";
346
347
export const HoverInteractions = {
348
play: async ({ canvasElement }) => {
349
const canvas = within(canvasElement);
350
351
// Hover over element
352
const tooltip = canvas.getByTestId('tooltip-trigger');
353
await userEvent.hover(tooltip);
354
355
// Wait for tooltip to appear
356
await waitFor(() => {
357
expect(canvas.getByRole('tooltip')).toBeVisible();
358
});
359
360
// Stop hovering
361
await userEvent.unhover(tooltip);
362
363
// Tooltip should disappear
364
await waitFor(() => {
365
expect(canvas.queryByRole('tooltip')).not.toBeInTheDocument();
366
});
367
}
368
};
369
```
370
371
## Advanced Usage Patterns
372
373
### Realistic User Flows
374
375
```typescript
376
import { within, userEvent, waitFor } from "@storybook/testing-library";
377
378
export const CompleteUserFlow = {
379
play: async ({ canvasElement }) => {
380
const canvas = within(canvasElement);
381
382
// Fill out a form realistically
383
const emailInput = canvas.getByLabelText(/email/i);
384
const passwordInput = canvas.getByLabelText(/password/i);
385
const submitButton = canvas.getByRole('button', { name: /sign in/i });
386
387
// Focus and type email
388
await userEvent.click(emailInput);
389
await userEvent.type(emailInput, 'user@example.com');
390
391
// Tab to password field
392
await userEvent.tab();
393
await userEvent.type(passwordInput, 'securepassword');
394
395
// Submit form
396
await userEvent.click(submitButton);
397
398
// Wait for success message
399
await waitFor(() => {
400
expect(canvas.getByText(/welcome back/i)).toBeInTheDocument();
401
});
402
}
403
};
404
```
405
406
### Error Handling
407
408
```typescript
409
import { within, userEvent } from "@storybook/testing-library";
410
411
export const ErrorHandling = {
412
play: async ({ canvasElement }) => {
413
const canvas = within(canvasElement);
414
415
try {
416
// Attempt to interact with element that might not exist
417
const button = canvas.queryByRole('button', { name: /optional/i });
418
if (button) {
419
await userEvent.click(button);
420
}
421
} catch (error) {
422
console.error('User interaction failed:', error);
423
}
424
}
425
};
426
```