Flow commands
Every step in a flow is one command: - name (bare), - name: value (scalar) or - name: with a map of keys. Selector-taking commands accept a bare string (exact visible text) or a selector object — see Flow file & selectors. Durations are milliseconds. ${VAR} is substituted in string values — see Variables.
| Command | Summary | web | android | ios |
|---|---|---|---|---|
| App & navigation | ||||
launchApp |
Launch the app (or open the URL) | ✓ | ✓ | ✓ |
stopApp |
Stop the app | ✓ | ✓ | ✓ |
installApp |
Install a build (apk / ipa) | — | ✓ | ✓ |
openLink |
Open a URL / deep link | ✓ | ✓ | ✓ |
clearState |
Wipe the app’s stored data | ✓ | ✓ | ✓ |
grantPermissions |
Grant the app’s runtime permissions | — | ✓ | ✓ |
setPermissions |
Allow / deny individual permissions | — | ✓ | ✓ |
back |
Go back | ✓ | ✓ | — |
vscodeCommand |
Run a VS Code command (VS Code) | ✓ | — | — |
| Taps & gestures | ||||
tapOn |
Tap an element or a point | ✓ | ✓ | ✓ |
longPressOn |
Long-press an element or a point | ✓ | ✓ | ✓ |
doubleTapOn |
Double-tap an element or a point | ✓ | ✓ | ✓ |
scroll |
Scroll the screen | ✓ | ✓ | ✓ |
swipe |
Swipe by direction or between points | ✓ | ✓ | ✓ |
scrollUntilVisible |
Scroll until an element appears | ✓ | ✓ | ✓ |
dragAndDrop |
Drag one element onto another | ✓ | ✓ | ✓ |
check |
Tick a checkbox or switch | ✓ | ✓ | ✓ |
uncheck |
Untick a checkbox or switch | ✓ | ✓ | ✓ |
hover |
Move the pointer over an element | ✓ | — | — |
| Text input & keys | ||||
inputText |
Type into the focused field | ✓ | ✓ | ✓ |
inputRandomEmail |
Type a random e-mail | ✓ | ✓ | ✓ |
inputRandomPersonName |
Type a random person name | ✓ | ✓ | ✓ |
inputRandomNumber |
Type a random number | ✓ | ✓ | ✓ |
inputRandomText |
Type random text | ✓ | ✓ | ✓ |
inputRandomCityName |
Type a random city name | ✓ | ✓ | ✓ |
inputRandomCountryName |
Type a random country name | ✓ | ✓ | ✓ |
inputRandomColorName |
Type a random color name | ✓ | ✓ | ✓ |
clearText |
Clear the focused field | ✓ | ✓ | ✓ |
eraseText |
Erase the last N characters | ✓ | ✓ | ✓ |
pressKey |
Press a key or a combo | ✓ | ✓ | ✓ |
hideKeyboard |
Dismiss the on-screen keyboard | ✓ | ✓ | ✓ |
setClipboard |
Set the flow’s clipboard register | ✓ | ✓ | ✓ |
pasteText |
Paste the register into the field | ✓ | ✓ | ✓ |
| Forms & page (web) | ||||
selectOption |
Choose an option in a | ✓ | — | — |
uploadFile |
Attach files to a file input | ✓ | — | — |
assertUrl |
Assert the page address | ✓ | — | — |
| Assertions & waits | ||||
assertVisible |
Assert an element is visible | ✓ | ✓ | ✓ |
assertNotVisible |
Assert an element is gone | ✓ | ✓ | ✓ |
waitForVisible |
Wait until an element is visible | ✓ | ✓ | ✓ |
extendedWaitUntil |
Wait until visible / not visible | ✓ | ✓ | ✓ |
waitForAnimationToEnd |
Wait for the screen to settle | ✓ | ✓ | ✓ |
assertTrue |
Assert over values or a JS expression | ✓ | ✓ | ✓ |
copyText |
Copy an element’s text into a variable | ✓ | ✓ | ✓ |
assertFile |
Assert a workspace file exists (VS Code) | ✓ | — | — |
assertScreenshot |
Visual check against a baseline | ✓ | ✓ | ✓ |
| Screenshots & video | ||||
takeScreenshot |
Save a screenshot | ✓ | ✓ | ✓ |
startRecording |
Start screen recording | ✓ | ✓ | ✓ |
stopRecording |
Stop recording and save the video | ✓ | ✓ | ✓ |
| Device state | ||||
setLocation |
Mock the GPS location | ✓ | ✓ | ✓ |
travel |
Move through GPS points | ✓ | ✓ | ✓ |
setOrientation |
Rotate the device | ✓ | ✓ | ✓ |
setAirplaneMode |
Turn airplane mode on / off | — | ✓ | — |
toggleAirplaneMode |
Toggle airplane mode | — | ✓ | — |
addMedia |
Add photos / videos to the gallery | — | ✓ | ✓ |
clearKeychain |
Clear the iOS keychain | — | — | ✓ |
| Control flow | ||||
runFlow |
Run a helper file or an inline block | ✓ | ✓ | ✓ |
repeat |
Repeat a block | ✓ | ✓ | ✓ |
retry |
Re-run a block on failure | ✓ | ✓ | ✓ |
| AI | ||||
assertWithAI |
AI screen assertion | ✓ | ✓ | ✓ |
assertNoDefectsWithAI |
AI visual-defect check | ✓ | ✓ | ✓ |
extractTextWithAI |
AI text extraction into a variable | ✓ | ✓ | ✓ |
| Scripting | ||||
evalScript |
Run inline JavaScript | ✓ | ✓ | ✓ |
runScript |
Run a JavaScript file | ✓ | ✓ | ✓ |
| API & data | ||||
request |
HTTP request with extract / assert | ✓ | ✓ | ✓ |
sql |
SQL statement with extract / assert | ✓ | ✓ | ✓ |
VS Code extension targets behave like web in this table; desktop-app targets (--driver app) share the web DOM walk but lack most optional capabilities — see Testing desktop apps.
App & navigation
Section titled “App & navigation”launchApp
Section titled “launchApp”Launches the app under test — on web it navigates to the URL. The bare string form takes the app id / URL; the map form adds clearState (wipe stored data first), stopApp (force a cold start), permissions (a name: allow|deny|unset map, mobile) and arguments (launch arguments, mobile).
Keys: appId — string · required; clearState — bool; stopApp — bool; permissions — map; arguments — map.
commands: - launchApp: com.example.app - launchApp: appId: com.example.app clearState: true permissions: notifications: allowstopApp
Section titled “stopApp”Terminates the app: bare - stopApp uses the flow’s appId, - stopApp: <appId> stops another one. killApp is an alias.
Aliases: killApp
Keys: appId — string.
commands: - stopApp - stopApp: com.example.otherinstallApp
Section titled “installApp”Installs a build from a path. grant: true grants runtime permissions right after the install.
Platforms: android, ios
Keys: path — string · required; grant — bool.
commands: - installApp: build/app-debug.apk - installApp: path: build/app.ipa grant: trueopenLink
Section titled “openLink”Opens a URL or deep link: browser navigation on web, am start -a VIEW on Android, simctl openurl on iOS.
Keys: url — string · required.
commands: - openLink: https://example.com/reset?token=${TOKEN}clearState
Section titled “clearState”Resets stored state: localStorage + cookies on web, pm clear on Android, the simulator’s app data container on iOS (simulator only). Bare form targets the flow’s appId; pass an app id to target another app.
Keys: appId — string.
commands: - clearState - clearState: com.example.othergrantPermissions
Section titled “grantPermissions”Grants the common runtime permissions (simctl privacy on iOS, pm grant on Android). Bare form uses the flow’s appId. Not supported on web — browsers have no permission model the driver can grant.
Platforms: android, ios
Keys: appId — string.
commands: - grantPermissionssetPermissions
Section titled “setPermissions”Sets permissions one by one: a name: allow|deny|unset map (all covers every permission), optionally under permissions: next to an appId. Names: camera, location, microphone, notifications, photos, contacts, calendar, bluetooth, motion, medialibrary, phone, reminders, siri, sms, storage.
Platforms: android, ios
Keys: appId — string; permissions — map.
commands: - setPermissions: camera: allow location: deny - setPermissions: appId: com.example.other permissions: all: unsetBrowser history back on web, the hardware Back key on Android. On iOS there is no system back — the step is a no-op there.
Platforms: web, android
commands: - backvscodeCommand
Section titled “vscodeCommand”VS Code flows only. id — a bare workbench.action.closeAllEditors, or { id, args } — goes through the helper extension the driver bundles, which answers when the command resolves; { title: … } types the palette title instead and is done when the palette closes. A bare string is read as an id when it is shaped like one (dotted, no spaces) and as a palette title otherwise. args needs id: the palette takes no arguments, and asking for both is an error rather than a silent drop — as is id together with title, or any other key in the map. Works in before: / after: too; before: clean is five of these steps.
Platforms: web
Keys: id — string; title — string; args — list of strings.
Exclusive: one of id, title.
commands: - vscodeCommand: workbench.action.closeAllEditors - vscodeCommand: title: "My Ext: Generate Config" - vscodeCommand: id: workbench.action.openSettings args: ["editor.fontSize"]Taps & gestures
Section titled “Taps & gestures”Taps the first match of a selector; off-screen targets are scrolled into view first. { point: "x, y" } taps coordinates instead (pixels or %). repeat taps N times with delay ms between taps (default 100). timeout (ms, default 5000) overrides the element wait for this tap alone.
Keys: selector — selector; repeat — integer; delay — ms; timeout — ms; point — point "x, y".
commands: - tapOn: "Log in" - tapOn: id: submit - tapOn: point: "50%, 90%" - tapOn: text: "+" repeat: 3 delay: 200 - tapOn: text: "Continue" timeout: 30000longPressOn
Section titled “longPressOn”Presses and holds for 600 ms — a selector or { point: "x, y" }.
Keys: selector — selector; point — point "x, y".
commands: - longPressOn: "Message" - longPressOn: point: "200, 400"doubleTapOn
Section titled “doubleTapOn”Two quick taps on a selector or { point: "x, y" }.
Keys: selector — selector; point — point "x, y".
commands: - doubleTapOn: "Photo"scroll
Section titled “scroll”Scrolls one screen up or down (bare - scroll = down). Horizontal moves are swipe: left|right.
Keys: direction — enum (up | down); within — selector.
commands: - scroll - scroll: upswipe: up|down|left|right, or { start, end } as "x, y" points in pixels or % of the screen.
Keys: direction — enum (up | down | left | right); start — point "x, y"; end — point "x, y".
Exclusive: direction not with start, end.
commands: - swipe: left - swipe: start: "50%, 80%" end: "50%, 20%"scrollUntilVisible
Section titled “scrollUntilVisible”Scrolls repeatedly until the selector is visible. Map form: direction (up|down|left|right, default down), timeout ms, speed 0–100, centerElement: true to finish with the element centred. Gives up after --scroll-attempts swipes (default 5).
Keys: selector — selector · required; direction — enum (up | down | left | right); timeout — ms; speed — integer; centerElement — bool; within — selector.
commands: - scrollUntilVisible: "Terms of service" - scrollUntilVisible: id: footer direction: down centerElement: truedragAndDrop
Section titled “dragAndDrop”Locates both elements and drags between their centres. duration (ms, default 600) is slow enough for a drop target’s hover state to fire, which is what most drag-and-drop implementations wait for.
Keys: from — selector · required; to — selector · required; duration — ms; path — pointList.
commands: - dragAndDrop: from: "Card" to: { id: done-column }Reads the element’s checked state first and taps only when it isn’t already on, then confirms the tap took — a disabled or covered control fails the step instead of passing silently. Safe to re-run.
Keys: selector — selector · required.
commands: - check: "Remember me"uncheck
Section titled “uncheck”The mirror of check: taps only when the element is on, then confirms it went off.
Keys: selector — selector · required.
commands: - uncheck: { id: newsletter }Moves the pointer without pressing — for menus, tooltips and controls that only appear under the cursor. Web and VS Code only; a touchscreen has no hover.
Platforms: web
Keys: selector — selector · required.
commands: - hover: "Account" - assertVisible: "Sign out"Text input & keys
Section titled “Text input & keys”inputText
Section titled “inputText”Types text into the currently focused input. ${VAR} is substituted first.
Keys: text — string · required.
commands: - tapOn: "Email" - inputText: "${EMAIL}"inputRandomEmail
Section titled “inputRandomEmail”Types a generated e-mail address into the focused field.
Keys: length — integer.
commands: - inputRandomEmailinputRandomPersonName
Section titled “inputRandomPersonName”Types a generated first + last name.
Keys: length — integer.
commands: - inputRandomPersonNameinputRandomNumber
Section titled “inputRandomNumber”Types a random number; length digits (default 8) — bare, : N or { length: N }.
Keys: length — integer.
commands: - inputRandomNumber: 6inputRandomText
Section titled “inputRandomText”Types random letters; length characters (default 8) — bare, : N or { length: N }.
Keys: length — integer.
commands: - inputRandomText: length: 12inputRandomCityName
Section titled “inputRandomCityName”Types a city name from a built-in list.
Keys: length — integer.
commands: - inputRandomCityNameinputRandomCountryName
Section titled “inputRandomCountryName”Types a country name from a built-in list.
Keys: length — integer.
commands: - inputRandomCountryNameinputRandomColorName
Section titled “inputRandomColorName”Types a color name from a built-in list.
Keys: length — integer.
commands: - inputRandomColorNameclearText
Section titled “clearText”Empties the focused text field.
commands: - tapOn: "Search" - clearTexteraseText
Section titled “eraseText”- eraseText: N deletes N characters before the caret; bare - eraseText clears the whole field.
Keys: count — integer.
commands: - eraseText: 3pressKey
Section titled “pressKey”Keys: enter, home, back, backspace, tab, escape, volume up, volume down, lock, power (case-insensitive). Combos like cmd+shift+p (mods ctrl/control, alt/option, shift, cmd/meta/win/super) work on web and VS Code targets.
Keys: key — enum (enter | home | back | backspace | tab | escape | arrow up | arrow down | arrow left | arrow right | volume up | volume down | lock | power) · required.
commands: - pressKey: enter - pressKey: cmd+shift+phideKeyboard
Section titled “hideKeyboard”Dismisses the software keyboard (blur on web, best-effort on iOS).
commands: - hideKeyboardsetClipboard
Section titled “setClipboard”Stores text in the flow’s copy register — the same one copyText fills and ${copiedText} reads. It is a flow-level register, not the OS clipboard.
Keys: text — string · required.
commands: - setClipboard: "hello" - pasteTextpasteText
Section titled “pasteText”Types the copy register (copyText / setClipboard, else a copiedText variable) into the focused field.
commands: - copyText: "Order #" - tapOn: "Search" - pasteTextForms & page (web)
Section titled “Forms & page (web)”selectOption
Section titled “selectOption”Picks an option by its visible text (option), by the underlying value (optionValue) or by position (optionIndex) — exactly one of the three. Fires input and change, so frameworks see the edit.
Platforms: web
Keys: selector — selector · required; option — string; optionValue — string; optionIndex — integer.
Exclusive: one of option, optionValue, optionIndex.
commands: - selectOption: id: country option: "Germany"uploadFile
Section titled “uploadFile”A file dialog is browser chrome and cannot be clicked, so the files are handed to the input directly. files takes a path or a list of paths, resolved like every other file a flow names; a missing file fails the step.
Platforms: web
Keys: selector — selector · required; files — list of strings · required.
commands: - uploadFile: id: avatar files: fixtures/face.pngassertUrl
Section titled “assertUrl”The bare form matches exactly; the map form takes one of equals, contains or regex, plus timeout (ms). Polls until it matches, so a click that navigates has time to land.
Platforms: web
Keys: equals — string; contains — string; regex — string; timeout — ms.
Exclusive: one of equals, contains, regex.
commands: - assertUrl: { contains: "/checkout" }Assertions & waits
Section titled “Assertions & waits”How a run knows the page settled
Section titled “How a run knows the page settled”The engine settles before every command, so most flows need no explicit wait at
all. On web a page counts as settled when four signals are quiet at once — the
document has finished loading, no request is in flight (streams that stay open
by design, and anything older than 5 s, do not count), the DOM has not changed
for 150 ms, and the address has not changed for 150 ms — and two reads of
the screen come back the same, ignoring drift of up to 2 px so an animation’s
tail is not mistaken for movement. On mobile and native the platform’s own idle
signal stands in for the first three. Settling is capped by --settle-timeout
(2000 ms); a step’s own timeout: raises that cap for that step. A screen whose
tree is slower to read than that budget still gets read twice — the cap stretches
to what the reads themselves cost, so a slow page reads slow, not unsettled.
A run that could not settle in time still goes on, and says so. assertNotVisible,
waitForNotVisible and when: notVisible only pass on a settled screen: an
element missing from a page that is still drawing itself has not gone away, it
has not arrived — so they fail with the reason instead of passing on a half-drawn
screen. Everything else (tapOn, assertVisible, inputText, …) acts anyway at
the deadline and the step carries acted on a page that had not settled (Network(2)), plus settled: false and unsettledReason in the report. The
reasons are Loading, Network(n), Mutating, Navigating and Timeout.
assertVisible
Section titled “assertVisible”Waits up to timeout ms (default 5000, --wait-timeout) for the selector to be visible, then fails the step if it isn’t.
Keys: selector — selector · required; timeout — ms.
commands: - assertVisible: "Welcome" - assertVisible: id: toast timeout: 10000assertNotVisible
Section titled “assertNotVisible”Polls until the selector is absent on a settled screen (up to timeout ms, default 5000); fails if it is still visible, and fails with the reason if the page never settled — an element missing from a page still drawing itself is not proof it went away.
Keys: selector — selector · required; timeout — ms.
commands: - assertNotVisible: "Loading…"waitForVisible
Section titled “waitForVisible”Same as assertVisible; reads better when a step is a wait, not a check.
Keys: selector — selector · required; timeout — ms.
commands: - waitForVisible: text: "Dashboard" timeout: 15000extendedWaitUntil
Section titled “extendedWaitUntil”Alias form: { visible: <selector> } or { notVisible: <selector> } plus timeout ms.
Keys: visible — selector; notVisible — selector; timeout — ms.
commands: - extendedWaitUntil: notVisible: "Spinner" timeout: 20000waitForAnimationToEnd
Section titled “waitForAnimationToEnd”Waits until the screen stops changing; timeout ms caps the wait (default --settle-timeout, 2000).
Keys: timeout — ms.
commands: - waitForAnimationToEnd - waitForAnimationToEnd: timeout: 5000assertTrue
Section titled “assertTrue”{ equals: [a, b] } / { contains: [haystack, needle] } over ${}-substituted strings, or a bare string evaluated as JavaScript in the flow’s script context (assertTrue: "${count} > 1").
Keys: expression — string; equals — list of strings; contains — list of strings.
Exclusive: one of expression, equals, contains.
commands: - copyText: id: total into: total - assertTrue: equals: ["${total}", "42"] - assertTrue: "Number(total) > 40"copyText
Section titled “copyText”Reads the element’s text into into (default copiedText), available as ${name} and as a JS global. Alias copyTextFrom.
Aliases: copyTextFrom
Keys: selector — selector · required; into — string.
commands: - copyText: id: order-number into: orderassertFile
Section titled “assertFile”For VS Code extension tests: asserts that path (relative to the open workspace) exists and, with contains, includes the text.
Platforms: web
Keys: path — string · required; contains — string.
commands: - assertFile: path: out/report.md contains: "PASS"assertScreenshot
Section titled “assertScreenshot”Compares the screen with <name>.png; the first run creates the baseline. tolerance is the allowed percentage of differing pixels (default 0.5); a failing actual is saved as <name>.actual.png.
Keys: name — string · required; tolerance — number.
commands: - assertScreenshot: checkout - assertScreenshot: name: screens/home tolerance: 1.5Screenshots & video
Section titled “Screenshots & video”takeScreenshot
Section titled “takeScreenshot”Writes <name>.png (default screenshot.png) — relative to the current directory in the CLI, next to the run’s artifacts in the desktop.
Keys: name — string.
commands: - takeScreenshot: after-loginstartRecording
Section titled “startRecording”Starts recording (native on Android/iOS, screencast on web); stopRecording writes <name>.mp4 (default recording.mp4). Whole-run video is simpler with tapstep test --video.
Keys: name — string.
commands: - startRecording: checkout - tapOn: "Pay" - stopRecordingstopRecording
Section titled “stopRecording”Ends the recording started by startRecording and saves the file.
commands: - stopRecordingDevice state
Section titled “Device state”setLocation
Section titled “setLocation”Overrides the device location with { lat, lon } (geolocation override on web).
Keys: lat — number · required; lon — number · required.
commands: - setLocation: lat: 43.238 lon: 76.889travel
Section titled “travel”Sets the location to each "lat, lon" point in turn; with speed (m/s) the pause between points follows the distance, otherwise 1 s.
Keys: points — list of strings · required; speed — number.
commands: - travel: points: - "43.238, 76.889" - "43.240, 76.900" speed: 15setOrientation
Section titled “setOrientation”portrait, landscapeLeft (landscape), landscapeRight, upsideDown (case-insensitive).
Keys: orientation — enum (portrait | landscapeLeft | landscapeRight | upsideDown) · required.
commands: - setOrientation: landscapesetAirplaneMode
Section titled “setAirplaneMode”enabled or disabled (true/false also work). Android only.
Platforms: android
Keys: mode — enum (enabled | disabled) · required.
commands: - setAirplaneMode: enabledtoggleAirplaneMode
Section titled “toggleAirplaneMode”Flips airplane mode. Android only.
Platforms: android
commands: - toggleAirplaneModeaddMedia
Section titled “addMedia”Pushes one path or a list of paths into the device’s media library.
Platforms: android, ios
Keys: paths — list of strings · required.
commands: - addMedia: - fixtures/photo.jpg - fixtures/clip.mp4clearKeychain
Section titled “clearKeychain”Wipes the simulator’s keychain. iOS only.
Platforms: ios
commands: - clearKeychainControl flow
Section titled “Control flow”runFlow
Section titled “runFlow”runFlow: path.helper.yaml inlines a helper (must end in .helper.yaml, resolved relative to the calling file). The map form takes file or inline commands, an optional when ({ visible | notVisible: <selector> } or { platform: android | [web, ios] }) and env — variables scoped to the block (shadowed names are restored afterwards; new ones persist). Helper params: are passed via env.
Keys: file — string; when — condition; env — map; commands — commands.
commands: - runFlow: helpers/login.helper.yaml - runFlow: file: helpers/login.helper.yaml env: USER: qa@example.com - runFlow: when: visible: "Accept cookies" commands: - tapOn: "Accept cookies"repeat
Section titled “repeat”times: N or while: <condition> ({ visible | notVisible: <selector> }), plus commands. A while loop stops after 1000 iterations.
Keys: times — integer; while — condition; commands — commands · required.
commands: - repeat: times: 3 commands: - tapOn: "+" - repeat: while: visible: "Load more" commands: - tapOn: "Load more"Runs commands; on failure retries up to maxRetries times (default 1). A pass after retries is reported with a note.
Keys: maxRetries — integer; commands — commands · required.
commands: - retry: maxRetries: 2 commands: - tapOn: "Refresh" - assertVisible: "Updated"assertWithAI
Section titled “assertWithAI”Asks the configured model whether the assertion holds for the current screen. The bare form is optional: true — a failed check is a warning; { assertion, optional: false } fails the flow. Needs an AI connection: ai: in config.yaml or TAPSTEP_AI_* — see AI agents.
Keys: assertion — string · required; optional — bool.
commands: - assertWithAI: "The cart shows exactly two items" - assertWithAI: assertion: "No overlapping text on the screen" optional: falseassertNoDefectsWithAI
Section titled “assertNoDefectsWithAI”Asks the model for obvious visual defects (clipped text, overlaps, broken layout). Optional by default; { optional: false } fails the flow.
Keys: optional — bool.
commands: - assertNoDefectsWithAIextractTextWithAI
Section titled “extractTextWithAI”Asks the model to read something off the screen into into (default aiText); with pattern (regex) the answer must match — one retry, then the step fails.
Keys: query — string · required; into — string; pattern — string.
commands: - extractTextWithAI: query: "the 6-digit confirmation code" into: code pattern: "\\d{6}" - inputText: "${code}"Scripting
Section titled “Scripting”evalScript
Section titled “evalScript”Evaluates JS in the flow’s script context (one QuickJS context per run). Flow variables are globals; write output.name = … to publish ${output.name}; console.log is captured into the report. See Scripts.
Keys: script — string · required.
commands: - evalScript: "output.total = Number(price) * 2" - assertVisible: "${output.total}"runScript
Section titled “runScript”Runs a .js file (relative to the flow) in the same context; env adds variables for the script.
Keys: file — string · required; env — map.
commands: - runScript: file: scripts/prepare.js env: SEED: "42"API & data
Section titled “API & data”request
Section titled “request”Calls an HTTP endpoint from the flow (http is an alias). - request: <url> is a GET; the map form takes method, headers, query, json or body, auth ({ bearer } / { basic: { user, pass } }), extract (name → JSONPath into ${vars}), assert or retryUntil (status as a code or "2xx", json equalities / { contains }, headers, schema file or inline), timeout ms (default 30000). retryUntil polls every second. ${} is substituted in the request, not in expected values. With device: none a flow is pure API — see API testing.
Aliases: http
Keys: url — string · required; method — enum (GET | POST | PUT | PATCH | DELETE | HEAD | OPTIONS); headers — map; query — map; json — map; body — string; auth — map; extract — map; assert — map; retryUntil — map; timeout — ms.
Exclusive: one of json, body; one of assert, retryUntil; inside auth: one of bearer, basic.
commands: - request: method: POST url: "${API}/login" json: email: "${EMAIL}" password: "${PASSWORD}" extract: token: $.token assert: status: 200 - request: url: "${API}/me" auth: bearer: "${token}" retryUntil: json: $.status: active timeout: 20000Runs one statement against Postgres, MySQL or SQLite — seed a fixture before the UI, check what the UI wrote afterwards, clean up in after. db is the connection string (postgres://…, mysql://…, sqlite:<path>), usually ${DATABASE_URL} from .env; the flow header’s db: is the default for every step that omits it. Every step opens its own connection, so sqlite::memory: starts empty again in the next step — point sqlite: at a file. Exactly one of query (rows come back) and exec (affected count comes back) is required. params is a list of scalars — strings, numbers, booleans, null — bound positionally to the statement’s own placeholders ($1, $2 on Postgres, ? on MySQL and SQLite), so a value is never spliced into SQL text. A ${var} param carries an extracted value and binds as text, so on Postgres cast the placeholder when the column is not text (where id = $1::int). readOnly: true runs inside a transaction that is rolled back: a check that cannot change data. extract takes a JSONPath over the result — the rows array for query ($[0].id), { "affected": N } for exec — and assert takes rows, minRows, none, first (columns on the first row), contains (some row matches every column) and affected. timeout ms (default 30000) covers connecting and the statement. After the step ${sql.rows} or ${sql.affected} is available. The DSN is a secret: reports and logs show scheme://user:***@host/db. Writes are allowed — in CI, point db: at a read-only database user for check-only suites. See API testing.
Keys: db — string; query — string; exec — string; params — list of strings; timeout — ms; readOnly — bool; extract — map; assert — map.
Exclusive: one of query, exec.
commands: - sql: exec: "insert into coupons (code, percent) values ($1, $2)" params: ["QA10", 10] assert: affected: 1 - sql: query: "select status, total from orders where coupon = $1" params: ["QA10"] readOnly: true extract: total: $[0].total assert: rows: 1 first: status: paid