Configuration
One shotlist.config.yaml in the project root. Every path in it resolves from its own directory, not from wherever the command was run.
| Key | Default | What it sets |
|---|---|---|
site.url | required | Where the site is running |
site.serve | — | Command that starts the site, when nothing answers at site.url |
site.allow | [] | Hosts a shot may open besides this site's own and everything under it |
site.viewport | 1280 × 800 | Browser size |
site.scale | 2 | Device pixel ratio; 2 is Retina |
site.theme | light | light, dark or no-preference |
site.reducedMotion | true | Disables animation before capture |
site.ready | — | Selector waited for after each navigation |
site.settle | 0 | Extra milliseconds to wait after ready |
site.timeout | 15000 | How long a step or a query may take |
paths.recipes | screenshots/recipes | Where recipes live |
paths.macros | screenshots/macros | Where macros live |
paths.data | screenshots/data | Where data files live |
paths.out | screenshots/out | Where images are written |
image.format | png | png, jpeg or webp |
image.quality | 90 | 1–100, for the two that are lossy |
install | {} | Named destinations a recipe refers to by name |
deny | [] | File or folder names this project will not have read, written or opened |
finders | {} | Named query aliases |
check.threshold | 0.002 | Fraction of pixels that may differ before a shot counts as changed |
check.tolerance | 8 | How far one channel may move, out of 255, before a pixel differs |
Starting the site
To have a run start the site itself, give site.serve the command that starts it. Without it, shotlist expects something to already be answering at site.url.
site:
url: http://localhost:3000
serve: npm run dev A server already up is used as it is. shotlist fetches site.url first and starts nothing when something answers, so this can stay in the config while you write recipes with the dev server open in another terminal. CI, where nothing is listening, is where it actually launches one.
What a run starts, it stops — including on Ctrl-C. The command runs in its own process group, because npm run dev is npm, which spawns the server: signalling only the process shotlist launched would leave the one holding the port.
site:
serve:
command: npm run dev
ready: 3000 # a http(s) URL, a port, or { log: <pattern> }
cwd: apps/web # resolved from the config file's directory
env: { PORT: '3000' }
timeout: 30000 ready is what proves the server is up, and defaults to fetching site.url. A URL is ready on any response at all, including a 404: that something answered is the question, not what it said. A port is ready when it accepts a connection, and { log: … } when the pattern matches the server's output.
There is no shell. The command is run directly, so &&, |, > and a VAR=value prefix are refused rather than half-understood — environment goes under env:, and anything needing a shell goes in a script you run instead. A run of only source: file recipes never opens the site, and does not start one.
Image format
png keeps every pixel and is the default. webp is the one worth reaching for: a shot of this site's homepage is 87 KB as a PNG and 33 KB as a WebP. jpeg is lossy in the way that shows worst on what a screenshot of an interface is mostly made of, which is text.
image:
format: webp
quality: 90 A recipe may set its own format and quality. quality runs from 1 to 100 and does nothing to a PNG, which has no detail to trade away. The file is named for the format — .png, .webp, and .jpg for jpeg — and --check compares against a committed file of the same name.
There is no AVIF. The only encoder here is the browser doing the drawing, and Chromium reads AVIF but will not write it — it answers a request for one with a PNG rather than an error, so every conversion checks what it got back.
Style
Every drawing constant is configurable, and a recipe may override any of them under its own style: key. Sizes are in image pixels, so they do not change when scale does. These are the defaults.
style:
color: '#DC2626' # boxes, arrows, discs
canvas: '#FFFFFF' # fills space added around the image for labels
box: { width: 6, radius: 10, pad: 8 }
arrow: { shaft: 6, headHalf: 19, headLength: 38 }
label:
font: 'Arial, Helvetica, sans-serif'
weight: 700
size: 44
fill: '#FFFFFF' # glyph fill
stroke: '#DC2626' # outline around the glyphs; defaults to `color`
strokeWidth: 6 # centred on the outline, so half is under the fill
gap: 40 # distance from the box
fontUrl: # optional stylesheet to load before drawing
number:
radius: 26
size: 40
fill: '#DC2626' # the disc; defaults to `color`
text: '#FFFFFF' # the numeral
mask:
fill: '#94A3B8' # what a masked region is painted with Fonts
Set the label typeface under style.label.font. It has to be resolvable by the browser doing the drawing, which by default means installed on the machine, and a family that is not installed falls back silently — every label renders in another typeface with nothing to say so. shotlist measures the rendered text to catch that and warns. It is a warning rather than an error: a fallback still produces an image, and which faces a machine has is not something a recipe can know.
To use a font that is not installed, give fontUrl a stylesheet. A http(s) or data: URL is fetched when the callouts are drawn, so that one needs network access at shoot time.
Anything else is a path, resolved from the config file's directory the way every other path is — for a font the project ships. It is read from disk and inlined, along with the font files it points at, because the page the callouts are drawn in is built in memory, and a browser gives such a page no file: subresource. An absolute file: URL works too.
style:
label:
fontUrl: https://fonts.googleapis.com/css2?family=Inter:wght@700&display=swap
font: 'Inter, Arial, sans-serif' style:
label:
fontUrl: fonts/inter.css # read from disk, fonts and all
font: 'Inter' strokeWidth is centred on the glyph outline, so half of it is painted under the fill. A 6-pixel stroke reads as a 3-pixel outline; tools that draw the stroke entirely outside need roughly double the number here to match.