Skip to main content

How to keep product images in documentation up to date

· 10 min read
Thomas Rooney

Demo

Docusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed image

How to keep product images in documentation up to date

Reflow is a tool for end-to-end testing of web products. It auto-heals browser interactions when your product changes, which means it can also be used creatively in long-living process automation tasks.

In this tutorial, we will show you how to use reflow to automatically capture screenshots of your web products, such that you can regenerate them automatically as your product changes.

We'll also make a Themed Image: an adaptive image that renders differently depending on the user's browser and dark mode preferences. The end result will be a single command to produce 6 sets of product images, one for each of the following scenarios, and a react component that will dynamically import the right one based on the user's viewport and browser.

ViewportDark modeLight mode
DesktopScenario 1Scenario 4
MobileScenario 2Scenario 5
TabletScenario 3Scenario 6

Why Do This?

Documentation with relevant and contextual product images is far better than documentation without images.

Unfortunately, there are a few problems with adding images into documentation:

  1. Your product is going to change. When it changes, you either need to update all your documentation images or allow for drift. When there's enough drift, your documentation may appear completely incorrect.
  2. Your users do not all have the same visual experience on your product. The simplest example of this is users that use mobile or tablet browsers, compared to those who use desktop browsers. If you theme both your product and your documentation, this difference can be more striking as the images in your documentation will often be in a different theme to the user preference.

The added benefit of generating images using this process is it also works as a small test suite that you can gradually improve upon to eventually cover your product's full functionality.

How long will this take?

Around 30 minutes.

Step 1 - Install and Run Reflow

Reflow is installed through npm and can be run from the command line. For this tutorial, we'll use Local Reflow, and start by running the following on the command line:

npm install -g reflowio
reflowio dashboard

Step 2 - Create an account / login

When we access reflow via the web interface dashboard, we need to authenticate to synchronize our local configuration with any cloud configuration.

Docusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed image

Step 3 - Create a new test

Click the "Create Test" button in the top right corner of the dashboard.

Docusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed image

Step 4 - Test Configuration

We're going to be using Desktop Chrome for our initial dashboard sequence, though we'll be able to regenerate these screenshots with a different device after recording our initial run.

For our starting URL, for this example we're using the reflowio dashboard running on localhost:3100. However, you should use any URL you like.

We'll use the "Test Name" attribute later, so make it something unique and relevant to the documentation images. In our case, we're going to use "Reflow Documentation Flow".

Docusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed imageDocusaurus themed image

Step 5 - Configure Dark Mode

Our application is configured to be in either dark mode, or light mode, based on the user preferences. We want our documentation to show images with the same theme as the user preference, so we'll add a step to conditionally enable dark mode dependent on a variable passed in.

Click "Add Action to start", and select "Execute Browser JavaScript", then click Edit to bring up the step editor. The Test Editor uses Monaco.

Once here, we see two functions exported: a handler function, and a dependsOn array.

We'll set dependsOn to ['darkMode'] and trigger a test execution. We see our log of the event parameter in the devtools pane, and can now fill in the darkMode variable.

We'll set it to false for the recording run, but by defining it we can trigger runs with different darkMode parameters. We'll also fill in the handler logic to conditionally set the darkMode flag. In our application, we can do this via setting it in localStorage and triggering a JavaScript storage event.

Use "Test Execute" to validate it works, and click "Add" to confirm it and add it to our test actions.

Step 5 - Take our first screenshot

For our first screenshot, we're going to use the entire page body, as we want to show what it's like for a user on first login.

We start by opening the custom action dialog in the Test pane, and selecting "Assert" type, then subtype "Visually Matches". Then, in the right-hand developer tools pane, we'll select the Elements subpanel and navigate to the body element. We should see the image appearing in the left hand pane once we've selected this.

We change the slider to "Raise Warning" as we're not expecting this to always remain the same on each run -- we want the flow to continue executing even when the screenshot differs.

Click "Add" to complete the action.

Step 6 - Add an action description to set the downloaded image name

Optionally, we can give actions a description. In most scenarios, this is unnecessary, but in this case it will help to simplify our image output filename.

For instance, by defining our image as "login", the images output we'll look for later will be login.element.actual.png. If we didn't name it, the image output would be 1.element.actual.png, named after the fact that this is the first step in our test.

To give the action a description, open it, then select the "Pen" icon. Click "Save" to confirm.

Step 7 - Save the test

We're going to save the test now, and start bringing the screenshots into our documentation. However, if you'd like, you can continue to add more product images into the test. Just continue interacting with your site until all the images you want to capture are captured. "Save" the test with the bottom-right button in the test pane, and confirm. You will be taken back to the test dashboard.

Click "Play" to capture the initial execution of the test.

Step 8 - Syncronize the screenshots into your codebase.

In this step, we're going to use the CLI runner to extract the most recent run from reflow into our codebase.

Navigate to a folder where you want the images stored, and run the following command. This will pull our images from the most recent run of this test into the directory "./images".

If you lack an API key, you will be prompted to login to reflow after executing this command.

reflowio pull --image --test-name "Reflow Documentation Flow" --action-description "login" --output-directory "./images"

Step 9 - Full Automation

The last step is full automation. We're going to use the CLI runner to run the test, and then extract the images from the most recent run for all our scenarios.

There's nothing clever here, we'll just run a series of commands to fully automate this.

Note: this final step struggles when you lack an API key to authenticate with. When you use the Reflow CLI in the free version, you will need to manually login via the Web UI to authenticate. After you set up an API key, this can be fully automated.

reflowio test "Reflow Documentation Flow"  --device-emulation-profile "iPad Pro 11 (landscape)" --params "darkMode=false"
reflowio test "Reflow Documentation Flow" --device-emulation-profile "iPad Pro 11 (landscape)" --params "darkMode=true"
reflowio test "Reflow Documentation Flow" --device-emulation-profile "Chrome Desktop" --params "darkMode=false"
reflowio test "Reflow Documentation Flow" --device-emulation-profile "Chrome Desktop" --params "darkMode=true"
reflowio test "Reflow Documentation Flow" --device-emulation-profile "iPhone 13" --params "darkMode=false"
reflowio test "Reflow Documentation Flow" --device-emulation-profile "iPhone 13" --params "darkMode=true"
reflowio pull images "Reflow Documentation Flow" --device-emulation-profile "iPad Pro 11 (landscape)" --params "darkMode=false" --out-dir "./tablet"
reflowio pull images "Reflow Documentation Flow" --device-emulation-profile "iPad Pro 11 (landscape)" --params "darkMode=true" --out-dir "./tablet-dark"
reflowio pull images "Reflow Documentation Flow" --device-emulation-profile "Chrome Desktop" --params "darkMode=false" --out-dir "./desktop"
reflowio pull images "Reflow Documentation Flow" --device-emulation-profile "Chrome Desktop" --params "darkMode=true" --out-dir "./desktop-dark"
reflowio pull images "Reflow Documentation Flow" --device-emulation-profile "iPhone 13" --params "darkMode=false" --out-dir "./mobile"
reflowio pull images "Reflow Documentation Flow" --device-emulation-profile "iPhone 13" --params "darkMode=true" --out-dir "./mobile-dark"

Step 10 - Import the images into your documentation depending on user theme

The last step is to dynamically import the images into your documentation, depending on the user theme and browser.

At reflow, we use Docusaurus / mdx files for our documentation. With a little swizzle, our images look like this:

import ThemedImage from '@theme/ThemedImage';

<ThemedImage
alt="Docusaurus themed image"
sources={{
lightdesktop: useBaseUrl('/img/desktop/login.signals.visualComparisonSignal.actual.image.png'),
lightmobile: useBaseUrl('/img/mobile/login.signals.visualComparisonSignal.actual.image.png'),
lighttablet: useBaseUrl('/img/tablet/login.signals.visualComparisonSignal.actual.image.png'),
darkdesktop: useBaseUrl('/img/desktop-dark/login.signals.visualComparisonSignal.actual.image.png'),
darkmobile: useBaseUrl('/img/mobile-dark/login.signals.visualComparisonSignal.actual.image.png'),
darktablet: useBaseUrl('/img/tablet-dark/login.signals.visualComparisonSignal.actual.image.png'),
}}
/>;

With the swizzle overriding ThemedImage with our new options:

/**
* Copyright (c) Facebook, Inc. and its affiliates.
* Modifications Copyright (c) Resilient Software Ltd
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import clsx from 'clsx';
import useIsBrowser from '@docusaurus/useIsBrowser';
import { useColorMode } from '@docusaurus/theme-common';
import useMediaQuery from '@mui/material/useMediaQuery';
import styles from './styles.module.css';

export default function ThemedImage(props) {
const isBrowser = useIsBrowser();
const { isDarkTheme } = useColorMode();
const mobile = useMediaQuery('(max-width: 480px)');
const tablet = useMediaQuery('(min-width: 481px) and (max-width: 1025px)');
const desktop = useMediaQuery('(min-width: 1026px)');

const { sources, className, alt = '', ...propsRest } = props;

const isSplit = Boolean(sources['darkmobile']);
let clientThemes = ['dark'];
if (isSplit) {
if (mobile && isDarkTheme) {
clientThemes = ['darkmobile'];
} else if (mobile && !isDarkTheme) {
clientThemes = ['lightmobile'];
} else if (tablet && isDarkTheme) {
clientThemes = ['darktablet'];
} else if (tablet && !isDarkTheme) {
clientThemes = ['lighttablet'];
} else if (desktop && isDarkTheme) {
clientThemes = ['darkdesktop'];
} else if (desktop && !isDarkTheme) {
clientThemes = ['lightdesktop'];
}
} else {
clientThemes = isDarkTheme ? ['dark'] : ['light'];
}
const renderedSourceNames = isBrowser
? clientThemes // We need to render both images on the server to avoid flash
: // See https://github.com/facebook/docusaurus/pull/3730
Boolean(isSplit)
? ['lightmobile', 'lighttablet', 'lightdesktop', 'darkmobile', 'darktablet', 'darkdesktop']
: ['light', 'dark'];
return (
<>
{renderedSourceNames.map((sourceName) => (
<img
key={sourceName}
src={sources[sourceName]}
alt={alt}
className={clsx(styles.themedImage, styles[`themedImage--${sourceName}`], className)}
{...propsRest}
/>
))}
</>
);
}

And a media query so this works with SSR:

/**
* Copyright (c) Facebook, Inc. and its affiliates.
* Modifications Copyright (c) Resilient Software Ltd
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

.themedImage {
display: none;
}

/* Media Query for Mobile Devices */
@media (max-width: 480px) {
[data-theme='light'] .themedImage--lightmobile {
display: initial;
}

[data-theme='dark'] .themedImage--darkmobile {
display: initial;
}
}

/* Media Query for Tablets */
@media (min-width: 481px) and (max-width: 1025px){
[data-theme='light'] .themedImage--lighttablet {
display: initial;
}

[data-theme='dark'] .themedImage--darktablet {
display: initial;
}
}

/* Media Query for Desktops */
@media (min-width: 1026px) {
[data-theme='light'] .themedImage--lightdesktop {
display: initial;
}

[data-theme='dark'] .themedImage--darkdesktop {
display: initial;
}
}

[data-theme='light'] .themedImage--light {
display: initial;
}

[data-theme='dark'] .themedImage--dark {
display: initial;
}
This site uses cookies to enhance your user experience and conduct analytics to understand how our services are used. By continuing to use our site, you consent to the placement and use of cookies as described in our Privacy Policy.