Skip to content

Flows

Some screens have no address: a menu that’s open, a confirmation dialog, a form with an error under one field. A flow reaches such a screen with a few steps, and takes a screenshot wherever you say. This page is also the reference for every step, used by flows, --do, sessions and the MCP tools.

flows/delete-account.yaml
start: /settings/account
steps:
- click: "role=button[name='Delete account']"
- wait: 'role=dialog'
- shot: confirm
- fill: 'input[name=confirm]'
with: 'wrong words'
- click: "role=button[name='Delete']"
- wait: 'text=Type DELETE to confirm'
- shot: error
facetshot flows/delete-account.yaml --on phone,desktop --as light,dark

Each shot becomes a screen of its own in the results, here delete-account.confirm and delete-account.error, each with its own contact sheet. The flow runs from the start once per device and variant.

The flow’s two shot steps, confirm and error, become two screens, each with a contact sheet of desktop and phone in light and dark

For a quick look, give the steps on the command line with --do. They are played on every target you name, and each shot is a screenshot:

facetshot /settings/account --do "click:role=button[name='Delete account']" --do "wait:role=dialog" --do shot:confirm

Agents pass the same strings in the MCP tools’ steps, and sessions take them in act.

The second column is how a step is written in a flow file; the third is the same step on the command line, in act, and in MCP steps.

Step In a flow file Inline Does
click click: <element> click:<element> clicks it
double-click dblclick: <element> dblclick:<element> double-clicks it
right-click rightclick: <element> rightclick:<element> right-clicks it, to open a context menu
tap tap: <element> tap:<element> taps it (touch devices)
long press longpress: <element> longpress:<element> presses and holds it for 0.8 s
hover hover: <element> hover:<element> moves the pointer over it
focus focus: <element> focus:<element> focuses it
drag drag: <element> + to: <element> drag:<element>><element> drags the first onto the second
fill fill: <element> + with: <text> fill:<element>=<text> replaces a field’s text
type type: <text> type:<text> types into whatever has focus
press press: <key> press:<key> presses a key, with modifiers: Enter, Escape, Shift+Meta+K
select select: <element> + option: <value> select:<element>=<value> picks an option in a <select>
scroll the page scroll: <pixels> scroll:<pixels> scrolls the page by that much
scroll to an element scroll: <element> scroll:<element> scrolls the element into view
scroll inside an element scroll: <element> + by: <pixels> scroll:<element>=<pixels> scrolls inside a scrollable element
wait wait: <element> wait:<element> waits until it is visible (up to 15 s)
wait until gone gone: <element> gone:<element> waits until it is hidden (up to 15 s)
go to goto: <path> goto:<path> opens another address
back, forward, reload back or back: true back the browser’s buttons
menu menu: File>Export… menu:File>Export… chooses an item from a desktop app’s menu bar
screenshot shot: <name> shot:<name> waits for the screen to be ready, then takes a screenshot

A flow with no shot step takes one screenshot at the end. Steps that act on an element wait up to 10 seconds for it. Inline, fill: and select: take the text after the last =, so the text itself can’t contain =; use a flow file for such text.

Steps are played as written: they aren’t filled with placeholders or environment variables. A sign-in flow (a persona’s login) therefore holds its credentials as written. Use a test account, keep the flow out of git, or give the persona a storageState file instead (Personas).

Wherever a step takes <element>, you can write any of these:

Write Means
role=button[name='Save'] an element by its accessible role and name. It survives most layout and style changes
text=Forgot password? an element by its visible text
label=Email the field with that label
placeholder=Search the field with that placeholder
alt=Logo the image with that alt text
title=Close the element with that title attribute
testid=menu the element with data-testid="menu"
#submit, .card:nth-child(2) plain CSS, or any other Playwright selector
@x,y a point, in CSS pixels from the top left of the screen
e14 in a session, the element with that ref in the latest list

label=, placeholder=, alt=, title= and testid= use Playwright’s getByLabel, getByPlaceholder, getByAltText, getByTitle and getByTestId. role= and text= are Playwright’s own selector engines. When several elements match, the first one is used.

If you don’t know the steps yet, open a session, try them one at a time, and close it with --save flows/name.yaml. The steps that worked become a flow.

Flows and profiles together make a plan you can run whenever you like. Decide once what to look at and how to get there, then let facetshot do it on every device and variant:

  1. Write a flow for each state that no address reaches (or save one from a session).
  2. List the pages and the flows in a profile’s targets. A pattern such as flows/*.yaml takes every flow in the folder.
  3. Run facetshot -p release (MCP: capture with profile: "release") whenever you need to check everything.
profiles: {
release: { targets: ['/', '/pricing', 'flows/*.yaml'], on: ['review'], as: ['light', 'dark', 'large'] }
}

The run plays the same steps everywhere, so the screenshots can be compared from one run to the next with facetshot diff. facetshot list flows and facetshot list profiles show what a project already has.

The screenshots the flow didn’t reach are reported as failed, with the step that stopped it:

delete-account.error phone/light: step 6 of delete-account ({"click":"role=button[name='Delete']"}) didn't work: Timeout 10000ms exceeded.

The contact sheet then shows how far the flow got on each device.

The same flow file works outside the browser. This table is the one place that says which steps work where:

Where Steps that work Elements named by
Electron, Tauri’s UI in WebKit every step, as in a browser; menu: in Electron everything above
Native Mac apps, Tauri’s real window every step but longpress, back, forward and reload (how each is done) role=, text=, label=, title= (the whole name, any case), a point, a session’s ref
iOS simulator click/tap, longpress (and rightclick, which becomes a long press), drag, fill, type, wait, gone, scroll by pixels (a swipe), goto (how) role=, text=, #id, an accessibility label, a point

On the iOS simulator, dblclick, hover, focus, press, select, scroll to an element, menu, back, forward and reload aren’t available. CSS selectors work only in pages (browsers, Electron, Tauri’s UI in WebKit).

On apps, a point is in points from the top left of the window (Mac) or the screen (iOS). A flow for an app starts with start: app:/path. A plain start: /path also runs in the desktop app’s window when the config has a desktop section, and only there when it has no web section.