# Your first screenshot

In this tutorial you will install shotlist into a small application and produce an annotated screenshot of part of it. You will finish with a PNG showing one row of an orders list, with two labelled arrows pointing at the parts that matter.

Everything runs on your own machine. You do not need an application of your own, and you will not write any JavaScript — everything you write here is YAML. The tutorial takes about fifteen minutes.

## Before you begin

You need Node.js 20 or later, npm, and a terminal.

## Step 1: get the example application

Download [shotlist-example.zip](/shotlist-example.zip) and unzip it wherever you keep projects. Inside is a small fake invoicing screen called Ledger, with a documentation page alongside it. It has no dependencies of its own.

Start it:

```
cd shotlist-example
npm run dev
```

Open [http://localhost:3000](http://localhost:3000). You should see a list of orders, one row per invoice, each with a customer name, a reference, an amount and a status. This is the application you are going to photograph.

If it reports that port 3000 is already in use, stop whatever is holding it, or run `PORT=3001 npm run dev` instead and use that port everywhere this tutorial says 3000 — including in `site.url` in step 3.

Leave it running, and open a second terminal in the same folder for everything that follows.

## Step 2: install shotlist

```
npm i -D shotlist playwright
```

```
pnpm add -D shotlist playwright
```

```
yarn add -D shotlist playwright
```

This takes a minute or two, because Playwright downloads a copy of Chromium. shotlist uses that browser to open the page and take the picture. For why the browser is a separate install, see [Why Playwright is a peer dependency](/docs/explanation/playwright/).

## Step 3: point shotlist at the application

Create a file called `shotlist.config.yaml` in the root of the example project, beside `package.json`:

shotlist.config.yaml

```
site:
  url: http://localhost:3000
  serve: npm run dev
```

- `site.url` is where the application is running. It is the only key shotlist requires, and it also decides which addresses your recipes are allowed to open.
- `site.serve` is the command that starts the application. shotlist requests `site.url` first and uses the server you already started, so this line is what matters on a machine where nothing is running yet.

## Step 4: write a recipe and take a picture

A recipe is one YAML file describing one screenshot. Create the folder `screenshots/recipes/` and, inside it, a file called `order-row.yaml`:

screenshots/recipes/order-row.yaml

```
name: order-row
clip: viewport
```

Take it:

```
npx shotlist order-row
```

You should see:

```
  ✓ order-row → screenshots/out/order-row.png
```

Open `screenshots/out/order-row.png`. It is the whole browser window, which is rarely what documentation wants. Next you will narrow it to a single row.

## Step 5: capture one region

Replace the recipe with this, and run the same command again:

screenshots/recipes/order-row.yaml

```
name: order-row

clip:
  css: 'li, div'
  contains: Acme Corp
  matching: '\$\d'
  maxChildren: 12
  pick: smallest
  pad: 20
```

The image is now a single row: Acme Corp, INV-1042, $42.00, Open. The query narrowed down to it in stages:

- `css` collects every list item and every div on the page.
- `contains` keeps the ones holding the text "Acme Corp".
- `matching` keeps the ones whose text also holds a currency figure.
- `maxChildren` discards containers with a great many children.
- `pick: smallest` chooses between what is left. Both the row and the panel around it match everything above, and the row is the smaller of the two.
- `pad` adds 20 pixels of room on every side.

Notice what the query does not mention: no class names, no positions in the document. Rename `order-row` in the stylesheet and this recipe still works.

## Step 6: point at something

Now add two callouts: a labelled arrow to the amount, and another to the status.

screenshots/recipes/order-row.yaml

```
name: order-row

clip:
  css: 'li, div'
  contains: Acme Corp
  matching: '\$\d'
  maxChildren: 12
  pick: smallest
  pad: 20

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

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

Two new sections appeared:

- `marks` gives a name to each region you care about. `within: clip` limits the search to the region being captured, which matters here: more than one row on the page says "Open", and only one of them is in your screenshot.
- `callouts` is what gets drawn. Each entry names a mark, the label text, and which side of the region the label sits on.

Run the command again and open the image. Both regions are outlined in red, with an arrow running from a red label to each outline. shotlist made the canvas taller than the captured row so the labels have somewhere to sit. Your result should match this image:

![Ledger order row annotated at its amount and status](/images/order-row.png)

The completed Ledger screenshot. “What they owe” points to $42.00; “Where it stands” points to Open.

## Step 7: see what happens when a mark is wrong

This step breaks the recipe on purpose, so you recognize the error when you meet it for real.

Change `text: Open` to `text: Opened` and run the command again:

```
recipe "order-row": marks.status — no element matched {"text":"Opened","within":"clip"}
```

No image was written. A recipe is an assertion that the page contains the things it points at, and shotlist would rather fail than publish a screenshot with an arrow pointing at nothing.

Change `Opened` back to `Open` and confirm the run succeeds again.

## Step 8: number the parts instead of labelling them

When the prose beside a screenshot is already a numbered list, discs are tidier than labels. Replace the whole `callouts` section with a single `numbered` line:

screenshots/recipes/order-row.yaml

```
name: order-row

clip:
  css: 'li, div'
  contains: Acme Corp
  matching: '\$\d'
  maxChildren: 12
  pick: smallest
  pad: 20

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

numbered: [amount, status]
```

Run the command once more. The labels and arrows are gone, and each region now carries a small red disc, numbered in the order you listed the marks.

Put the `callouts` section back before you move on — the next tutorial uses it.

## Step 9: list what you have

```
npx shotlist
```

With no arguments, shotlist prints every recipe it found. In a real project this folder holds one file per screenshot in your documentation, and `npx shotlist --all` re-takes all of them.

## What you have learned

- installed shotlist and Playwright into a project
- written a configuration naming the application to photograph
- captured a whole window, then narrowed the capture to one region
- described that region by what a person sees rather than by its markup
- annotated it with labelled callouts, and with numbered discs
- seen shotlist refuse to produce a screenshot whose annotations no longer match the page

## Next steps

- [Keeping a screenshot current](/docs/tutorials/keeping-a-screenshot-current/) continues in the same project. It publishes this screenshot into Ledger's own documentation page, then detects when the application changes underneath it.
- [Capture a page after interaction](/docs/how-to/drive-a-page/) shows how to click and type before capturing. The example application has a "Load more" button to practise on.
- [Recipe file](/docs/reference/recipe/) lists every field a recipe can contain.

Written by Nicola Mustone · Applies to shotlist 0.6.0 · Maintained by Nicola Mustone

Published date unavailable · Updated date unavailable · [View source](https://github.com/SirDarcanos/shotlist.dev/blob/main/src/pages/docs/tutorials/first-screenshot.astro) · [Propose a correction](https://github.com/SirDarcanos/shotlist.dev/edit/main/src/pages/docs/tutorials/first-screenshot.astro)
