0
# Testing & Simulation
1
2
Tools for simulating DOM events, page lifecycle, and creating test utilities. These functions are essential for testing stories and addons in various environments, enabling comprehensive testing scenarios and cross-platform compatibility.
3
4
## Capabilities
5
6
### Page Lifecycle Simulation
7
8
Functions for simulating browser page lifecycle events in testing environments.
9
10
```typescript { .api }
11
/**
12
* Simulates complete page loading with script execution and DOM setup
13
* @param container - DOM element to simulate page load within
14
*/
15
function simulatePageLoad(container: Element): void;
16
17
/**
18
* Triggers DOMContentLoaded event for testing DOM-dependent code
19
*/
20
function simulateDOMContentLoaded(): void;
21
```
22
23
**Usage Examples:**
24
25
```typescript
26
import { simulatePageLoad, simulateDOMContentLoaded } from "@storybook/preview-api";
27
28
// Simulate page load in a container
29
test('component initializes after page load', () => {
30
const container = document.createElement('div');
31
document.body.appendChild(container);
32
33
// Add your component to the container
34
container.innerHTML = '<my-component></my-component>';
35
36
// Simulate page load to trigger initialization scripts
37
simulatePageLoad(container);
38
39
// Verify component initialized correctly
40
expect(container.querySelector('my-component')).toHaveClass('initialized');
41
});
42
43
// Simulate DOMContentLoaded for testing initialization
44
test('library initializes on DOMContentLoaded', () => {
45
const mockInit = jest.fn();
46
document.addEventListener('DOMContentLoaded', mockInit);
47
48
// Trigger the event
49
simulateDOMContentLoaded();
50
51
expect(mockInit).toHaveBeenCalled();
52
});
53
```
54
55
### Playwright Test Integration
56
57
Utilities for creating Playwright test fixtures with Storybook story mounting capabilities.
58
59
```typescript { .api }
60
/**
61
* Creates Playwright test utilities with story mounting support
62
* @param baseTest - Base Playwright test fixture
63
* @returns Extended test fixture with mount capabilities
64
*/
65
function createPlaywrightTest<TFixture>(baseTest: TFixture): PlaywrightTestExtended<TFixture>;
66
67
interface PlaywrightTestExtended<TFixture> extends TFixture {
68
/**
69
* Mount a composed story in the test page
70
* @param story - Composed story function
71
* @param options - Mounting options
72
* @returns Promise resolving to mounted component locator
73
*/
74
mount(story: ComposedStoryFn, options?: MountOptions): Promise<Locator>;
75
76
/**
77
* Mount JSX/React element in the test page
78
* @param component - React element to mount
79
* @param options - Mounting options
80
* @returns Promise resolving to mounted component locator
81
*/
82
mountComponent(component: ReactElement, options?: MountOptions): Promise<Locator>;
83
}
84
85
interface MountOptions {
86
/** Additional props to pass to the story/component */
87
props?: Record<string, any>;
88
/** Container selector or element */
89
container?: string | Element;
90
/** Wait for specific conditions after mounting */
91
waitFor?: () => Promise<void>;
92
}
93
```
94
95
**Usage Examples:**
96
97
```typescript
98
import { test, expect } from '@playwright/test';
99
import { createPlaywrightTest } from '@storybook/preview-api';
100
import { composeStory } from '@storybook/preview-api';
101
import * as ButtonStories from './Button.stories';
102
103
// Create extended test with story mounting
104
const storyTest = createPlaywrightTest(test);
105
106
// Compose story
107
const Primary = composeStory(ButtonStories.Primary, ButtonStories.default);
108
109
storyTest('Primary button is interactive', async ({ mount, page }) => {
110
// Mount the story
111
const component = await mount(Primary, {
112
props: { label: 'Test Button' }
113
});
114
115
// Test interactions
116
await expect(component).toBeVisible();
117
await component.click();
118
119
// Verify behavior
120
await expect(component).toHaveClass('clicked');
121
});
122
123
storyTest('Button with custom args', async ({ mount }) => {
124
// Mount with custom args
125
const component = await mount(Primary, {
126
props: {
127
label: 'Custom Label',
128
disabled: true,
129
variant: 'secondary'
130
}
131
});
132
133
await expect(component).toBeDisabled();
134
await expect(component).toHaveText('Custom Label');
135
});
136
```
137
138
### CSF Factory Utilities
139
140
Functions for extracting annotations and metadata from CSF stories for testing.
141
142
```typescript { .api }
143
/**
144
* Gets factory annotations for CSF story testing and composition
145
* @param story - Story object or annotations
146
* @param meta - Component meta annotations (optional)
147
* @param projectAnnotations - Project-level annotations (optional)
148
* @returns Combined annotations for story factory
149
*/
150
function getCsfFactoryAnnotations<TRenderer>(
151
story: StoryAnnotations<TRenderer> | Story<TRenderer>,
152
meta?: ComponentAnnotations<TRenderer>,
153
projectAnnotations?: ProjectAnnotations<TRenderer>
154
): FactoryAnnotations<TRenderer>;
155
156
interface FactoryAnnotations<TRenderer> {
157
/** Combined story parameters */
158
parameters: Parameters;
159
/** Combined decorators */
160
decorators: DecoratorFunction<TRenderer>[];
161
/** Combined args */
162
args: Args;
163
/** Combined arg types */
164
argTypes: ArgTypes;
165
/** Render function */
166
render?: Function;
167
/** Play function */
168
play?: Function;
169
}
170
```
171
172
**Usage Examples:**
173
174
```typescript
175
import { getCsfFactoryAnnotations } from "@storybook/preview-api";
176
import * as ButtonStories from './Button.stories';
177
178
// Extract annotations for testing
179
const annotations = getCsfFactoryAnnotations(
180
ButtonStories.Primary,
181
ButtonStories.default,
182
globalConfig
183
);
184
185
// Use annotations in test setup
186
test('story has correct parameters', () => {
187
expect(annotations.parameters.docs?.description?.story).toBeDefined();
188
expect(annotations.args.primary).toBe(true);
189
});
190
191
// Create test utilities from annotations
192
const createTestStory = (overrides = {}) => {
193
return {
194
...annotations,
195
args: { ...annotations.args, ...overrides }
196
};
197
};
198
```
199
200
### Mock Utilities
201
202
Testing utilities for mocking Storybook's communication system.
203
204
```typescript { .api }
205
/**
206
* Creates a mock communication channel for testing addons and decorators
207
* @returns Mock channel with event emission and subscription
208
*/
209
function mockChannel(): MockChannel;
210
211
interface MockChannel {
212
/**
213
* Emit an event with data
214
* @param eventId - Event identifier
215
* @param args - Event data
216
*/
217
emit(eventId: string, ...args: any[]): void;
218
219
/**
220
* Subscribe to events
221
* @param eventId - Event identifier
222
* @param listener - Event handler function
223
*/
224
on(eventId: string, listener: Function): void;
225
226
/**
227
* Unsubscribe from events
228
* @param eventId - Event identifier
229
* @param listener - Event handler function to remove
230
*/
231
off(eventId: string, listener: Function): void;
232
233
/**
234
* Get all emitted events (for testing)
235
* @returns Array of emitted events
236
*/
237
getEmittedEvents(): EmittedEvent[];
238
239
/**
240
* Clear all emitted events history
241
*/
242
clearEmittedEvents(): void;
243
}
244
245
interface EmittedEvent {
246
eventId: string;
247
args: any[];
248
timestamp: number;
249
}
250
```
251
252
**Usage Examples:**
253
254
```typescript
255
import { mockChannel } from "@storybook/preview-api";
256
257
// Create mock channel for testing
258
const channel = mockChannel();
259
260
// Test addon communication
261
test('addon emits correct events', () => {
262
const addon = new MyAddon(channel);
263
264
addon.doSomething();
265
266
const events = channel.getEmittedEvents();
267
expect(events).toHaveLength(1);
268
expect(events[0].eventId).toBe('addon-action');
269
expect(events[0].args[0]).toEqual({ type: 'success' });
270
});
271
272
// Test event subscriptions
273
test('addon responds to events', () => {
274
const mockHandler = jest.fn();
275
const addon = new MyAddon(channel);
276
277
channel.on('external-event', mockHandler);
278
channel.emit('external-event', { data: 'test' });
279
280
expect(mockHandler).toHaveBeenCalledWith({ data: 'test' });
281
});
282
```
283
284
### Testing Utilities for Stories
285
286
Helper functions for testing story behavior and composition.
287
288
```typescript { .api }
289
/**
290
* Create test harness for story testing
291
* @param story - Story to test
292
* @param options - Test configuration options
293
* @returns Test harness with utilities
294
*/
295
function createStoryTestHarness<TRenderer>(
296
story: Story<TRenderer>,
297
options?: TestHarnessOptions
298
): StoryTestHarness<TRenderer>;
299
300
/**
301
* Creates mock channel implementation for testing (moved from deprecated addon API)
302
* @returns Mock channel with event tracking
303
*/
304
function mockChannel(): MockChannel;
305
306
interface TestHarnessOptions {
307
/** Mock implementations for dependencies */
308
mocks?: Record<string, any>;
309
/** Custom render container */
310
container?: HTMLElement;
311
/** Global test configuration */
312
globals?: Args;
313
}
314
315
interface StoryTestHarness<TRenderer> {
316
/**
317
* Render the story with given args
318
* @param args - Args to pass to story
319
* @returns Promise resolving to rendered result
320
*/
321
render(args?: Args): Promise<any>;
322
323
/**
324
* Update story args and re-render
325
* @param newArgs - Args to update
326
*/
327
updateArgs(newArgs: Args): Promise<void>;
328
329
/**
330
* Execute story play function
331
* @returns Promise resolving when play function completes
332
*/
333
executePlay(): Promise<void>;
334
335
/**
336
* Get current story context
337
* @returns Current story context
338
*/
339
getContext(): StoryContext<TRenderer>;
340
341
/**
342
* Clean up test harness
343
*/
344
cleanup(): void;
345
}
346
```
347
348
## Types & Interfaces
349
350
```typescript { .api }
351
interface ComposedStoryFn<TRenderer = any, TArgs = any> {
352
/** Execute story with optional args override */
353
(args?: Partial<TArgs>): any;
354
/** Story metadata */
355
storyName?: string;
356
args?: TArgs;
357
parameters?: Parameters;
358
argTypes?: ArgTypes;
359
id?: string;
360
}
361
362
interface PlayFunctionContext<TRenderer = any, TArgs = any> {
363
/** Story args */
364
args: TArgs;
365
/** Canvas element for interactions */
366
canvasElement: HTMLElement;
367
/** Step function for organizing test steps */
368
step: (label: string, play: (context: PlayFunctionContext<TRenderer, TArgs>) => Promise<void> | void) => Promise<void>;
369
/** Global values */
370
globals: Args;
371
/** Hooks context */
372
hooks: HooksContext<TRenderer>;
373
}
374
375
interface StoryAnnotations<TRenderer = any, TArgs = any> {
376
args?: Partial<TArgs>;
377
argTypes?: ArgTypes;
378
parameters?: Parameters;
379
decorators?: DecoratorFunction<TRenderer>[];
380
render?: (args: TArgs, context: StoryContext<TRenderer>) => any;
381
play?: (context: PlayFunctionContext<TRenderer, TArgs>) => Promise<void> | void;
382
}
383
384
interface ComponentAnnotations<TRenderer = any, TArgs = any> {
385
title?: string;
386
component?: any;
387
args?: Partial<TArgs>;
388
argTypes?: ArgTypes;
389
parameters?: Parameters;
390
decorators?: DecoratorFunction<TRenderer>[];
391
render?: (args: TArgs, context: StoryContext<TRenderer>) => any;
392
}
393
```