|
1 | 1 | # Cypress Storybook Commands |
2 | 2 |
|
3 | | -This package registers a set of cypress commands that will allow you to test components in storybook visually and functionally. |
| 3 | +## Workspace |
4 | 4 |
|
5 | | -Now supports projects using cypress 10 and Storybook 6. |
| 5 | +This repo contains a cypress and storybook sandbox for the development of the `cypress-storybook-commands` plugin. |
6 | 6 |
|
7 | | -## Installation |
| 7 | +For plugin usage [view the package documentation](packages/cypress-storybook-commands/README.md) |
8 | 8 |
|
9 | | -``` |
10 | | -yarn add cypress-storybook-commands --dev |
11 | | -``` |
| 9 | +For contributing to this plugin continue reading below. |
12 | 10 |
|
13 | | -or |
| 11 | +## Modifying the plugin |
14 | 12 |
|
15 | | -``` |
16 | | -npm i cypress-storybook-commands --dev |
17 | | -``` |
18 | | - |
19 | | -### Setup |
20 | | - |
21 | | -You must include the `cypress-image-snapshot` plugin in `cypress/plugins/index.js` (create the folder and file if not there) |
22 | | - |
23 | | -```js |
24 | | -const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin') |
25 | | - |
26 | | -module.exports = (on, config) => { |
27 | | - addMatchImageSnapshotPlugin(on, config) |
28 | | -} |
29 | | -``` |
30 | | - |
31 | | -and ensure that your root `cypress.config.js` is configured to load the plugins |
32 | | - |
33 | | -```js |
34 | | -const { defineConfig } = require('cypress'); |
35 | | - |
36 | | -module.exports = defineConfig({ |
37 | | - e2e: { |
38 | | - setupNodeEvents(on, config) { |
39 | | - return require('./cypress/plugins/index.js')(on, config); |
40 | | - }, |
41 | | - }, |
42 | | -}); |
43 | | -``` |
44 | | - |
45 | | -#### Add the commands |
46 | | - |
47 | | -In your `cypress/support/commands.js` add the following: |
48 | | - |
49 | | -```js |
50 | | -import addStoryBookCommands from 'cypress-storybook-commands'; |
51 | | - |
52 | | -addStoryBookCommands({ |
53 | | - version: 6, // currently compatible with version 5 and 6 of storybook, and native |
54 | | - viewportPresets: { // see https://docs.cypress.io/api/commands/viewport.html#Arguments |
55 | | - mobile: 'iphone-x', |
56 | | - tablet: 'ipad-2', |
57 | | - laptop: 'macbook-13', |
58 | | - desktop: [1920, 1080], |
59 | | - }, |
60 | | - registerSnapshotCommands: true, // false if you already include cypress-image-snapshot/command |
61 | | - preSnapshotFunc: () => { console.log('code before snapshot: hide elements, etc' )}, |
62 | | - postSnapshotFunc: () => { console.log('code after snapshot: reset elements, etc' )}, |
63 | | - snapshotSelector: '#root' // selector of element to use for snapshots, |
64 | | -}) |
65 | | - |
66 | | -``` |
67 | | - |
68 | | -#### Add the run scripts |
69 | | - |
70 | | -In your `cypress/package.json` add a new script for storybook tests: |
71 | | - |
72 | | -``` |
73 | | - "scripts": { |
74 | | - ... |
75 | | - "cy:storybook": "cypress open --e2e --config baseUrl=http://localhost:9002/,specPattern='cypress/storybook/*'", |
76 | | - "cy:storybook:headless": "cypress run --e2e --browser chrome --headless --config baseUrl=http://localhost:9002/,specPattern='cypress/storybook/*'" |
77 | | - } |
78 | | -``` |
79 | | - |
80 | | -Note we will put storybook tests in their own folder so they do not run with the rest of your `e2e` tests, and we specify the baseUrl of where your storybook can be found. |
81 | | - |
82 | | - |
83 | | -## Usage |
84 | | - |
85 | | -### Visual regression tests |
86 | | - |
87 | | -In your test file e.g. `cypress/storybook/visual-regression.cy.js` you can simply: |
88 | | - |
89 | | -```js |
90 | | -context('Storybook', () => { |
91 | | - it('render components as expected', () => { |
92 | | - cy.visit('/') |
93 | | - .runStorybookVisualRegression({storiesToSkip: []}); |
94 | | - }); |
95 | | -}); |
96 | | -``` |
97 | | - |
98 | | -this will iterate over all the stories and capture visual snapshots. |
99 | | - |
100 | | -Note: `runStorybookVisualRegression` has optional settings `storiesToSkip`, `storyWaits` and `storyActions`. You can use full story ids or story prefixes e.g. `accordion--primary` or `accordion--` |
101 | | - |
102 | | -```js |
103 | | -cy.visit('/') |
104 | | - .runStorybookVisualRegression({ |
105 | | - // array of story ids or prefixes (found in url of story or the data-item-id in the nav) |
106 | | - storiesToSkip: ['button-disabled'], |
107 | | - // map of story matches to wait times in ms before screenshot (e.g. avoid capturing snapshots mid animation, etc)); |
108 | | - storyWaits: {'chart--': 1000} |
109 | | - // map of story matches to actions to execute before screenshot |
110 | | - storyActions: { |
111 | | - 'core-dialog': () => cy.contains('Open dialog').click(), |
112 | | - 'core-modal': () => cy.contains('Open Modal').click(), |
113 | | - }, |
114 | | - }); |
115 | | -``` |
116 | | - |
117 | | -### Functional tests |
118 | | - |
119 | | -In your test file e.g. `cypress/storybook/accordion.cy.js` you can load the story to carry out functional testing as follows: |
120 | | - |
121 | | -```js |
122 | | -context('Storybook: Accordion', () => { |
123 | | - beforeEach(() => { |
124 | | - cy.visit('/') |
125 | | - .loadStory('Accordion.default') |
126 | | - }); |
127 | | - |
128 | | - it('toggles content as expected', () => { |
129 | | - cy.get('div[data-testid="tab"]').click() |
130 | | - ... etc ... |
131 | | - }); |
132 | | -}); |
133 | | -``` |
134 | | - |
135 | | -Note: `Accordion.default` is the testID set in the navigator for that story via the `data-testid` attribute. |
136 | | - |
137 | | -### Running |
138 | | - |
139 | | -Ensure storybook is available on a url (should match baseUrl in the above added scripts). Then run `yarn run cy:storybook` or `yarn run cy:storybook:headless` as preferred. |
140 | | - |
141 | | -On CI if you build your storybook you can use `npx serve` to serve the distribution files generated by storybook, alternatively just run the storybook app in the background. You will then want to use `npx start-server-and-test` to wait for storybook to be ready before running the cypress headless command. |
142 | | - |
143 | | -``` |
144 | | -APPCMD="yarn storybook" |
145 | | -CYPRESSCMD="yarn cy:storybook:headless --env requireSnapshots=true" |
146 | | -npx start-server-and-test "$APPCMD" 9002 "$CYPRESSCMD" |
147 | | -``` |
148 | | - |
149 | | -Note the use of `--env requireSnapshots=true` so that any missing snapshots in the branch cause a CI fail. |
150 | | - |
151 | | -#### Parallelism |
152 | | - |
153 | | -If you are using storybook 6+ we can make use of the `/stories.json` endpoint to get and split all the stories in the storybook instance. |
154 | | - |
155 | | -This means you can pass a comma separated list of stories to include in the parallel instance (injected via the environment): |
156 | | - |
157 | | -```js |
158 | | -context('Storybook', () => { |
159 | | - it('render components as expected', () => { |
160 | | - cy.visit('/') |
161 | | - .runStorybookVisualRegression({ |
162 | | - storyList: Cypress.env('storyList') || null, |
163 | | - storiesToSkip: [] |
164 | | - }); |
165 | | - }); |
166 | | -}); |
167 | | -``` |
168 | | - |
169 | | -Your CI to run different set of stories in parallel could look something like: |
170 | | - |
171 | | -```yml |
172 | | - visual-regression-storybook: |
173 | | - docker: |
174 | | - - image: cypress/included:10.7.0 |
175 | | - parallelism: 3 |
176 | | - steps: |
177 | | - - attach_workspace: |
178 | | - at: . |
179 | | - - run: |
180 | | - name: Run Cypress on Storybook |
181 | | - command: | |
182 | | - FULLSTORYLIST=$(node -e "console.log(Object.keys(require('./devtools/storybook/dist/stories.json').stories).join('\n'))") |
183 | | - TESTS=$(echo $FULLSTORYLIST | circleci tests split) |
184 | | - APPCMD="npx serve@13 --listen 9002 ./devtools/storybook/dist" |
185 | | - CYPRESSCMD="CYPRESS_storyList='${TESTS// /,}' yarn cy:storybook:headless --env requireSnapshots=true" |
186 | | - npx start-server-and-test "$APPCMD" 9002 "$CYPRESSCMD" |
187 | | - - store_artifacts: |
188 | | - path: devtools/cypress/cypress/snapshots |
189 | | - destination: snapshots |
190 | | - - store_artifacts: |
191 | | - path: stories.json |
192 | | - destination: stories.json |
193 | | - |
194 | | -``` |
195 | | - |
196 | | -#### Advance usage |
197 | | - |
198 | | -Composition and splitting of stories in cy tests). You can get and filter stories as follows: |
199 | | - |
200 | | -```js |
201 | | - const storiesWithLoopingAnimations = ['loading', 'zerostate']; |
202 | | - |
203 | | - const storyList = (stories, filter) => stories.filter(filter).join(','); |
204 | | - |
205 | | - it('renders forms as expected', () => { |
206 | | - cy.getStories().then((stories) => { |
207 | | - cy.runStorybookVisualRegression({ |
208 | | - storyList: storyList(stories, (storyId) => storyId.includes('forms-')), |
209 | | - storiesToSkip: [...storiesWithLoopingAnimations, 'forms-somestory-to-skip'], |
210 | | - }); |
211 | | - }); |
212 | | - }); |
213 | | -``` |
214 | | - |
215 | | - |
216 | | -## Minimising Cross OS visual differences |
217 | | - |
218 | | -If you are running the snapshots locally on a different OS to what you run on CI, you can minimise visual differences with the following in `cypress/plugins/index.js` |
219 | | - |
220 | | -```js |
221 | | -const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin') |
222 | | - |
223 | | -module.exports = (on, config) => { |
224 | | - addMatchImageSnapshotPlugin(on, config); |
225 | | - |
226 | | - on('before:browser:launch', (browser = {}, launchOptions) => { |
227 | | - // https://github.com/cypress-io/cypress/issues/5240 |
228 | | - const args = Array.isArray(launchOptions) ? launchOptions : launchOptions.args; |
229 | | - |
230 | | - if (browser.name === 'chrome' || browser.name === 'chromium') { |
231 | | - // In headless mode, Cypress fixes the scale factor to 1, and this forces |
232 | | - // screenshots to be taken with an image size matching the viewport size |
233 | | - // instead of the viewport size multiplied by the scale factor. |
234 | | - // |
235 | | - // Since we also want to run the image regression tests in development mode, |
236 | | - // we need to set the device scale factor to 1 in chrome / chromium. |
237 | | - // |
238 | | - // See: https://github.com/cypress-io/cypress/issues/2102#issuecomment-521299946 |
239 | | - // See: https://github.com/cypress-io/cypress/blame/a7dfda986531f9176468de4156e3f1215869c342/packages/server/lib/cypress.coffee#L132-L137 |
240 | | - args.push('--force-device-scale-factor=1'); |
241 | | - |
242 | | - // Force the colour profile - should reduce colour differences in diffs between MacOS and Linux (CI) |
243 | | - args.push('--force-color-profile=srgb'); |
244 | | - |
245 | | - // Force font rendering - should reduce differences in diffs between MacOS and Linux (CI) |
246 | | - args.push('--font-render-hinting=none'); |
247 | | - } |
248 | | - |
249 | | - return launchOptions; |
250 | | - }); |
251 | | -} |
252 | | -``` |
| 13 | +Code is found in `packages/cypress-storybook-commands` and can be tested via the `sandbox/cypress` instance. A storybook instance is also provided in `sandbox/storybook` but you can run against any instance by modifying the `baseUrl` in the cypress setup. |
0 commit comments