MIT · Node 20+ · driven by Playwright
Annotated UI screenshots, described as data.
shotlist opens your running site, drives it to the state you describe, clips a region, draws the callouts on it, and writes the image where you want it. Each screenshot is a YAML file. There is no per-screenshot code.
npm i -D shotlist playwright # The shot at the top of this page, described in full.
name: recipe-anatomy
install: site
setup:
- click: { role: link, name: Docs }
- wait: { css: '[data-recipe]' }
clip:
css: '[data-recipe]'
pad: 24
marks:
drive: { within: clip, text: 'setup:' }
region: { within: clip, text: 'clip:' }
draw: { within: clip, text: 'callouts:' }
callouts:
- { mark: drive, text: Drive the page, place: left }
- { mark: region, text: Clip a region, place: left }
- { mark: draw, text: Draw on top, place: right } What a recipe contains
Three sections, read top to bottom: drive the page to a state, clip a region of it, draw on what the clip contains.
-
setup:Drive the page to a state
Twenty verbs — click, fill, hover, select, wait, and the rest — run against your site until the thing you want to photograph is on screen. Steps shared between shots go in a macro.
-
clip:Clip a region of it
The whole viewport, the whole page, or whatever an element query finds: the smallest box holding a name and an amount, a modal card climbed out of its overlay.
-
callouts:Draw on top
Say which side of a mark a label belongs on. shotlist works out the position, grows the canvas when a label needs room outside the shot, and moves one that would collide with another.
A recipe is data
A recipe is a YAML file, and nothing in it runs. There is no JavaScript step, and no plugin hook that turns into one.
Twenty verbs drive the page. A query language says what to point at — a row in a table, a modal, the amount in a cell.
A recipe can be read without knowing the codebase, and fixed by whoever notices it broke.
name: order-row
install: guide
clip: { css: '.order-row', contains: Acme Corp, pad: 20 }
marks:
amount: { within: clip, text: $42.00 }
callouts:
- { mark: amount, text: What they owe, place: left } Find an element by CSS, role, label or text, then narrow it down until one thing matches.
Checking screenshots against the site
shotlist --check re-shoots every recipe, compares each against the committed image, and exits non-zero if any of them moved. In CI that makes a screenshot which no longer matches the site a failing build.
Nothing else reports it. When an interface changes, the committed image goes on rendering exactly as before; it is only no longer true.
The parts a recipe does not decide — a clock, a live figure — are named rather than tolerated, so the check is worth reading. One running on a different machine from the one that took the images says so before it says anything else.
$ npx shotlist --check
same order-row
same modal-detail
CHANGED billing-plan — 3.41% of pixels differ
committed: content/guide/images/billing-plan.png
1 of 3 need attention
The rest of what it does
None of it is needed for a first screenshot.
-
site.serveStart the site it shoots
Names the command, waits until it answers, stops it afterwards — and uses a server you already have running rather than fighting it for the port.
-
maskPaint over a region
A clock, a live total, a face. The rest of the shot stays under drift detection instead of the whole image opting out.
-
check.ignoreCheck a shot whose subject changes
Leaves a region out of the comparison but shoots it as it is — and still reports the box moving, resizing or rendering nothing.
-
--diffWrite a diff image
A committed, re-shot and changed-pixels three-up per drifted shot. --json reports the run for something other than a person.
-
use:Share setup, and drive a shot from a list
A macro for the steps every recipe repeats, a data file for the rows to walk. One recipe, one screenshot per order.
-
source: fileAnnotate an image you already have
For a screen no script can reach — behind a sign-in you cannot automate, or an extension. Same callouts, same install.
-
--untrustedRun a config you did not write
A shot list only opens its own site and never reads a .env, in every mode. In CI, one flag takes away the rest.
-
SKILL.mdEditor and agent support
JSON Schemas generated from the same definitions that validate a run, and a skill that teaches an agent to write a query worth keeping.
Getting started
Playwright is an optional peer dependency — shotlist does not install it, because its postinstall downloads browsers and a project consuming shotlist should not pay for that on every install.
npx shotlist --init writes the config and a first recipe. Edit them, add one file per screenshot, and shoot the set with npx shotlist --all --install.
site:
url: http://localhost:3000
viewport: { width: 1440, height: 900 }
scale: 2
install:
guide: content/guide/images