4 Secrets to Comfortably Working With Screenshots

Lynn Jepsen
Frontend Weekly
Published in
6 min readDec 5, 2018
Photo by Daria Nepriakhina on Unsplash

Are you comfortable using screenshots in your development workflow?

If you’re working in a large React code base, with lots of components…you probably want screenshots of individual components.

We all want screenshots to help us code review HTML and CSS.

This blog post gives you a lightweight way to integrate screenshots into your React development. I’ll give you my secret recipe for good screenshot workflow. And I’ll teach you the secret to managing images in a repository.

Screenshot Workflow

There are two secrets to good screenshot workflow

  • Automation
  • Screenshot individual components

Automate taking screenshots, and you are more likely to use screenshots on a day-to-day basis. Take screenshots of individual components, and your images will be easier to navigate.

Create a Web server, which renders individual components, by reading Debug a React Component Faster with routine-design. The rest of this blog post will explain how to automate taking screenshots of that Web server, which I call the RenderServer.

Screenshots work best when they are deterministic. Which is why I suggest not taking screenshots of components in the middle of an animation. My blog post about creating a RenderServer, builds on top of Create React App (CRA). But CRA has a constantly spinning logo. In order to capture a deterministic screenshot of the logo component, I suggest you remove the animation property from src/logo/index.scss.

Puppeteer is a headless version of Chrome. It automates capturing screenshots. Install it in your repository.

npm install --save-dev puppeteer

By combining routine-design with Puppeteer, you are more likely to look at screenshots of individual components on a day-to-day basis. Constant review will naturally increase the quality of your React code. It is hard to ignore visual bugs once you have captured them in an image.

Capture.js

Assuming you’ve just read Debug a React Component Faster with routine-design, then you’ve created a new test/render directory on top of CRA. From now on, I’m going to call this directory the “render directory”, because it is the directory that contains all the components we want to render.

Create a new capture.js file. This is where you will write code to automate taking screenshots.

const puppeteer = require(‘puppeteer’);const RoutineDesign = require(‘routine-design’);async function run(renderDirectory) {  //TODO, replace me with code}run(‘./test/render’);

Note that the run function is asynchronous. Inside the run function, create a RoutineDesignTree with your render directory. Then launch a new Puppeteer browser. Use the browser and WebPage to check if the server has finished compiling JavaScript/CSS. Once everything is ready, iterate over all the directories within the render directory. At the end of the run function, exit the process. This will turn off the server and close the browser.

const routineDesign = new RoutineDesign();const routineDesignTree = routineDesign.createRoutineDesignTree(renderDirectory);routineDesignTree.render();const browser = await puppeteer.launch();const webPage = routineDesign.createWebPage(browser, 8080, '/#/app');await webPage.waitForResolution(10);const directories = routineDesignTree.getComponentTree().getDirectories();for (let entry of directories) {  const componentDirectory = entry[1];  //TODO, replace me with code}process.exit(0);

Within the for-loop, iterate over each component file within the directory. Take a screenshot of each component, using routine-design’s LocalStorage.

const componentFiles = componentDirectory.getFiles();for (let i=0; i<componentFiles.length; i++) {  const componentFile = componentFiles[i];  const page = await browser.newPage();  await page.goto(‘http://localhost:8080/#/' + componentFile.getPath());  const localImage = routineDesign.getLocalStorage().createLocalImage()  await localImage.prepareForWriting();  await page.screenshot({path: localImage.getPath()});}

Now run

node ./capture.js

If you open routine-design-output/images you should see two images, one for src/logo/index.js and one for src/App.js. Congratulations! You’ve automated taking screenshots of individual components.

If you have any problems with routine-design, be sure to file bugs in the GitHub repository.

Images in Developer Environment

The secret to managing images in a repository is: do not save images in a repository. Git does not merge images well. Instead, store images on the cloud, and keep track of their information in a JSON file. Images hosted on the cloud are easy to look at, and JSON files are easy to merge.

Google Cloud is a simple way to store images on the cloud. The newest feature in routine-design — ImageStorage — uploads images to Google Cloud Storage. ImageStorage keeps track of image information by reading from and writing to a JSON file.

Sign into Google Cloud Platform using a Google account. Click the “Try Free” button and follow the instructions to create a new Project. Take note of the project ID. You’ll need it later.

Once you’ve created a project, Go to IAM & admin> Service accounts

Side navigation of Google Cloud Platform, showing IAM & admin

Create a service account. Name your service account by combining the word “screenshots” with the name of your repository. You do not need specify a role fro the service account. Create a JSON key.

The key is downloaded as a JSON file onto your computer. I recommend moving the JSON file out of your Downloads directory, and into a more permanent location. Now set your environment variable to point to that JSON file.

export GOOGLE_APPLICATION_CREDENTIALS="/home/user/auth.json”

Once you’ve created a service account, navigate to Google Cloud Storage.

Side navigation of Google Cloud Platform, showing Storage

Click the button for “Create Bucket”. Name your new bucket the same as the service account, by combining the word “screenshots” with the name of your repository.

Now click on the Permissions tab. Click “Add members” and add “allUsers” as the “Storage Object Viewer”

Click “Add members” and add “allUsers” as the “Storage Object Viewer”

Now anyone can see the images hosted on the cloud.

But not everyone can upload images to the cloud, only authorized users. So click “Add members” again and add the service account as the “Storage Object Creator”

That is all you need to connect your developer environment to Google Cloud. Unfortunately, you won’t know if you did it correctly until you go through the next section, which will actually upload images to Google Cloud.

Store Images in the Cloud

Update your capture.js file within the componentDirectories for-loop to initialize an ImageStorage variable with the Google Cloud project ID and storage bucket name. Each componentFile in the directory automatically matches a single componentImage object. At the end of the for-loop, you’ll need to ImageStorage.save, which writes the information to an image.json file within the componentDirectory.

const componentFiles = componentDirectory.getFiles();

const gcpImageBucket = routineDesign.createGcpImageBucket('project-id', 'my-app-screentshots');
const routineDesignDirectory = gcpImageBucket.createRoutineDesignDirectory(componentDirectory);const imageStorage = routineDesignDirectory.getImageStorage();const componentImages = await imageStorage.getImages();for (let i=0; i<componentFiles.length; i++) { const componentFile = componentFiles[i]; const componentImage = componentImages[i]; ...}await imageStorage.save();

Update the internal componentFiles for-loop to save the localImage to the componentImage. You no longer need to keep the localImage, so call LocalImage.delete to clean it up. Every time you save a new localImage, you update the image.json file in the componentDirectory. If the screenshot doesn’t change, there is no need to update the image.json file. The following logic will only save a localImage if the screenshot has changed.

const localImagePng = await localImage.getPng();const imageId = componentImage.getId();if (imageId) {  const goldenPng = await componentImage.createGcpImage().download();if (!Buffer.compare(localImagePng.data, goldenPng.data) === 0) {    await componentImage.saveImage(localImagePng);  }} else {  await componentImage.saveImage(localImagePng);}await localImage.delete();

Run node ./capture.js to run this new code. If you open test/render/logo/image.json you should an image URL. Open this URL, and you should see a screenshot of the Logo component. Congratulations! You’ve really automated taking screenshots of individual components. The images are stored on Google Cloud Platform, and image.json tracks the changes.

Remember, if you have any problems with routine-design, be sure to file bugs in the GitHub repository.

--

--

Lynn Jepsen
Frontend Weekly

Software Engineer. MIT alum. Expert at JavaScript, CSS and HTML. On a mission to align code and design.