UNPKG

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