Dalius's blog

Thursday, March 7, 2024

Frontend testing: visual testing

So we have covered business logic part but now we want to handle UI. The next lowest hanging fruit is visual tests.

The idea is following:

  1. First you isolate your components. Normally you do this using Storybook. IMHO you can skip using Storybook in very simple projects, but overall I recommend start using it if you are not doing it yet.

  2. In your test cases (be it Storybook or other tool) you recreate each state with mock data. Actually doing this has many other benefits as well, but that’s different story.

  3. Capture snapshot of each test case and use a diffing tools in the future to check changes. The idea here is that sometimes changes are not intended (e.g. style change in one component breaks the whole layout) and sometimes they are intended, but do not look good (enough).

Now let’s move to implementation part. You can use services specifically written for that (e.g. Chromatic) or you can do it yourself. Services solves initial pain points, but might become expensive. My way to go is to use Playwright to take snapshots, because Playwright does one thing right: it take snapshots for different platforms in different place. This is critical as different platforms render fonts and other minor details slightly differently and in result snapshots become different.

Snapshots can be taken using Playwright directly (in playwright test), or you can use Storywright (or different Storybook + Playwright plugin) to take snapshots in storybook directly, or you can write tool to take snapshots of all stories in your storybook (recommended way). Extra step forward would be to parallelize this process (e.g. I saw people abusing GitHub actions for this).

Viewing diffs

If you use your own solution then you can simply use GitHub’s image diffing tool (with assumption that you are using GitHub).

In addition to the tools that are available to me I like to use image diffing in terminal (all you need is terminal that can show images). It takes a little bit of effort to setup but totally worth it.

First specify what are considered images in .gitattributes file:

*.png diff=image

Next in your git config configure make sure you have something like this:

[core]
    attributesfile = ~/.gitattributes

[alias]
    diff-image = "!f() { cd -- \"${GIT_PREFIX:-.}\"; GIT_DIFF_IMAGE_ENABLED=1 git --no-pager diff \"$@\"; }; f"

[diff "image"]
    command = ~/bin/image-diff.sh

Lastly create script image-diff.sh to diff images (you will need to install GraphicsMagick and img2sixel). Here is shortened version that can be improved:


#!/bin/sh

set - # "set -e" is default and if diff tools returns non 0 exit code then git produces "fatal: external diff died"

bn="$(basename "$1")"

if [[ -z "$GIT_DIFF_IMAGE_ENABLED" ]]; then
  echo "Diffing disabled for \"$bn\". Use 'git diff-image' to see image diffs."
else
  echo "Diffing $1"
  if [[ "$2" != "/dev/null" ]]; then
    diff="$(mktemp -t "$bn.XXXXXXX").png"
    gm compare -highlight-style assign -highlight-color red -file "$diff" "$1" "$2"
    img2sixel "$diff"
  else
    echo "New:"
    img2sixel "$1"
  fi
fi

Now if you have images that are different between runs you can simply run git diff-image to see the differences.

Different platforms problem

Now one more interesting problem is that different platforms renders things differently. In addition it is quite common nowadays for developers to use Macs (sometimes Windows), but CI usually runs Linux. The solution is to use Docker to run tests on Linux. E.g. something like this can be used:

DOCKER_HOST="unix://$HOME/.colima/docker.sock" docker run -it --rm --ipc=host -v "${PWD}:/var/app/" mcr.microsoft.com/playwright:v1.31.2-focal /bin/bash -c 'cd /var/app; yarn; yarn playwright test',