0
# DOM Testing
1
2
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.
3
4
## Capabilities
5
6
### Element Queries
7
8
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).
9
10
#### Get Queries (Sync, throws if not found)
11
12
```typescript { .api }
13
/**
14
* Get element by ARIA role
15
* @param container - Container element to search within
16
* @param role - ARIA role to search for
17
* @param options - Additional query options
18
* @returns Found element (throws if not found)
19
*/
20
function getByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement;
21
function getAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];
22
23
function getByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
24
function getAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
25
26
function getByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
27
function getAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
28
29
function getByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
30
function getAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
31
32
function getByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
33
function getAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
34
35
function getByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
36
function getAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
37
38
function getByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement;
39
function getAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement[];
40
41
function getByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement;
42
function getAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement[];
43
44
interface ByRoleOptions extends MatcherOptions {
45
checked?: boolean;
46
selected?: boolean;
47
expanded?: boolean;
48
pressed?: boolean;
49
level?: number;
50
name?: string | RegExp;
51
description?: string | RegExp;
52
}
53
54
interface SelectorMatcherOptions extends MatcherOptions {
55
selector?: string;
56
}
57
58
interface MatcherOptions {
59
exact?: boolean;
60
normalizer?: (text: string) => string;
61
}
62
```
63
64
#### Find Queries (Async, throws if not found)
65
66
```typescript { .api }
67
/**
68
* Asynchronously find element by ARIA role
69
* @param container - Container element to search within
70
* @param role - ARIA role to search for
71
* @param options - Additional query options
72
* @param waitForOptions - Options for waiting
73
* @returns Promise resolving to found element
74
*/
75
function findByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
76
function findAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
77
78
function findByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
79
function findAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
80
81
function findByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
82
function findAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
83
84
function findByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
85
function findAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
86
87
function findByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
88
function findAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
89
90
function findByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
91
function findAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
92
93
function findByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
94
function findAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
95
96
function findByTestId(container: HTMLElement, testId: string, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
97
function findAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;
98
```
99
100
#### Query Queries (Sync, returns null if not found)
101
102
```typescript { .api }
103
/**
104
* Query element by ARIA role (returns null if not found)
105
* @param container - Container element to search within
106
* @param role - ARIA role to search for
107
* @param options - Additional query options
108
* @returns Found element or null
109
*/
110
function queryByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement | null;
111
function queryAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];
112
113
function queryByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
114
function queryAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
115
116
function queryByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
117
function queryAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
118
119
function queryByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
120
function queryAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
121
122
function queryByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
123
function queryAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
124
125
function queryByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
126
function queryAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];
127
128
function queryByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement | null;
129
function queryAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement[];
130
131
function queryByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement | null;
132
function queryAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement[];
133
134
function queryByAttribute(container: HTMLElement, name: string, value?: string | RegExp, options?: MatcherOptions): HTMLElement | null;
135
function queryAllByAttribute(container: HTMLElement, name: string, value?: string | RegExp, options?: MatcherOptions): HTMLElement[];
136
```
137
138
**Query Usage Examples:**
139
140
```typescript
141
import { within, getByRole, findByText, queryByTestId } from '@storybook/test';
142
143
export const QueryStory = {
144
play: async ({ canvasElement }) => {
145
const canvas = within(canvasElement);
146
147
// Get queries (throw if not found)
148
const button = canvas.getByRole('button', { name: /submit/i });
149
const heading = canvas.getByText('Welcome');
150
const input = canvas.getByLabelText('Email');
151
152
// Find queries (async, wait for element)
153
const asyncContent = await canvas.findByText('Loaded content');
154
const asyncButton = await canvas.findByRole('button', { name: /save/i });
155
156
// Query queries (return null if not found)
157
const optionalElement = canvas.queryByTestId('optional-feature');
158
if (optionalElement) {
159
// Element exists, interact with it
160
expect(optionalElement).toBeInTheDocument();
161
}
162
163
// Multiple elements
164
const allButtons = canvas.getAllByRole('button');
165
const allInputs = await canvas.findAllByRole('textbox');
166
167
expect(allButtons.length).toBeGreaterThan(0);
168
expect(allInputs.length).toBeGreaterThan(0);
169
},
170
};
171
```
172
173
### Element Scoping
174
175
Scope queries to specific parts of the DOM using the `within` function.
176
177
```typescript { .api }
178
/**
179
* Create bound queries scoped to a specific element
180
* @param element - Element to scope queries within
181
* @returns Object with all query functions bound to the element
182
*/
183
function within(element: HTMLElement): BoundFunctions<typeof queries>;
184
185
type BoundFunctions<T> = {
186
[K in keyof T]: T[K] extends (...args: any[]) => any
187
? (...args: Parameters<T[K]>) => ReturnType<T[K]>
188
: T[K];
189
};
190
191
/**
192
* Global screen object for document-wide queries (deprecated in Storybook context)
193
* Use within(canvasElement) instead
194
*/
195
const screen: BoundFunctions<typeof queries>;
196
```
197
198
**Scoping Usage Examples:**
199
200
```typescript
201
import { within, expect } from '@storybook/test';
202
203
export const ScopingStory = {
204
play: async ({ canvasElement }) => {
205
// Scope queries to the story canvas
206
const canvas = within(canvasElement);
207
208
// Find a modal or specific section
209
const modal = canvas.getByRole('dialog');
210
const modalScope = within(modal);
211
212
// Queries within modal only
213
const modalButton = modalScope.getByRole('button', { name: /close/i });
214
const modalHeading = modalScope.getByRole('heading');
215
216
// This would find buttons anywhere in canvas
217
const allCanvasButtons = canvas.getAllByRole('button');
218
219
// This only finds buttons within the modal
220
const modalButtons = modalScope.getAllByRole('button');
221
222
expect(modalButtons.length).toBeLesserThanOrEqual(allCanvasButtons.length);
223
},
224
};
225
```
226
227
### User Interactions
228
229
Simulate user interactions with elements using the instrumented userEvent API.
230
231
```typescript { .api }
232
interface UserEvent {
233
/**
234
* Click an element
235
* @param element - Element to click
236
* @param options - Click options
237
*/
238
click(element: Element, options?: ClickOptions): Promise<void>;
239
240
/**
241
* Double-click an element
242
* @param element - Element to double-click
243
* @param options - Click options
244
*/
245
dblClick(element: Element, options?: ClickOptions): Promise<void>;
246
247
/**
248
* Type text into an element
249
* @param element - Element to type into
250
* @param text - Text to type
251
* @param options - Typing options
252
*/
253
type(element: Element, text: string, options?: TypeOptions): Promise<void>;
254
255
/**
256
* Clear an input element
257
* @param element - Element to clear
258
*/
259
clear(element: Element): Promise<void>;
260
261
/**
262
* Select options in a select element
263
* @param element - Select element
264
* @param values - Values to select
265
*/
266
selectOptions(element: Element, values: string | string[]): Promise<void>;
267
268
/**
269
* Deselect options in a select element
270
* @param element - Select element
271
* @param values - Values to deselect
272
*/
273
deselectOptions(element: Element, values: string | string[]): Promise<void>;
274
275
/**
276
* Upload files to a file input
277
* @param element - File input element
278
* @param file - File or files to upload
279
*/
280
upload(element: Element, file: File | File[]): Promise<void>;
281
282
/**
283
* Tab to the next focusable element
284
* @param options - Tab options
285
*/
286
tab(options?: TabOptions): Promise<void>;
287
288
/**
289
* Hover over an element
290
* @param element - Element to hover
291
*/
292
hover(element: Element): Promise<void>;
293
294
/**
295
* Stop hovering over an element
296
* @param element - Element to stop hovering
297
*/
298
unhover(element: Element): Promise<void>;
299
300
/**
301
* Paste text into an element
302
* @param text - Text to paste
303
*/
304
paste(text: string): Promise<void>;
305
306
/**
307
* Press keyboard key(s)
308
* @param keys - Key or key combination to press
309
*/
310
keyboard(keys: string): Promise<void>;
311
}
312
313
interface ClickOptions {
314
button?: number;
315
detail?: number;
316
ctrlKey?: boolean;
317
shiftKey?: boolean;
318
altKey?: boolean;
319
metaKey?: boolean;
320
}
321
322
interface TypeOptions {
323
delay?: number;
324
skipClick?: boolean;
325
skipAutoClose?: boolean;
326
initialSelectionStart?: number;
327
initialSelectionEnd?: number;
328
}
329
330
interface TabOptions {
331
shift?: boolean;
332
}
333
```
334
335
**User Interaction Examples:**
336
337
```typescript
338
import { userEvent, within, expect } from '@storybook/test';
339
340
export const InteractionStory = {
341
play: async ({ canvasElement }) => {
342
const canvas = within(canvasElement);
343
344
// Basic interactions
345
const button = canvas.getByRole('button', { name: /submit/i });
346
await userEvent.click(button);
347
348
const input = canvas.getByLabelText('Username');
349
await userEvent.type(input, 'john.doe');
350
expect(input).toHaveValue('john.doe');
351
352
// Clear and retype
353
await userEvent.clear(input);
354
await userEvent.type(input, 'jane.doe');
355
expect(input).toHaveValue('jane.doe');
356
357
// Select dropdown
358
const select = canvas.getByLabelText('Country');
359
await userEvent.selectOptions(select, 'us');
360
expect(select).toHaveValue('us');
361
362
// Multiple selections
363
const multiSelect = canvas.getByLabelText('Skills');
364
await userEvent.selectOptions(multiSelect, ['javascript', 'typescript']);
365
366
// File upload
367
const fileInput = canvas.getByLabelText('Upload file');
368
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
369
await userEvent.upload(fileInput, file);
370
expect(fileInput.files![0]).toBe(file);
371
372
// Keyboard interactions
373
await userEvent.keyboard('{Enter}');
374
await userEvent.keyboard('{Escape}');
375
await userEvent.keyboard('{ctrl}a'); // Select all
376
377
// Hover effects
378
const tooltip = canvas.getByText('Hover me');
379
await userEvent.hover(tooltip);
380
await expect(canvas.findByText('Tooltip content')).resolves.toBeInTheDocument();
381
382
await userEvent.unhover(tooltip);
383
expect(canvas.queryByText('Tooltip content')).not.toBeInTheDocument();
384
},
385
};
386
```
387
388
### Async Utilities
389
390
Wait for conditions and element changes in the DOM.
391
392
```typescript { .api }
393
/**
394
* Wait for a condition to be met
395
* @param callback - Function to test condition
396
* @param options - Wait options
397
* @returns Promise resolving when condition is met
398
*/
399
function waitFor<T>(callback: () => T | Promise<T>, options?: WaitForOptions): Promise<T>;
400
401
/**
402
* Wait for elements to be removed from the DOM
403
* @param callback - Function returning elements to wait for removal
404
* @param options - Wait options
405
* @returns Promise resolving when elements are removed
406
*/
407
function waitForElementToBeRemoved(
408
callback: () => Element | Element[] | Promise<Element | Element[]>,
409
options?: WaitForOptions
410
): Promise<void>;
411
412
interface WaitForOptions {
413
container?: HTMLElement;
414
timeout?: number;
415
interval?: number;
416
onTimeout?: (error: Error) => Error;
417
mutationObserverOptions?: MutationObserverInit;
418
}
419
```
420
421
**Async Utility Examples:**
422
423
```typescript
424
import { waitFor, waitForElementToBeRemoved, within, userEvent, expect } from '@storybook/test';
425
426
export const AsyncStory = {
427
play: async ({ canvasElement }) => {
428
const canvas = within(canvasElement);
429
430
// Wait for async content to appear
431
await waitFor(() => {
432
expect(canvas.getByText('Loading complete')).toBeInTheDocument();
433
});
434
435
// Wait for API call to complete
436
const loadButton = canvas.getByRole('button', { name: /load data/i });
437
await userEvent.click(loadButton);
438
439
await waitFor(
440
async () => {
441
const dataList = canvas.getByRole('list');
442
const items = canvas.getAllByRole('listitem');
443
expect(items.length).toBeGreaterThan(0);
444
return items;
445
},
446
{ timeout: 5000 }
447
);
448
449
// Wait for element removal
450
const dismissButton = canvas.getByRole('button', { name: /dismiss/i });
451
await userEvent.click(dismissButton);
452
453
await waitForElementToBeRemoved(
454
() => canvas.queryByText('Notification message'),
455
{ timeout: 3000 }
456
);
457
458
// Custom timeout and interval
459
await waitFor(
460
() => {
461
const status = canvas.getByTestId('status');
462
expect(status).toHaveTextContent('Ready');
463
},
464
{
465
timeout: 10000,
466
interval: 500,
467
}
468
);
469
},
470
};
471
```
472
473
### DOM Utilities
474
475
Additional utilities for DOM interaction and debugging.
476
477
```typescript { .api }
478
/**
479
* Create DOM events programmatically
480
* @param eventName - Name of the event
481
* @param node - Target node for the event
482
* @param init - Event initialization options
483
*/
484
function createEvent(eventName: string, node: Element, init?: EventInit): Event;
485
486
/**
487
* Fire events on DOM elements
488
*/
489
const fireEvent: FireFunction & FireObject;
490
491
interface FireFunction {
492
(element: Element, event: Event): boolean;
493
}
494
495
interface FireObject {
496
click(element: Element, options?: EventInit): boolean;
497
change(element: Element, options?: EventInit): boolean;
498
input(element: Element, options?: EventInit): boolean;
499
keyDown(element: Element, options?: KeyboardEventInit): boolean;
500
keyPress(element: Element, options?: KeyboardEventInit): boolean;
501
keyUp(element: Element, options?: KeyboardEventInit): boolean;
502
mouseDown(element: Element, options?: MouseEventInit): boolean;
503
mouseEnter(element: Element, options?: MouseEventInit): boolean;
504
mouseLeave(element: Element, options?: MouseEventInit): boolean;
505
mouseMove(element: Element, options?: MouseEventInit): boolean;
506
mouseOut(element: Element, options?: MouseEventInit): boolean;
507
mouseOver(element: Element, options?: MouseEventInit): boolean;
508
mouseUp(element: Element, options?: MouseEventInit): boolean;
509
focus(element: Element, options?: FocusEventInit): boolean;
510
blur(element: Element, options?: FocusEventInit): boolean;
511
submit(element: Element, options?: EventInit): boolean;
512
}
513
514
/**
515
* Pretty print DOM structure for debugging
516
* @param element - Element to print
517
* @param maxLength - Maximum length of output
518
* @param options - Pretty printing options
519
*/
520
function prettyDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): string;
521
522
/**
523
* Log DOM structure to console
524
* @param element - Element to log
525
* @param maxLength - Maximum length of output
526
* @param options - Pretty printing options
527
*/
528
function logDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): void;
529
530
/**
531
* Log ARIA roles for debugging accessibility
532
* @param element - Element to analyze
533
*/
534
function logRoles(element: Element): void;
535
536
/**
537
* Get suggested query for an element
538
* @param element - Element to analyze
539
* @param variant - Query variant to suggest
540
*/
541
function getSuggestedQuery(element: Element, variant?: 'get' | 'find' | 'query'): string;
542
543
/**
544
* Get default text normalizer function
545
* @returns Default normalizer function
546
*/
547
function getDefaultNormalizer(): (text: string) => string;
548
549
/**
550
* Get error message for element not found
551
* @param message - Error message
552
* @param container - Container element
553
* @returns Error instance
554
*/
555
function getElementError(message: string, container: HTMLElement): Error;
556
557
/**
558
* Get text content from a DOM node
559
* @param node - DOM node to extract text from
560
* @returns Text content
561
*/
562
function getNodeText(node: Node): string;
563
564
/**
565
* Get bound query functions for a specific element
566
* @param element - Element to bind queries to
567
* @returns Object with bound query functions
568
*/
569
function getQueriesForElement(element: HTMLElement): BoundFunctions<typeof queries>;
570
571
/**
572
* Get all ARIA roles for an element
573
* @param element - Element to analyze
574
* @returns Object with role information
575
*/
576
function getRoles(element: Element): { [role: string]: HTMLElement[] };
577
578
/**
579
* Check if element is accessible
580
* @param element - Element to check
581
* @returns True if element is accessible
582
*/
583
function isInaccessible(element: Element): boolean;
584
585
/**
586
* Pretty format values for debugging
587
* @param value - Value to format
588
* @param options - Formatting options
589
* @returns Formatted string
590
*/
591
function prettyFormat(value: any, options?: any): string;
592
593
/**
594
* Query helper utilities
595
*/
596
const queryHelpers: {
597
queryByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement | null;
598
queryAllByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement[];
599
getElementError: (message: string, container: HTMLElement) => Error;
600
};
601
602
/**
603
* All available query functions
604
*/
605
const queries: {
606
queryByRole: typeof queryByRole;
607
queryAllByRole: typeof queryAllByRole;
608
getByRole: typeof getByRole;
609
getAllByRole: typeof getAllByRole;
610
findByRole: typeof findByRole;
611
findAllByRole: typeof findAllByRole;
612
// ... (all other query functions)
613
};
614
615
interface PrettyDOMOptions {
616
highlight?: boolean;
617
filterNode?: (node: Node) => boolean;
618
}
619
```
620
621
**DOM Utility Examples:**
622
623
```typescript
624
import {
625
fireEvent,
626
createEvent,
627
prettyDOM,
628
logDOM,
629
logRoles,
630
getSuggestedQuery,
631
within
632
} from '@storybook/test';
633
634
export const UtilityStory = {
635
play: async ({ canvasElement }) => {
636
const canvas = within(canvasElement);
637
638
// Manual event firing
639
const button = canvas.getByRole('button');
640
fireEvent.click(button);
641
fireEvent.mouseEnter(button);
642
fireEvent.mouseLeave(button);
643
644
// Custom events
645
const customEvent = createEvent('customEvent', button, {
646
bubbles: true,
647
cancelable: true
648
});
649
fireEvent(button, customEvent);
650
651
// Debugging utilities
652
const form = canvas.getByRole('form');
653
654
// Log DOM structure
655
console.log('Form DOM:', prettyDOM(form));
656
logDOM(form); // Logs to console with nice formatting
657
658
// Analyze accessibility
659
logRoles(form); // Shows all ARIA roles
660
661
// Get suggested queries
662
const input = canvas.getByLabelText('Email');
663
console.log('Suggested query:', getSuggestedQuery(input));
664
// Output: getByLabelText(/email/i)
665
666
// Additional utility functions
667
console.log('Node text:', getNodeText(input));
668
console.log('Is accessible:', !isInaccessible(input));
669
console.log('Element roles:', getRoles(form));
670
671
// Get bound queries for specific element
672
const boundQueries = getQueriesForElement(form);
673
const formButton = boundQueries.getByRole('button');
674
675
// Pretty formatting for debugging
676
console.log('Pretty formatted value:', prettyFormat({ key: 'value' }));
677
},
678
};
679
```
680
681
### Configuration
682
683
Configure testing-library behavior globally.
684
685
```typescript { .api }
686
/**
687
* Configure testing-library options
688
* @param options - Configuration options
689
*/
690
function configure(options: ConfigureOptions): void;
691
692
/**
693
* Get current configuration
694
* @returns Current configuration object
695
*/
696
function getConfig(): Config;
697
698
interface ConfigureOptions {
699
testIdAttribute?: string;
700
asyncUtilTimeout?: number;
701
computedStyleSupportsPseudoElements?: boolean;
702
defaultHidden?: boolean;
703
showOriginalStackTrace?: boolean;
704
throwSuggestions?: boolean;
705
getElementError?: (message: string, container: HTMLElement) => Error;
706
}
707
708
interface Config extends ConfigureOptions {
709
testIdAttribute: string;
710
asyncUtilTimeout: number;
711
computedStyleSupportsPseudoElements: boolean;
712
defaultHidden: boolean;
713
showOriginalStackTrace: boolean;
714
throwSuggestions: boolean;
715
}
716
```
717
718
**Configuration Examples:**
719
720
```typescript
721
import { configure, getConfig } from '@storybook/test';
722
723
// Configure at story level or in preview.js
724
configure({
725
testIdAttribute: 'data-cy', // Use Cypress test IDs
726
asyncUtilTimeout: 5000, // Increase timeout
727
throwSuggestions: false, // Disable query suggestions
728
});
729
730
export const ConfigStory = {
731
play: async () => {
732
const config = getConfig();
733
console.log('Current timeout:', config.asyncUtilTimeout);
734
735
// Now getByTestId uses 'data-cy' instead of 'data-testid'
736
},
737
};
738
```
739
740
### Query by Attribute
741
742
Generic attribute-based queries for custom attributes not covered by other query types.
743
744
```typescript { .api }
745
/**
746
* Query elements by any attribute value
747
* @param container - Container element to search within
748
* @param attribute - Attribute name to search for
749
* @param value - Attribute value to match
750
* @param options - Additional query options
751
* @returns Found element or null
752
*/
753
function queryByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement | null;
754
function queryAllByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement[];
755
```
756
757
**Usage Examples:**
758
759
```typescript
760
import { queryByAttribute, queryAllByAttribute, within } from '@storybook/test';
761
762
export const AttributeQueryStory = {
763
play: async ({ canvasElement }) => {
764
const canvas = within(canvasElement);
765
766
// Query by custom attribute
767
const element = queryByAttribute(canvasElement, 'data-custom', 'value');
768
if (element) {
769
console.log('Found element with data-custom="value"');
770
}
771
772
// Query all elements with specific attribute
773
const allElements = queryAllByAttribute(canvasElement, 'aria-expanded', 'true');
774
console.log(`Found ${allElements.length} expanded elements`);
775
},
776
};
777
```
778
779
### Fire Events
780
781
Manually trigger DOM events for advanced testing scenarios.
782
783
```typescript { .api }
784
interface FireEvent {
785
(element: Element, event: Event): boolean;
786
787
// Event-specific methods
788
abort(element: Element, eventProperties?: {}): boolean;
789
animationEnd(element: Element, eventProperties?: {}): boolean;
790
animationIteration(element: Element, eventProperties?: {}): boolean;
791
animationStart(element: Element, eventProperties?: {}): boolean;
792
blur(element: Element, eventProperties?: {}): boolean;
793
canPlay(element: Element, eventProperties?: {}): boolean;
794
canPlayThrough(element: Element, eventProperties?: {}): boolean;
795
change(element: Element, eventProperties?: {}): boolean;
796
click(element: Element, eventProperties?: {}): boolean;
797
contextMenu(element: Element, eventProperties?: {}): boolean;
798
copy(element: Element, eventProperties?: {}): boolean;
799
cut(element: Element, eventProperties?: {}): boolean;
800
doubleClick(element: Element, eventProperties?: {}): boolean;
801
drag(element: Element, eventProperties?: {}): boolean;
802
dragEnd(element: Element, eventProperties?: {}): boolean;
803
dragEnter(element: Element, eventProperties?: {}): boolean;
804
dragExit(element: Element, eventProperties?: {}): boolean;
805
dragLeave(element: Element, eventProperties?: {}): boolean;
806
dragOver(element: Element, eventProperties?: {}): boolean;
807
dragStart(element: Element, eventProperties?: {}): boolean;
808
drop(element: Element, eventProperties?: {}): boolean;
809
durationChange(element: Element, eventProperties?: {}): boolean;
810
emptied(element: Element, eventProperties?: {}): boolean;
811
encrypted(element: Element, eventProperties?: {}): boolean;
812
ended(element: Element, eventProperties?: {}): boolean;
813
error(element: Element, eventProperties?: {}): boolean;
814
focus(element: Element, eventProperties?: {}): boolean;
815
focusIn(element: Element, eventProperties?: {}): boolean;
816
focusOut(element: Element, eventProperties?: {}): boolean;
817
input(element: Element, eventProperties?: {}): boolean;
818
invalid(element: Element, eventProperties?: {}): boolean;
819
keyDown(element: Element, eventProperties?: {}): boolean;
820
keyPress(element: Element, eventProperties?: {}): boolean;
821
keyUp(element: Element, eventProperties?: {}): boolean;
822
load(element: Element, eventProperties?: {}): boolean;
823
loadStart(element: Element, eventProperties?: {}): boolean;
824
loadedData(element: Element, eventProperties?: {}): boolean;
825
loadedMetadata(element: Element, eventProperties?: {}): boolean;
826
mouseDown(element: Element, eventProperties?: {}): boolean;
827
mouseEnter(element: Element, eventProperties?: {}): boolean;
828
mouseLeave(element: Element, eventProperties?: {}): boolean;
829
mouseMove(element: Element, eventProperties?: {}): boolean;
830
mouseOut(element: Element, eventProperties?: {}): boolean;
831
mouseOver(element: Element, eventProperties?: {}): boolean;
832
mouseUp(element: Element, eventProperties?: {}): boolean;
833
paste(element: Element, eventProperties?: {}): boolean;
834
pause(element: Element, eventProperties?: {}): boolean;
835
play(element: Element, eventProperties?: {}): boolean;
836
playing(element: Element, eventProperties?: {}): boolean;
837
pointerCancel(element: Element, eventProperties?: {}): boolean;
838
pointerDown(element: Element, eventProperties?: {}): boolean;
839
pointerEnter(element: Element, eventProperties?: {}): boolean;
840
pointerLeave(element: Element, eventProperties?: {}): boolean;
841
pointerMove(element: Element, eventProperties?: {}): boolean;
842
pointerOut(element: Element, eventProperties?: {}): boolean;
843
pointerOver(element: Element, eventProperties?: {}): boolean;
844
pointerUp(element: Element, eventProperties?: {}): boolean;
845
progress(element: Element, eventProperties?: {}): boolean;
846
rateChange(element: Element, eventProperties?: {}): boolean;
847
scroll(element: Element, eventProperties?: {}): boolean;
848
seeked(element: Element, eventProperties?: {}): boolean;
849
seeking(element: Element, eventProperties?: {}): boolean;
850
select(element: Element, eventProperties?: {}): boolean;
851
stalled(element: Element, eventProperties?: {}): boolean;
852
submit(element: Element, eventProperties?: {}): boolean;
853
suspend(element: Element, eventProperties?: {}): boolean;
854
timeUpdate(element: Element, eventProperties?: {}): boolean;
855
touchCancel(element: Element, eventProperties?: {}): boolean;
856
touchEnd(element: Element, eventProperties?: {}): boolean;
857
touchMove(element: Element, eventProperties?: {}): boolean;
858
touchStart(element: Element, eventProperties?: {}): boolean;
859
transitionEnd(element: Element, eventProperties?: {}): boolean;
860
volumeChange(element: Element, eventProperties?: {}): boolean;
861
waiting(element: Element, eventProperties?: {}): boolean;
862
wheel(element: Element, eventProperties?: {}): boolean;
863
}
864
865
/**
866
* Create custom DOM events
867
* @param eventName - Name of the event to create
868
* @param node - Target element for the event
869
* @param init - Event initialization options
870
* @returns Created event object
871
*/
872
function createEvent(eventName: string, node: Element, init?: {}): Event;
873
874
const fireEvent: FireEvent;
875
```
876
877
**Usage Examples:**
878
879
```typescript
880
import { fireEvent, createEvent, within } from '@storybook/test';
881
882
export const FireEventStory = {
883
play: async ({ canvasElement }) => {
884
const canvas = within(canvasElement);
885
const button = canvas.getByRole('button');
886
887
// Fire common events
888
fireEvent.click(button);
889
fireEvent.mouseOver(button);
890
fireEvent.focus(button);
891
892
// Fire events with custom properties
893
fireEvent.keyDown(button, { key: 'Enter', code: 'Enter' });
894
895
// Create and fire custom events
896
const customEvent = createEvent('custom-event', button, { detail: { custom: 'data' } });
897
fireEvent(button, customEvent);
898
},
899
};
900
```
901
902
### Debug and Utility Functions
903
904
Helper functions for debugging, introspection, and DOM manipulation.
905
906
```typescript { .api }
907
/**
908
* Pretty-print DOM element structure for debugging
909
* @param element - Element to print (defaults to document.body)
910
* @param maxLength - Maximum string length (default: 7000)
911
* @param options - Formatting options
912
* @returns Formatted string representation of DOM
913
*/
914
function prettyDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): string;
915
916
/**
917
* Log DOM structure to console
918
* @param element - Element to log (defaults to document.body)
919
* @param maxLength - Maximum string length
920
* @param options - Formatting options
921
*/
922
function logDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): void;
923
924
/**
925
* Log all ARIA roles found in the DOM
926
* @param element - Container element (defaults to document.body)
927
*/
928
function logRoles(element?: Element | HTMLDocument): void;
929
930
/**
931
* Get all ARIA roles present in an element
932
* @param element - Container element
933
* @returns Array of role information objects
934
*/
935
function getRoles(element: Element): Array<{ role: string; elements: Element[] }>;
936
937
/**
938
* Get text content of a node including hidden text
939
* @param node - DOM node to extract text from
940
* @returns Text content string
941
*/
942
function getNodeText(node: Node): string;
943
944
/**
945
* Check if an element is inaccessible to screen readers
946
* @param element - Element to check
947
* @returns True if element is inaccessible
948
*/
949
function isInaccessible(element: Element): boolean;
950
951
/**
952
* Get suggested query for finding an element
953
* @param element - Element to analyze
954
* @param variant - Query variant preference ('get' | 'find' | 'query')
955
* @param method - Query method preference
956
* @returns Suggested query string
957
*/
958
function getSuggestedQuery(element: Element, variant?: string, method?: string): string;
959
960
/**
961
* Create an error with element information
962
* @param message - Error message
963
* @param container - Container element for context
964
* @returns Error with DOM context
965
*/
966
function getElementError(message: string, container: Element): Error;
967
968
/**
969
* Get default text normalizer function
970
* @returns Default normalizer function
971
*/
972
function getDefaultNormalizer(): (text: string) => string;
973
974
/**
975
* Pretty format any value for display
976
* @param value - Value to format
977
* @returns Formatted string representation
978
*/
979
function prettyFormat(value: any): string;
980
```
981
982
**Usage Examples:**
983
984
```typescript
985
import {
986
prettyDOM, logDOM, logRoles, getRoles, getNodeText,
987
isInaccessible, getSuggestedQuery, within
988
} from '@storybook/test';
989
990
export const DebugUtilsStory = {
991
play: async ({ canvasElement }) => {
992
const canvas = within(canvasElement);
993
994
// Debug DOM structure
995
console.log('DOM structure:', prettyDOM(canvasElement));
996
logDOM(canvasElement, 1000); // Log to console with max length
997
998
// Analyze accessibility
999
logRoles(canvasElement); // Log all ARIA roles
1000
const roles = getRoles(canvasElement);
1001
console.log('Available roles:', roles.map(r => r.role));
1002
1003
// Get element text content
1004
const button = canvas.getByRole('button');
1005
console.log('Button text:', getNodeText(button));
1006
1007
// Check accessibility
1008
if (isInaccessible(button)) {
1009
console.warn('Button is not accessible to screen readers');
1010
}
1011
1012
// Get suggested query for element
1013
const suggestion = getSuggestedQuery(button, 'get');
1014
console.log('Suggested query:', suggestion);
1015
},
1016
};
1017
```
1018
1019
### Query Building and Customization
1020
1021
Advanced utilities for building custom queries and extending testing-library functionality.
1022
1023
```typescript { .api }
1024
/**
1025
* Build custom query functions from base query logic
1026
* @param queryName - Name for the query type
1027
* @param queryAllByAttribute - Function to find all matching elements
1028
* @param getMultipleError - Error function for multiple matches
1029
* @param getMissingError - Error function for no matches
1030
* @returns Object with get, getAll, query, queryAll, find, findAll functions
1031
*/
1032
function buildQueries(
1033
queryName: string,
1034
queryAllByAttribute: (container: Element, ...args: any[]) => Element[],
1035
getMultipleError: (container: Element, ...args: any[]) => string,
1036
getMissingError: (container: Element, ...args: any[]) => string
1037
): {
1038
[key: string]: (...args: any[]) => Element | Element[] | null | Promise<Element | Element[]>;
1039
};
1040
1041
/**
1042
* Get bound query functions for a specific element
1043
* @param element - Element to bind queries to
1044
* @returns Object with all query functions pre-bound to the element
1045
*/
1046
function getQueriesForElement(element: Element): BoundFunctions<typeof queries>;
1047
1048
/**
1049
* Collection of all base query functions
1050
*/
1051
const queries: {
1052
queryByRole: typeof queryByRole;
1053
queryAllByRole: typeof queryAllByRole;
1054
getByRole: typeof getByRole;
1055
getAllByRole: typeof getAllByRole;
1056
findByRole: typeof findByRole;
1057
findAllByRole: typeof findAllByRole;
1058
// ... all other query variants
1059
};
1060
1061
/**
1062
* Helpers for building custom queries
1063
*/
1064
const queryHelpers: {
1065
queryByAttribute: typeof queryByAttribute;
1066
queryAllByAttribute: typeof queryAllByAttribute;
1067
buildQueries: typeof buildQueries;
1068
getMultipleElementsFoundError: (message: string, container: Element) => Error;
1069
getElementError: typeof getElementError;
1070
};
1071
```
1072
1073
**Usage Examples:**
1074
1075
```typescript
1076
import { buildQueries, getQueriesForElement, queryHelpers } from '@storybook/test';
1077
1078
// Build custom query for data-cy attributes (Cypress style)
1079
const [
1080
queryByCy,
1081
queryAllByCy,
1082
getByCy,
1083
getAllByCy,
1084
findByCy,
1085
findAllByCy,
1086
] = buildQueries(
1087
'Cy',
1088
(container, id) => queryHelpers.queryAllByAttribute(container, 'data-cy', id),
1089
() => 'Found multiple elements with the same data-cy attribute',
1090
() => 'Unable to find an element with the data-cy attribute'
1091
);
1092
1093
export const CustomQueryStory = {
1094
play: async ({ canvasElement }) => {
1095
// Use custom query
1096
const element = getByCy(canvasElement, 'submit-button');
1097
1098
// Get all queries bound to an element
1099
const boundQueries = getQueriesForElement(canvasElement);
1100
const sameElement = boundQueries.getByCy('submit-button');
1101
},
1102
};
1103
```
1104
1105
### Screen Object (Legacy - Use within() instead)
1106
1107
The screen object provides global queries but is discouraged in Storybook. Use `within(canvasElement)` instead.
1108
1109
```typescript { .api }
1110
/**
1111
* Global query object (deprecated in Storybook context)
1112
* @deprecated Use within(canvasElement) instead for Storybook stories
1113
*/
1114
const screen: BoundFunctions<typeof queries>;
1115
```
1116
1117
**Migration Example:**
1118
1119
```typescript
1120
import { screen, within } from '@storybook/test';
1121
1122
export const MigrationStory = {
1123
play: async ({ canvasElement }) => {
1124
// ❌ Don't use screen in Storybook (will show warning)
1125
// const button = screen.getByRole('button');
1126
1127
// ✅ Use within() instead
1128
const canvas = within(canvasElement);
1129
const button = canvas.getByRole('button');
1130
},
1131
};
1132
```