This is a walkthrough of how to set up Visual Regression Testing with Jest for an application created with create-react-app
.
The following walkthrough uses React as an example, but the approach should work for any modern frontend library! I assume it can be used with Angular, Vue, Cycle.js and more.
This gist walks you through a create-react-app
application as an example of how to set up Visual Regression Testing in Jest using libraries I wrote recently which enable this: jsdom-screenshot
, jest-transform-css
and jest-transform-file
.
I will write a more detailed article soon. This gist is for the curious.
You can also check out this repo which is what you'll end up with in case you get confused along the way.
Create a new create-react-app project called "vrt-cra" (short for visual-regression-testing-create-react-app), or with whatever name you prefer.
npx create-react-app vrt-cra
Open project
cd vrt-cra
Eject project, as we need a more sophisticated setup.
You will be asked whether you really want to eject, confirm it by typing y and pressing Enter.
yarn eject
You can start the application to see what it looks like by running
yarn start
.
You can also run the tests once to ensure they work properly with
yarn test App.test.js
. You have to quit them withq
after they ran as they start in watch mode automatically.
Now we need to add some libraries to enable Visual Regression Testing: jsdom-screenshot
, jest-transform-css
and jest-transform-file
:
yarn add jest-image-snapshot jsdom-screenshot jest-transform-file jest-transform-css
Edit package.json
to add the Visual Regression Testing libraries to Jest.
// package.json
"jest": {
// ... more stuff ...
// add this entry
"setupTestFrameworkScriptFile": "./src/setupTests.js",
// this entry already exists, change it from "node" to "jsdom"
"testEnvironment": "jsdom",
// this entry already exists, change it
"transform": {
"^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "jest-transform-css",
"^.+\\.svg$": "jest-transform-file",
"^(?!.*\\.(js|jsx|mjs|css|json|svg)$)": "<rootDir>/config/jest/fileTransform.js"
},
// ... more stuff ...
}
Notice that we added "svg" to the list of files not handled by fileTransform.js
in the last line, as we're now transforming SVGs through jest-transform-file
instead.
Extend the expect
with the toMatchImageSnapshot
function so that we can compare images.
// src/setupTests.js
import { toMatchImageSnapshot } from "jest-image-snapshot";
expect.extend({ toMatchImageSnapshot });
Adapt the App.test.js
test.
// src/App.test.js
import React from "react";
import ReactDOM from "react-dom";
import { generateImage } from "jsdom-screenshot";
import App from "./App";
it("has no visual regressions", async () => {
// render App into jsdom
const div = document.createElement("div");
document.body.appendChild(div);
ReactDOM.render(<App />, div);
// prevent spinner from rotating to ensure consistent screenshots
document
.getElementsByClassName("App-logo")[0]
.setAttribute("style", "animation: none");
// Take screenshot with generateImage()
const screenshot = await generateImage();
// and compare it to the previous sceenshot with toMatchImageSnapshot()
expect(screenshot).toMatchImageSnapshot();
// clean up for next test
ReactDOM.unmountComponentAtNode(div);
document.body.removeChild(div);
});
🎉 This is already the end of the setup for basic Visual Regression Testing!
We can now rerun the tests with yarn test App.test.js
.
Notice that you must restart your tests completely (exit the "watch" mode) as we changed the configuration quite a bit.
You will see a screenshot was saved to src/__image_snapshots__
. The next time you run your tests, another screenshot will get taken and compared with the one already existing there. If they match, your tests succeed as there were no visual regressions. When they differ, an image showing the differences will be created and your tests will fail. In case the changes were on purpose, the saved image can be updated by pressing u in the tests, as may already know from Jest's snapshotting feature (not to confuse with screenshots).
This is great as it gives you confidence that your layout did not change. You don't need to make any tests for classnames anymore, just take a screenshot instead.
Make some changes to App.js
and see how it affects the tests.
A sidenote on performance: Taking a screenshot (the generateImage
function) takes one or two seconds, depeding on your system and the size of the sreenshot. So use the feature wisely.
The setup is quite cumbersome for each test as we're manually mounting the div
, rendering the application and cleaning up. We can let react-testing-library
handle that. If you're not using React, you can use dom-testing-library
instead.
Add react-testing-library
and jest-dom
.
yarn add react-testing-library jest-dom
Enable jest-dom
helpers and clean up react-testing-library
automatically.
// src/setupTests.js
// add some helpful assertions
import "jest-dom/extend-expect";
// clean up after each test
import "react-testing-library/cleanup-after-each";
// This was here before as we added it earlier. We still need it.
import { toMatchImageSnapshot } from "jest-image-snapshot";
expect.extend({ toMatchImageSnapshot });
And now, we can clean up our test in App.test.js
:
// src/App.test.js
import React from "react";
import { generateImage } from "jsdom-screenshot";
import { render } from "react-testing-library";
import App from "./App";
it("has no visual regressions", async () => {
// render App into jsdom
render(<App />);
// prevent spinner from rotating to ensure consistent screenshots
document
.getElementsByClassName("App-logo")[0]
.setAttribute("style", "animation: none");
// Take screenshot with generateImage()
const screenshot = await generateImage();
// and compare it to the previous sceenshot with toMatchImageSnapshot()
expect(screenshot).toMatchImageSnapshot();
});
It is possible to interact with any component before taking the screenshot. The screenshot will contain whatever the jsdom used in tests contains at that moment. See react-testing-library
for more information about that.
This example showed how to use jest-transform-css
with global CSS. The library can also handle CSS modules and Styled Components. See jest-transform-css
for setup instructions.
In case your components make requests when mounted, you can use Request Interception to respond to requests from the tests. See jsdom-screenshot
for more information.
A tiny webserver gets started by passing generateImage({ serve: ['public'] })
which can serve local assets for the screenshots. See jsdom-screenshot
for more information.
It is possible to print the markup that the screenshot gets taken of by passing generateImage({ debug: true })
. See jsdom-screenshot
for more information.
This setup has shown how to do Visual Regression Testing in Jest by the example of a create-react-app
application. We were able to load the component's styles and the SVG file (or any other images). The walkthrough hinted at how we can use
A more advanced setup can be found at visual-regression-testing-example.
This setup is highly experimental. I tried it with a few different configurations and it worked fine so far, but I'm sure there is more that needs to be fixed. Handle it with care, it is early stage!
Feel free to ask any questions on Twitter: @dferber90!
Hi @dferber90,
I'm just looking for exactly such a guide, only for Vue.js. You wouldn't happen to know of one? Or have you heard of similar, corresponding toolings?