Skip to content

Capture a page behind a sign-in

Pages that require an account cannot be reached by a browser that has just started. This guide saves a signed-in session once, then reuses it for every screenshot that needs it.

A session is what a signed-in browser carries: its cookies and per-origin local storage. shotlist stores it in a JSON file. You never write that file by hand.

Step 1: declare the sessions

Name each session in your configuration, and say where its file should live:

shotlist.config.yaml
site:
  sessions:
    admin: .shotlist/admin.json
    reader:
      path: .shotlist/reader.json
      verify: '[data-signed-in]'

Sessions are named rather than singular because the same page is often worth photographing more than once: as an administrator, and as somebody with no rights at all.

verify is a selector that only a signed-in page contains, such as an avatar, an admin bar or a sign-out link. Set it. Without it, an expired session does not fail: the site redirects to its sign-in form, every screenshot in the run becomes a picture of that form, and --install publishes them.

Step 2: sign in once

npx shotlist --login admin

This opens a browser at site.url and waits while you sign in by hand. When you are done, it writes the cookies the browser came back with to the path you declared, creating the folder if needed. Your password never reaches shotlist.

What it writes is this site's cookies and local storage, and nothing else. Signing in through Google, Discord or any other provider leaves that provider's session in the browser too, and none of it is saved — those cookies are your account rather than a screenshot credential. --login names what it left out.

Before writing, it loads what remains into a fresh browser and checks the session's verify selector against it. A session that no longer signs in is refused with the hosts it dropped, and nothing is written — see when the sign-in needs a provider's cookies.

Step 3: point a recipe at the session

Add one line to any recipe that should be taken as that account:

screenshots/recipes/dashboard.yaml
name: dashboard
install: guide
session: admin

clip: { css: '.panel' }

Leave it out and the browser is a stranger to the site, which is what you want for screenshots of signed-out pages.

Sign in without a person at the keyboard

In continuous integration there is nobody to type a password. Write the sign-in as a macro and pass it to --using, which runs it in a headless browser instead of waiting:

npx shotlist --login admin --using sign-in
screenshots/macros/sign-in.yaml
steps:
  - fill: { label: Email }
    value: [email protected]
  - fill: { label: Password }
    value: ${env.ADMIN_PASSWORD}
  - click: { role: button, name: Sign in }

${env.ADMIN_PASSWORD} resolves only for names the person running the command allowed, so the run must name it:

npx shotlist --login admin --using sign-in --allow-env ADMIN_PASSWORD

--allow-env is repeatable. A CI system that already exports its secrets can list the names in SHOTLIST_ENV instead; the two add up. An allowed name that is empty stays unresolved and the run reports which one it was, rather than typing nothing into a password field and reporting a failed sign-in.

If your project always signs in the same way, name the variables once in the configuration instead of repeating the flag on every run:

shotlist.config.yaml
allowEnv:
  - ADMIN_PASSWORD

allowEnv holds names, never values — the values stay in the environment. It adds to whatever the command line and SHOTLIST_ENV allowed, and an --untrusted run ignores it, the same way it ignores site.allow. A machine that sets SHOTLIST_ENV_DENY can put a name beyond all three. See the configuration reference.

Keeping the password in a .env file

You can, and it works with everything on this page — but load it yourself. shotlist never reads a .env file. That name is on the list of paths it refuses to open in every mode, with no flag to turn it off, so a configuration cannot aim anything at one either.

Write the file as usual:

.env
ADMIN_PASSWORD=hunter2
SHOTLIST_ENV=ADMIN_PASSWORD

Then put it into the environment before shotlist starts. Node can do it:

node --env-file=.env node_modules/.bin/shotlist --all --install

Or the shell can:

set -a; . ./.env; set +a
npx shotlist --all --install

--env-file arrived in Node 20.6, and shotlist supports Node 20 and later, so on 20.0 to 20.5 it fails with an unknown option. The set -a form works everywhere.

Once the file is loaded there is nothing special about it. shotlist reads the process environment, so SHOTLIST_ENV, SHOTLIST_ENV_DENY, SHOTLIST_ALLOW, SHOTLIST_WORK_LIMITS, SHOTLIST_DENY and SHOTLIST_UNTRUSTED can all live in .env alongside the secrets themselves. For why shotlist will not load it for you, see What a configuration can do.

Without a terminal to wait in and without --using, the run reports that it cannot sign in rather than hanging.

When the sign-in needs a provider's cookies

Almost no application does: its own session is a cookie or a token under its own origin, and that is what gets kept. Where one genuinely needs the provider's, approve its destination and name the credential host on the session:

shotlist.config.yaml
site:
  allow:
    - accounts.google.com
  sessions:
    admin:
      path: .shotlist/admin.json
      verify: '[data-signed-in]'
      keep: [accounts.google.com]

site.allow lets the browser reach the provider; keep retains that provider's cookies. The file then signs in as that Google account as well as yours, and --login says so every time it writes one. Treat it as the credential it is: what is on disk is no longer only a screenshot session.

Keep the session file out of version control

The file holds live cookies. Anyone who has it is signed in as whoever created it. shotlist writes it 0600, so no other account on the machine can read it — version control is the gap that leaves. Add the folder to your .gitignore:

.gitignore
.shotlist/

An --untrusted run loads no session at all, so a configuration you did not write cannot ask to be somebody.