Writing flows
A flow is one journey through your app, written in Maestro's YAML: what to tap, what to type, and what should be on the screen. Trailcam runs it as written on every platform your project has.
The shape of a flow
A flow has a short config section, a line with ---, then its steps:
appId: ${APP_ID}
name: Checkout
tags:
- checkout
env:
# A test card your payment provider accepts.
CARD_NUMBER: "4242 4242 4242 4242"
---
- launchApp:
clearState: true
- tapOn: "Shop"
- tapOn: "Add to basket"
- tapOn: "Checkout"
- tapOn: "Card number"
- inputText: ${CARD_NUMBER}
- tapOn: "Pay now"
- extendedWaitUntil:
visible: "Thanks for your order"
timeout: 15000appId: ${APP_ID} is required. Trailcam fills ${APP_ID} with the app ID of the build each run uses, so one flow
runs on your iOS and Android builds alike.
Start from a template (blank, sign up, sign in, checkout or onboarding), describe the journey and let Trailcam AI write it, or let Trailcam AI find your journeys in a new build and write the ones you choose.
Variables
Values a flow needs, like a test account's password, go in its env: section and are used as ${NAME}:
env:
PASSWORD: Trailcam-check-2468
---
- tapOn: "Password"
- inputText: ${PASSWORD}Variables are passed to the device privately. A run's steps show them by name, and Trailcam AI reads them only by name (what it sees). The device's own message can quote a value a step used, so use accounts made for testing, never a real customer's.
Sign-ups that email a code
Every run has an email address of its own, made for that run: ${TRAILCAM_EMAIL}. Sign up with it, and the step
runScript: trailcam/email-code.js waits for the email your app sends and puts the code in it in
${output.emailCode}, for the next step to type:
- tapOn: "Email"
- inputText: ${TRAILCAM_EMAIL}
- tapOn: "Continue"
# Waits for your app's email, and reads the code in it.
- runScript: trailcam/email-code.js
- tapOn: "Code"
- inputText: ${output.emailCode}
- tapOn: "Confirm email"-
It waits a minute, or as long as you say, up to 5 minutes. A code of a shape it doesn't find can be named with a pattern:
- runScript: file: trailcam/email-code.js env: TIMEOUT: 120 PATTERN: "[A-Z]{3}-\\d{3}" -
It finds the code near words like "code" or "verify", or on a line of its own: 4 to 8 digits, or capitals and digits like
K7Q-2M9, never a price, a date or a phone number. -
A link too: when the email asks you to confirm or sign in by a link, it's in
${output.emailLink}. -
The run's page shows what arrived: each email's sender, subject and words, the code found, and whether the flow typed it.
-
Each run's address is new, so a sign-up never meets the last run's account. It takes mail from the moment the run is queued until 30 minutes after it ends, at most 20 emails of up to 1 MB, and turns away anything else. Emails are kept with the run's recording and removed with it, and shared links leave them out.
-
Codes by text message can't be read yet.
Describe it and Find my journeys write sign-ups like this themselves.
Commands
Trailcam runs Maestro 2. The editor suggests every command it can run as you type:
| What | Commands |
|---|---|
| The app | launchApp, stopApp, killApp, clearState, clearKeychain |
| Tapping and gestures | tapOn, doubleTapOn, longPressOn, swipe, scroll, scrollUntilVisible, back |
| Typing | inputText, inputRandomEmail, inputRandomPersonName, inputRandomNumber, inputRandomText, eraseText, pressKey, pasteText, copyTextFrom, setClipboard, hideKeyboard |
| Checking the screen | assertVisible, assertNotVisible, assertTrue, assertDarkMode, assertLightMode |
| Checking with AI | assertWithAI, assertNoDefectsWithAI, extractTextWithAI |
| Waiting | extendedWaitUntil, waitForAnimationToEnd |
| The device | openLink, setLocation, travel, setAirplaneMode, toggleAirplaneMode, setDarkMode, toggleDarkMode, setOrientation, setPermissions |
| Pictures and recordings | takeScreenshot, startRecording, stopRecording |
| Logic | evalScript, runFlow (with its steps inline), repeat, retry, runScript: trailcam/email-code.js (above) |
The AI commands are answered by Trailcam's own models, with no key of your own. Each plan includes a number of them a month; see Trailcam AI.
What flows can't do on Trailcam
A few Maestro features need files or a network that a hosted device doesn't have. The editor marks them as errors, and a flow with errors doesn't run until they're fixed:
addMediaandassertScreenshot, which read files from your computer, andrunScriptwith any script but Trailcam's own (trailcam/email-code.js).runFloworretrypointing at another file: put the steps inline withcommands:instead.- Screenshot and recording names that are paths, like
../shot. - Web requests from scripts (Maestro's
http). - Links to your own computer or private network, like
localhost,10.0.2.2or192.168.…. - YAML aliases (
*name).
Warnings never stop a flow from running.
Devices
Each run starts on a clean device, discarded afterwards, so nothing one run does carries over to the next. Runs use each platform's default device, an iPhone 17 or a Pixel 8, unless they name others:
- In the app, the arrow beside Run chooses the devices, and a system for each. Run remembers your choice for that flow, in this browser.
- From CI,
--device(or the Action'sdevices), as the command-line tool shows. - A production check runs on one device for each platform, chosen when you add or change it.
| Device | --device | Systems |
|---|---|---|
| iPhone 17 (the default) | iphone-17 | iOS 26.5 |
| iPhone 17 Pro Max | iphone-17-pro-max | iOS 26.5 |
| iPad (A16) | ipad-a16 | iPadOS 26.5 |
| iPad Pro 13-inch (M5) | ipad-pro-13-inch-m5 | iPadOS 26.5 |
| Pixel 8 (the default) | pixel-8 | Android 14 (the default), Android 16 and 11 |
| Pixel Tablet | pixel-tablet | Android 16 |
| Pixel 9 Pro Fold | pixel-9-pro-fold | Android 16 |
A run counts once on any device. An iPhone app runs on an iPad as iPadOS runs it, and an app built only for iPad runs only on the iPads. A foldable runs in the position it starts in: a flow can't fold or unfold it.
Limits
- A flow's run can take up to 15 minutes. Long runs count once for every 3 minutes of the flow; see Runs and results.
- A flow can have up to 1,000 steps and 100,000 characters, and its name up to 80 characters.
In the editor
- ⌘ S (or Ctrl S) saves. If a teammate saved the flow while you were editing, Trailcam asks first: load their version, or save yours over it.
- Ctrl Space suggests commands, F8 goes to the next problem, and quick fixes correct the common ones.
- When Trailcam AI suggests a change, Run to check runs the flow as it is in the editor, unsaved, on the build that failed. It's a draft: it never becomes the flow's latest result.