UNPKG

13.8 kBMarkdownView Raw
1# StoryShots + [Puppeteer](https://github.com/GoogleChrome/puppeteer)
2
3## Getting Started
4
5Add the following modules into your app.
6
7```sh
8npm install @storybook/addon-storyshots-puppeteer puppeteer --save-dev
9```
10
11⚠️ As of Storybook 5.3 `puppeteer` is no longer included in the addon dependencies and must be added to your project directly.
12
13## Configure Storyshots for Puppeteer tests
14
15⚠️ **React-native** is **not supported** by this test function.
16
17When running Puppeteer tests for your stories, you have two options:
18
19- Have a storybook running (ie. accessible via http(s), for instance using `npm run storybook`)
20- Have a static build of the storybook (for instance, using `npm run build-storybook`)
21
22Then you will need to reference the storybook URL (`file://...` if local, `http(s)://...` if served)
23
24## _puppeteerTest_
25
26Allows to define arbitrary Puppeteer tests as `story.parameters.puppeteerTest` function.
27
28You can either create a new Storyshots instance or edit the one you previously used:
29
30```js
31import initStoryshots from '@storybook/addon-storyshots';
32import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
33
34initStoryshots({ suite: 'Puppeteer storyshots', test: puppeteerTest() });
35```
36
37Then, in your stories:
38
39```js
40export const myExample = () => {
41 ...
42};
43myExample.parameters = {
44 async puppeteerTest(page) {
45 const element = await page.$('<some-selector>');
46 await element.click();
47 expect(something).toBe(something);
48 },
49};
50```
51
52This will assume you have a storybook running on at _<http://localhost:6006>_.
53Internally here are the steps:
54
55- Launches a Chrome headless using [puppeteer](https://github.com/GoogleChrome/puppeteer)
56- Browses each stories (calling _<http://localhost:6006/iframe.html?...>_ URL),
57- Runs the `parameters.puppeteerTest` function if it's defined.
58
59### Specifying the storybook URL
60
61If you want to set specific storybook URL, you can specify via the `storybookUrl` parameter, see below:
62
63```js
64import initStoryshots from '@storybook/addon-storyshots';
65import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
66
67initStoryshots({
68 suite: 'Puppeteer storyshots',
69 test: puppeteerTest({ storybookUrl: 'http://my-specific-domain.com:9010' }),
70});
71```
72
73The above config will use _<https://my-specific-domain.com:9010>_ for tests. You can also use query parameters in your URL (e.g. for setting a different background for your storyshots, if you use `@storybook/addon-backgrounds`).
74
75You may also use a local static build of storybook if you do not want to run the webpack dev-server:
76
77```js
78import initStoryshots from '@storybook/addon-storyshots';
79import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
80
81initStoryshots({
82 suite: 'Puppeteer storyshots',
83 test: puppeteerTest({
84 storybookUrl: 'file:///path/to/my/storybook-static',
85 // storybookUrl: 'file://${path.resolve(__dirname, '../storybook-static')}'
86 }),
87});
88```
89
90### Specifying options to _goto()_ (Puppeteer API)
91
92You might use `getGotoOptions` to specify options when the storybook is navigating to a story (using the `goto` method). Will be passed to [Puppeteer .goto() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options)
93
94```js
95import initStoryshots from '@storybook/addon-storyshots';
96import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
97
98const getGotoOptions = ({ context, url }) => {
99 return {
100 waitUntil: 'networkidle0',
101 };
102};
103
104initStoryshots({
105 suite: 'Puppeteer storyshots',
106 test: puppeteerTest({ storybookUrl: 'http://localhost:6006', getGotoOptions }),
107});
108```
109
110### Specifying custom Chrome executable path (Puppeteer API)
111
112You might use `chromeExecutablePath` to specify the path to a different version of Chrome, without downloading Chromium. Will be passed to [Runs a bundled version of Chromium](https://github.com/GoogleChrome/puppeteer#default-runtime-settings)
113
114```js
115import initStoryshots from '@storybook/addon-storyshots';
116import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
117
118const chromeExecutablePath = '/usr/local/bin/chrome';
119
120initStoryshots({
121 suite: 'Puppeteer storyshots',
122 test: puppeteerTest({ storybookUrl: 'http://localhost:6006', chromeExecutablePath }),
123});
124```
125
126Alternatively, you may set the `SB_CHROMIUM_PATH` environment variable. If both are set, then `chromeExecutablePath` will take precedence.
127
128### Specifying a custom Puppeteer `browser` instance
129
130You might use the async `getCustomBrowser` function to obtain a custom instance of a Puppeteer `browser` object. This will prevent `storyshots-puppeteer` from creating its own `browser`. It will create and close pages within the `browser`, and it is your responsibility to manage the lifecycle of the `browser` itself.
131
132```js
133import initStoryshots from '@storybook/addon-storyshots';
134import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
135import puppeteer from 'puppeteer';
136
137(async function () {
138 initStoryshots({
139 suite: 'Puppeteer storyshots',
140 test: puppeteerTest({
141 storybookUrl: 'http://localhost:6006',
142 getCustomBrowser: () => puppeteer.connect({ browserWSEndpoint: 'ws://yourUrl' }),
143 }),
144 });
145})();
146```
147
148### Customizing a `page` instance
149
150Sometimes, there is a need to customize a page before it calls the `goto` api.
151
152An example of device emulation:
153
154```js
155import initStoryshots from '@storybook/addon-storyshots';
156import { puppeteerTest } from '@storybook/addon-storyshots-puppeteer';
157const devices = require('puppeteer/DeviceDescriptors');
158
159const iPhone = devices['iPhone 6'];
160
161function customizePage(page) {
162 return page.emulate(iPhone);
163}
164
165initStoryshots({
166 suite: 'Puppeteer storyshots',
167 test: puppeteerTest({
168 storybookUrl: 'http://localhost:6006',
169 customizePage,
170 }),
171});
172```
173
174### Specifying setup and tests timeout
175
176By default, `@storybook/addon-storyshots-puppeteer` uses 15 second timeouts for browser setup and test functions.
177Those can be customized with `setupTimeout` and `testTimeout` parameters.
178
179### Integrate Puppeteer storyshots with regular app
180
181You may want to use another Jest project to run your Puppeteer storyshots as they require more resources: Chrome and Storybook built/served.
182You can find a working example of this in the [official-storybook](https://github.com/storybookjs/storybook/tree/main/examples/official-storybook) example.
183
184### Integrate Puppeteer storyshots with [Create React App](https://github.com/facebookincubator/create-react-app)
185
186You have two options here, you can either:
187
188- Add the storyshots configuration inside any of your `test.js` file. You must ensure you have either a running storybook or a static build available.
189
190- Create a custom test file using Jest outside of the CRA scope:
191
192 A more robust approach would be to separate existing test files ran by create-react-app (anything `(test|spec).js` suffixed files) from the test files to run Puppeteer storyshots.
193 This use case can be achieved by using a custom name for the test file, ie something like `puppeteer-storyshots.runner.js`. This file will contain the `initStoryshots` call with Puppeteer storyshots configuration.
194 Then you will create a separate script entry in your package.json, for instance
195
196 ```json
197 {
198 "scripts": {
199 "puppeteer-storyshots": "jest puppeteer-storyshots.runner.js --config path/to/custom/jest.config.json"
200 }
201 }
202 ```
203
204 Note that you will certainly need a custom config file for Jest as you run it outside of the CRA scope and thus you do not have the built-in config.
205
206 Once that's setup, you can run `npm run puppeteer-storyshots`.
207
208### Reminder
209
210Puppeteer launches a web browser (Chrome) internally.
211
212The browser opens a page (either using the static build of storybook or a running instance of Storybook)
213
214If you run your test without either the static build or a running instance, this wont work.
215
216To make sure your tests run against the latest changes of your Storybook, you must keep your static build or running Storybook up-to-date.
217This can be achieved by adding a step before running the test ie: `npm run build-storybook && npm run image-snapshots`.
218If you run the Puppeteer storyshots against a running Storybook in dev mode, you don't have to worry about the stories being up-to-date because the dev-server is watching changes and rebuilds automatically.
219
220## _axeTest_
221
222Runs [Axe](https://www.deque.com/axe/) accessibility checks and verifies that they pass using [jest-puppeteer-axe](https://github.com/WordPress/gutenberg/tree/master/packages/jest-puppeteer-axe).
223
224```js
225import initStoryshots from '@storybook/addon-storyshots';
226import { axeTest } from '@storybook/addon-storyshots-puppeteer';
227
228initStoryshots({ suite: 'A11y checks', test: axeTest() });
229```
230
231For configuration, it uses the same `story.parameters.a11y` parameter as [`@storybook/addon-a11y`](https://github.com/storybookjs/storybook/tree/next/addons/a11y#parameters)
232
233### Specifying options to `axeTest`
234
235```js
236import initStoryshots from '@storybook/addon-storyshots';
237import { axeTest } from '@storybook/addon-storyshots-puppeteer';
238
239const beforeAxeTest = (page, { context: { kind, story }, url }) => {
240 return new Promise((resolve) =>
241 setTimeout(() => {
242 resolve();
243 }, 600)
244 );
245};
246
247initStoryshots({ suite: 'A11y checks', test: axeTest({ beforeAxeTest }) });
248```
249
250`beforeAxeTest` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeAxeTest` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the axe test .
251
252## _imageSnapshots_
253
254Generates and compares screenshots of your stories using [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot).
255
256```js
257import initStoryshots from '@storybook/addon-storyshots';
258import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer';
259
260initStoryshots({ suite: 'Image storyshots', test: imageSnapshot() });
261```
262
263It saves all images under \_\_image_snapshots\_\_ folder.
264
265### Specifying options to _jest-image-snapshots_
266
267If you wish to customize [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot), then you can provide a `getMatchOptions` parameter that should return the options config object. Additionally, you can provide `beforeScreenshot` which is called before the screenshot is captured and a `afterScreenshot` handler which is called after the screenshot and receives the just created image.
268
269```js
270import initStoryshots from '@storybook/addon-storyshots';
271import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer';
272const getMatchOptions = ({ context: { kind, story }, url }) => {
273 return {
274 failureThreshold: 0.2,
275 failureThresholdType: 'percent',
276 };
277};
278const beforeScreenshot = (page, { context: { kind, story }, url }) => {
279 return new Promise((resolve) =>
280 setTimeout(() => {
281 resolve();
282 }, 600)
283 );
284};
285const afterScreenshot = ({ image, context }) => {
286 return new Promise((resolve) =>
287 setTimeout(() => {
288 resolve();
289 }, 600)
290 );
291};
292initStoryshots({
293 suite: 'Image storyshots',
294 test: imageSnapshot({
295 storybookUrl: 'http://localhost:6006',
296 getMatchOptions,
297 beforeScreenshot,
298 afterScreenshot,
299 }),
300});
301```
302
303`getMatchOptions` receives an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot.
304
305`beforeScreenshot` receives the [Puppeteer page instance](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#class-page) and an object: `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot. `beforeScreenshot` is part of the promise chain and is called after the browser navigation is completed but before the screenshot is taken. It allows for triggering events on the page elements and delaying the screenshot and can be used avoid regressions due to mounting animations.
306
307`afterScreenshot` receives the created image from puppeteer.
308
309### Specifying options to _screenshot()_ (Puppeteer API)
310
311You might use `getScreenshotOptions` to specify options for screenshot. Will be passed to [Puppeteer .screenshot() fn](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagescreenshotoptions)
312
313```js
314import initStoryshots from '@storybook/addon-storyshots';
315import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer';
316const getScreenshotOptions = ({ context, url }) => {
317 return {
318 encoding: 'base64', // encoding: 'base64' is a property required by puppeteer
319 fullPage: false, // Do not take the full page screenshot. Default is 'true' in Storyshots.,
320 };
321};
322initStoryshots({
323 suite: 'Image storyshots',
324 test: imageSnapshot({ storybookUrl: 'http://localhost:6006', getScreenshotOptions }),
325});
326```
327
328`getScreenshotOptions` receives an object `{ context: {kind, story}, url}`. _kind_ is the kind of the story and the _story_ its name. _url_ is the URL the browser will use to screenshot.
329
330To create a screenshot of just a single element (with its children), rather than the page or current viewport, an ElementHandle can be returned from `beforeScreenshot`:
331
332```js
333import initStoryshots from '@storybook/addon-storyshots';
334import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer';
335
336const beforeScreenshot = (page) => page.$('#root > *');
337
338initStoryshots({
339 suite: 'Image storyshots',
340 test: imageSnapshot({ storybookUrl: 'http://localhost:6006', beforeScreenshot }),
341});
342```