BackstopJS Part Deux: Javascript Config and Makefile
I’ve written previously about my setup for BackstopJS (which I’m still excited to say is the creator-recommended
Fun fact: The creator of BackstopJS has referenced this blog post as the recommended tutorial for BackstopJS v2!
Fun fact: The creator of BackstopJS has referenced this blog post as the recommended tutorial for BackstopJS v2!
I first came across BackstopJS when I started as a QA Engineer in March. My team had a Drupal 7 project that was suffering from pretty regular visual regressions when we deployed. The visual regressions on the project were consistent only in their frustration - sometimes a deploy to the dev environment would be fine, but staging and prod would have regressions; or dev would have regressions, but then staging wouldn’t. At the time, the site had about 60 individual pages and several integrations, and it was pretty tedious to check for regressions manually.
I needed to find a tool to help us avoid deploying code that would cause visual regressions. After doing some research, I decided to use BackstopJS, an npm package created for visual regression testing. It uses configuration files to target specific URLs and CSS selectors, and takes both reference and test screenshots to compare against each other. BackstopJS also runs a report that shows the screenshot comparison, and includes information like how much the comparison failed by.
BackstopJS sets up directories to house the reference screenshots, test screenshots, and regression reports. These folders are automatically re-generated each time the associated command is run (e.g. backstop test to begin generating the test screenshots), and the path for each is customizable - so you can choose the setup that best fits your project. A visual regression report is automatically generated, saved, and can be accessed later; which is great for sharing the results with our PMs and developers, as well as with our clients.
When I first set up BackstopJS, I created a config file for each of our four environments, so we could check each environment as our deploys progressed up the chain. I used a naming convention that would make sense across projects, e.g. local_backstop.json, dev_backstop.json, staging_backstop.json, and prod_backstop.json. Each environment has its own database and varying states of code deploys, but we tend to use prod as the “source of truth” for adherence to quality and features, so I used the prod site for the page and CSS selectors in my config files.
Because the history of this project was so fraught with regressions, I went through the site and included every URL in the BackstopJS configuration file. I was also determined to use a variety of selectors for each page to ensure coverage (something that’s especially annoying in Drupal). It ended up that each of the 60 URLs I was testing had about 8-10 CSS selectors; and I was testing on both desktop and mobile viewports. This means that I was using BackstopJS to create about 1200 reference screenshots, plus the same number of testing screenshots, on each of the four environments. It was one of those tasks where you start it running, then go eat lunch and play a few rounds of Towerfall (see http://xkcd.com/303/).
Having so many URLs and selectors wasn’t working as well as I wanted. I chose some selectors originally because they covered several sections within a page, but it turned out that BackstopJS would only hit the first instance of the selector, instead of using it multiple times.
Time was also an issue. It took FOREVER to run each test. BackstopJS 1.x version included a command to make sure the npm server didn’t time out; but if I forgot to run that command before I started a test, the server would die out by the time the report was ready to run, and I wouldn’t be able to view the report - making the entire test process a useless task. And when I did remember to keep the npm server open, I also had to remember to stop it after the report finished. If I didn’t stop and then restart the npm server for each test, the next config file would serve the report from the previous test, and I would again have wasted that time.
I took a step back, and thought about what BackstopJS reported on, and how it compared the reference and test screenshots. With a low mismatch threshold (I had mine set to 0.1%), BackstopJS will catch extremely nuanced differences, in everything from margins to image placement to font weights. And in spite of a somewhat cumbersome process, BackstopJS had already helped us catch and mitigate regressions before they made it to prod. It was obviously a good tool, but there was less of a need to be so finicky and precise about what it was checking.
I decided to scale down. I used a much smaller sampling of URLS in my config files - many pages used the same layout, so a regression showing in one or two of those should be enough to catch regressions in the similar pages. I also changed my viewports sizes for mobile and desktop. Instead of using specific WxH for each, I chose the width to match the device, and just gave each one a height value of 3000px. Now it didn’t matter what selectors I chose, or how far down on the page they were, since each screenshot would cover 3000px worth of content. So I also changed my selectors, and only used body and footer for each URL. I’ve gone from about 1200 screenshots per environment, down to about 160!
Now that I’ve got my configuration down to a repeatable and logical process, I’ll start setting up most of our projects with visual regression tests. We currently do support for about 15 Drupal projects, and BackstopJS is a great smoke test for our monthly security updates. For these support projects, I set up symlinks to the Drupal files directory from the BackstopJS test folder. This means that none of the screenshots get committed to the repo, so we avoid unnecessary repo bloat. The configuration files are committed to the repo as part of a tests/backstop directory, so any developer has access to them, and can run visual regression tests locally against their feature branch. The process for this would be:
backstop reference --configPath=local_backstop.json
backstop test --configPath=local_backstop.json
The practice is similar for running BackstopJS against deploys up to our various environments. If someone was deploying to production, the steps they'd need to follow would be:
backstop reference --configPath=prod_backstop.json
, to get the pre-deploy comparisons (The branch they’re on doesn’t matter now, since the config file is referencing the prod URL, not their local site)backstop test --configPath=prod_backstop.json
For our green field projects, the BackstopJS references will get updated at the end of every sprint, after we deploy the sprint’s work up to prod. For support projects, the references will get updated after our weekly deploy day. This ensures that I’ll always have a fresh set of references to test against as needed.
The npm package page has nicely detailed documentation on setting up and using BackstopJS, but I’ll run through the steps here as well on the assumption that it's always nice to have another tutorial. With the recent upgrade to 2.x, BackstopJS can be installed globally, and this is what I’d recommend. Some of these steps are customized for my personal workflow, so I’ll try to note where your process could differ. Below these steps, I’ve included a config file example as well.
node
and npm
installed on your computer, then run npm install -g backstopjs
tests/backstop
directory. NOTE: It’s my preference to keep all tests within a tests directory, so you don’t clutter up the project root with them.backstopjs
folder that you've just created, run backstop genConfig
. This creates the default backstop.json
config file, and the backstop_data
folder (which will eventually house the folders containing the screenshots and reports).backstop.json
to prod_backstop.json
. NOTE: This is to support a logical naming convention within the project, and so other developers can easily identify which config file to use for each environment. At this point, you’ve installed BackstopJS globally through npm, and generated and customized your first config file. With my personal workflow, the next step would be to make 3 copies of prod_backstop.json
and rename them for each environment (staging_backstop.json, dev_backstop.json, and local_backstop.json). If you're using SublimeText, 'command+D' is your friend for quickly modifying all of the environment-specific names in the new config files. Below is an example of a BackstopJS config file for a prod environment.
{ "id": "project_prod_config", "viewports": [ { // Really long height to let BackstopJS capture the full page without worrying about selectors "name": "desktop", "width": 1300, "height": 3000 }, { "name": "mobile", "width": 400, "height": 3000 } ], "scenarios": [ { // The label can be used as a filter to run incremental reference or test screenshots "label": "item0-projectProd-homepage", "url": "https://project.com/", "hideSelectors": [], "removeSelectors": [], "selectors": [ "#content" ], "delay": 500, // The low mismatch number means that BackstopJS will catch extremely small visual changes "misMatchThreshold" : 0.1 }, { "label": "item1-projectProd-suitesLanding", "url": "https://project.com/suites", "hideSelectors": [], "removeSelectors": [], "selectors": [ "#content" ], "delay": 500, "misMatchThreshold" : 0.1 }, ], "paths": { // When using symlinks, the paths for saving the images needed to point to the actual file path // Not to the symlinks // The point of reference is from this config file's location. "bitmaps_reference": "../../webroot/sites/default/files/backstopjs_tests/prod_reference", "bitmaps_test": "../../webroot/sites/default/files/backstopjs_tests/prod_test", "html_report": ../../"webroot/sites/default/files/backstopjs_tests/prod_html_report", "ci_report": "../../webroot/sites/default/files/backstopjs_tests/prod_ci_report" }, "casperFlags": [ // Without this flag, BackstopJS had trouble accessing the local site because of https notices // This tells BackstopJS to ignore it "--ignore-ssl-errors=true", "--ssl-protocol=any" ], "engine": "phantomjs", // With the 2.x upgrade, reporting in your CLI happens by default "report": ["browser"], "cliExitOnFail": false, // This can be changed to true for extremely verbose CLI reporting "debug": false }
I would definitely recommend BackstopJS for any developer or company looking to add to their QA toolkit, for a few reasons:
filter
flag to your commands, which means you can easily keep your regression tests up to date as you build out a new website or add content to an existing sitebackstop openReport
command. This is especially helpful for being able to show the visual regression results to a product owner or client.Visual regression testing has already benefited Metal Toad and our clients. The Drupal 7 project that was plagued with visual regressions? We’ve run into visual regressions a few more times - and with BackstopJS in place, we’ve caught it every single time. It’s also helped catch an issue on another project where the subnav menu was disappearing, when a seemingly unrelated ticket caused problems with the site’s menus. Without the regression tests, that bug would have been deployed all the way up to production. Recently, we inherited a new support project that needed security and module updates after going without them for about 6 months, and the regression tests caught missing content as a result of some of those updates. Whether used as a basic smoke test, or more detailed testing to prevent visual regressions, BackstopJS is a very useful tool, and has quickly become an important part of our commitment to code quality.
Have you used BackstopJS for visual regression tests? How has it helped your team? If there’s another framework that you’ve tried, what pros and cons have you discovered?
If you’re interested in hearing more about how I’ve set up or used visual regression testing as a QA Engineer, please feel free to reach out here in the comments!
I’ve written previously about my setup for BackstopJS (which I’m still excited to say is the creator-recommended
I'm a big fan of having an automated deployment process. It's really the web development analog to the "one step build process", as described in the...
The beauty of Drupal 8's built-in Migrate module is its flexibility. Lots of people will likely use it to migrate their Drupal 6 or 7 sites, but...
Be the first to know about new B2B SaaS Marketing insights to build or refine your marketing function with the tools and knowledge of today’s industry.