Skip to content

Keeping a screenshot current

In this tutorial you will publish a screenshot into a documentation page, change the application, and watch shotlist catch the screenshot going stale. You will finish with a command you can run in continuous integration that fails when your documentation shows something that is no longer true.

It continues in the project from Your first screenshot, with the order-row recipe working and its callouts section in place. Keep the application running at http://localhost:3000.

Step 1: choose where screenshots are published

So far every image has landed in screenshots/out/, which is a scratch folder. Ledger keeps the images its documentation uses in public/images/.

Before you continue, visit the Documentation page at http://localhost:3000/docs.html and see what it looks like.

Name public/images as a destination:

shotlist.config.yaml
site:
  url: http://localhost:3000
  serve: npm run dev

install:
  docs: public/images

docs is a name you invented; public/images is the folder it points at. A real project usually has two or three, one per place that shows screenshots.

Now point the recipe at it. Add one line under name:

screenshots/recipes/order-row.yaml
name: order-row
install: docs

Step 2: publish the screenshot

npx shotlist order-row --install

You should see two lines:

  ✓ order-row → screenshots/out/order-row.png
    installed public/images/order-row.png

Now open http://localhost:3000/docs.html. Where the page said no screenshot was there yet, your annotated order row is now sitting in the documentation, under the sentence it illustrates.

Published Ledger order row with amount and status callouts
The committed image is the baseline that --check compares against.

shotlist also wrote shotlist.baseline.json beside your configuration, recording the versions of shotlist, Playwright and Chromium that took the picture, and the operating system it ran on. In a real project you commit that alongside the images.

Step 3: check that the screenshot is still accurate

npx shotlist --check

This re-takes every screenshot and compares each against the published copy. Nothing has changed, so you should see:

  same     order-row

The command exits with status 0.

Step 4: change the application

This is the situation the tool exists for: somebody changes the product, and nobody remembers which screenshots showed the part they changed.

Open public/style.css and restyle the Open badge — the sort of change a designer makes without ever thinking about documentation:

public/style.css
.status-open {
  background: #fde68a;
  color: #92400e;
}

Reload the Orders screen to confirm the badge is now amber, then run the check again:

npx shotlist --check

This time it reports a difference and exits non-zero:

  CHANGED  order-row — 0.60% of pixels differ
           committed: public/images/order-row.png
           re-shot:   screenshots/out/order-row.png
1 of 1 need attention

Your percentage will be close to this rather than identical, because text rendering differs between machines. Treat it as a prompt to look rather than as a verdict: a small percentage can be a serious change, and a large one can be a rendering difference that means nothing.

Ledger order row with an amber Open badge
The re-shot image shows the application now; the committed image remains blue until you accept the change.

Why a smaller edit can report same

check.threshold is the fraction of pixels that must differ before a screenshot counts as changed, and it defaults to 0.002 — two pixels in every thousand. That is deliberate: without it, antialiasing noise would report a change on every run.

It also means small edits pass silently, and the effect is stronger than it looks: the image is larger than the region you clipped, because the canvas grew to hold the labels, so every change is a smaller fraction of it than you would guess from the row alone.

Restyling the badge moves about 0.6% of this screenshot — three times the threshold, which is why this step uses it. Changing a single character somewhere in the row moves roughly 0.05%, well under the limit, and is reported as same.

If your screenshots need to catch smaller changes than that, lower check.threshold in the configuration, or set it for one recipe under its own check key. See the recipe reference.

Step 5: look at what changed

npx shotlist --check --diff

For every screenshot that moved, shotlist writes an image into screenshots/out/diff/ with three panels side by side: the published version, the one it just took, and the new one again with every changed pixel tinted.

Open screenshots/out/diff/order-row.png. Only the badge is lit up, which tells you the change is confined to the thing you restyled: the row did not move, and the callouts still point where they did.

Three-panel diff highlighting the Open badge change
The diff places the committed image, the re-shot image, and the highlighted pixels from left to right. The pink patch isolates the badge restyle rather than a shifted row or callout.

Step 6: accept the change

You have looked at it and decided the new picture is the right one. Publishing it is step 2 again:

npx shotlist order-row --install

Reload the documentation page: the screenshot in it now shows the amber badge. Run npx shotlist --check once more and it reports same.

That is the whole working loop: change the application, run --check, look at the diff, re-run with --install when the new picture is correct.

Step 7: meet a screenshot that can never pass

Not every difference is a change worth knowing about. The panel carries a "Cash collected this week" chart drawn from live figures, so it is different every time the page loads. Add a second recipe capturing the whole panel, in screenshots/recipes/orders-panel.yaml:

screenshots/recipes/orders-panel.yaml
name: orders-panel
install: docs

clip: { css: '.panel', pad: 20 }

Publish it with npx shotlist orders-panel --install, then run npx shotlist --check twice in a row. The second run reports a change even though nobody touched anything:

  CHANGED  orders-panel — 1.60% of pixels differ

Your percentage will be a different number again, because the figures are different again. Left alone, this recipe reports a change forever, and a check that always fails is a check people stop reading.

The clock underneath the chart also changes on every capture, and it is worth knowing why it is not the problem here: a ticking digit moves a few hundred pixels of a two-million-pixel image, which is under the threshold from step 4. The chart moves tens of thousands, because its bars are solid fills. Size is what decides whether volatile content matters, not the fact that it is volatile.

shotlist gives you two ways out, and they answer different questions. Ask whether the region belongs in the picture at all:

  • check.ignore keeps it in the screenshot and excuses only its contents from the comparison.
  • mask paints over it, so it is not in the screenshot at all.

The chart is part of the product this documentation describes, so a reader should see it. Excuse it from the comparison rather than painting it out:

screenshots/recipes/orders-panel.yaml
name: orders-panel
install: docs

clip: { css: '.panel', pad: 20 }

check:
  ignore:
    - { within: clip, css: '#bars' }

Publish once more with --install, then run the check twice again. Both runs now report same, the chart is still in the published screenshot, and the rest of the panel is still being compared. The result says what it skipped — same orders-panel (1 region not compared) — so a pass is never read as covering the whole image.

Reach for mask when the region should not be in the picture in the first place: a customer's name, a face, a real account balance. It replaces the region with a flat fill, which is the right answer for a screenshot you are about to publish and the wrong one for a chart the documentation is about. Both are covered in Hide data that changes:

screenshots/recipes/orders-panel.yaml
mask:
  - { within: clip, css: '#bars' }

Step 8: produce a report a machine can read

npx shotlist --check --json > report.json

Everything meant for a person moves to standard error, so the file you just wrote contains only the report. Open report.json: it lists every recipe, its status, and how much of it changed.

What you have learned

  • named a publication folder and installed a screenshot into a documentation page
  • compared a live application against its published screenshots
  • read a percentage difference and a three-panel diff image
  • accepted a change by republishing
  • excused a region that differs on every capture, without removing it from the picture
  • produced a machine-readable report

Next steps