Skip to content

Recipes

One YAML file per screenshot, in paths.recipes. The filename is the recipe's name unless name: says otherwise.

Field Default What it does
namefilenameOutput filename, without the extension
sourceappapp drives the site; file annotates an image you have
fileThe image to annotate, with source: file
installWhich named destination to copy to
urlsite.urlPage to open for this recipe
viewportsite'sViewport for this recipe
scalesite'sDevice pixel ratio for this recipe
themesite'sColour scheme for this recipe
styleStyle overrides for this recipe
formatproject'spng, jpeg or webp for this shot
qualityproject'sFor the two that are lossy
setup[]Steps that drive the site into the state to capture
clipviewportThe region to capture: viewport, full, or a query
marks{}Named regions, resolved after setup runs
mask[]Regions painted over before the callouts are drawn
callouts[]What to draw on those marks
numberedMarks to number 1…n with a disc, in the order given
retries0How many times to shoot this again if it fails, up to 5
checkproject'sThis recipe's --check limits and ignore, or false

What gets captured

clip: viewport is the browser window at site.viewport. clip: full is the whole document, however far it scrolls. Anything else is a query, and the shot is the box of the element it finds — with pad or grow if it needs room around it.

A clip is held to the width of the viewport, because nothing widens a page the way scrolling lengthens it. Its height is not held to anything: a region reaching past the fold is captured by scrolling to it, so a long form or a full table comes out whole rather than cut at the bottom of the window.

Marks

A mark is a name you choose for a region inside the shot. Callouts point at marks by name, and so do numbered and check.ignore; one mark can carry as many callouts as you like.

screenshots/recipes/order-row.yaml
clip: { css: '.order-row', contains: Acme Corp }

marks:
  amount: { within: clip, text: $42.00 }
  status: { within: clip, text: Open }

callouts:
  - { mark: amount, text: What they owe }
  - { mark: status, text: Where it stands }

Marks resolve after setup has run, in the order they are written. That order is usable: within: clip narrows a query to the region being captured, and within: <mark> narrows it to one already resolved above.

A mark that matches nothing stops the run, naming the recipe and the mark — recipe "order-row": marks.amount — no element matched. That is the assertion a recipe makes: the shot is of what it says it is of, or there is no shot.

Annotating an existing image

To annotate an image that is already on disk, set source: file and give file its path. Callouts, style and install work the same way. It is the way to shoot a screen no script can reach — anything behind a sign-in you cannot automate, or a browser extension's UI. The file may be a PNG, a JPEG or a WebP; what it is read as comes from its bytes rather than its name.

There is no page to query, so marks are literal rectangles measured off the image: [x, y, width, height] in image pixels.

screenshots/recipes/billing-settings.yaml
name: billing-settings
source: file
file: captures/billing-settings.png
install: guide

marks:
  plan: { rect: [866, 874, 150, 50] }

callouts:
  - { mark: plan, text: The current plan, place: top }

When a shot fails

A run stops at the first recipe that fails, and says which recipe and which key in it could not be resolved. --keep-going shoots the rest instead, prints each failure as it happens, and names them all at the end.

 modal screenshots/out/modal.png
 recipe "order-row": marks.amount no element matched {"css":".amount"}
1 of 2 failed: order-row

Either way the exit code is non-zero, so CI still fails. --keep-going works the same way with --check, where a recipe that could not be shot is reported FAILED alongside the ones that diffed.

retries shoots a recipe again when it fails, up to five times. It is for the failures that are gone a second later — an element that had not rendered, a request that had not landed — which a capture has because it drives a real application.

screenshots/recipes/order-row.yaml
name: order-row
retries: 2 # three attempts in all

Each attempt is a fresh browser context, and a failure is reported while the run is still going rather than after it. A source: file recipe never retries whatever this says — it has no page to be flaky about, so a second attempt could only reach the same answer.