Test format
YAML test format
Schema reference for defining tests as YAML
Tests can be exported as YAML from the test editor for version control and CI/CD use, or authored from scratch via the CLI (revyl test create --from-file my-test.yaml).
Every block uses an explicit type: field plus a step_description: payload. The shape is uniform across all step types, which keeps the schema parser-friendly and round-trippable through the editor and CLI.
Schema
test:
metadata:
name: "Test name" # required
platform: "android" # required: "android", "ios", or "web"
tags: # optional
- smoke
build:
name: "My App" # required, matches the app name in Revyl
branch_id: "BRANCH_UUID" # optional; omit for Default branch
build_selection: inherit # inherit (Automatic), latest, latest_all_branches, or pinned
# pinned_build_id: "BUILD_UUID" # required with pinned, with branch_id
blocks: # required, array of steps
- type: instructions
step_description: "..."The app launches automatically at the start of every test, so you don't need an initial open_app step.
Build Selection
A test's choice lives under test.build. See
How a run picks its build for the
model.
build_selection | Choice | Fields | Runs |
|---|---|---|---|
inherit (default) | Automatic | Optional branch_id | The branch's default build: its pinned build, or else its newest |
latest | Latest build | Optional branch_id | The branch's newest build, even when it's pinned |
latest_all_branches | All branches · Latest build | No branch_id | The newest build on any of the app's branches |
pinned | Exact build | branch_id and pinned_build_id | That build |
- No
branch_idmeans Default branch: runs from the CLI, MCP, and the Revyl GitHub Actions try their Git branch first, then the app's default branch. YAML never records the Git branch. - YAML takes a branch ID, not a name. Find IDs with
revyl app branch list --app "My App" --json. - On update, omitting
branch_id,build_selection, andpinned_build_idkeeps the saved choice;build_selection: inheritwith no IDs saves Default branch. - A new test from YAML with no choice starts on Default branch.
revyl test pullomitslatest_all_branches; add it back before creating a new test from pulled YAML.
Older files. The pinned_version key (a version label) is still accepted. A
file with both pinned_version and the fields above must name the same build,
branch, and version.
Example
test:
metadata:
name: "Login flow"
platform: android
build:
name: "My App"
blocks:
- type: instructions
step_description: "Type '{{username}}' in the email field"
- type: instructions
step_description: "Type '{{password}}' in the password field"
- type: instructions
step_description: "Tap the Login button"
- type: validation
step_description: "The dashboard screen is visible"
- type: extraction
step_description: "Get the welcome message text"
variable_name: "welcome_msg"Block Types
type: value | Purpose | Additional fields |
|---|---|---|
instructions | A single user action (tap, type, swipe) | — |
validation | Assert something is visible or true | — |
extraction | Read a value from the screen into a variable | variable_name |
manual | Framework-level action (wait, navigate, set_location, …) | step_type + step-specific |
if | Conditional branch | condition, then, optional else |
while | Loop while condition holds | condition, body |
code_execution | Run a registered script | script, optional variable_name and timeout_seconds |
module_import | Insert a reusable module's blocks | module_id |
Action and validation blocks use step_description; code-execution blocks can
use script without a description.
Code-execution timeout_seconds accepts an integer from 1 to 300 and defaults to
60 when omitted. The deadline starts when the script is submitted. YAML/API
configuration is preserved by visual-editor saves. You can also set Timeout
on a script step in the test or reusable module editor; clearing the field
restores the 60-second default. The limit applies to that step, not every use of
the saved script. Only successful stdout is captured into variable_name. Reports display
stdout followed by stderr in one output block, without preserving interleaving.
Failed, timed-out, and empty output leaves an existing
variable unchanged. A worker-enforced timeout reports exit code 124.
Output retrieval errors preserve the script's exit code. If stdout cannot be
retrieved for a step with variable_name, the step fails and leaves the variable
unchanged, even if the script exited successfully. Without an output variable,
a stdout retrieval error is a warning. Stderr retrieval errors are diagnostic
and do not fail the step or discard captured stdout.
The report's script details show the timeout recorded for that execution,
separately from elapsed duration. Editing the test later does not change it.
Reports without a captured limit show Timeout: not recorded. CLI report JSON
and report APIs expose it as steps[].type_data.timeout_seconds; CLI run-summary
JSON uses steps[].timeout_seconds and omits it when unavailable.
With fail_fast enabled, a failed code step stops the test before the next step.
Disabling it allows continuation, but the test still fails. Transient provisioning
failures may receive up to three creation attempts. Once submitted, customer code
is never automatically resubmitted. Setup failures report that the script did not
run.
Manual Steps
The manual block runs framework-level actions. The step_type field picks which one:
# Wait for N seconds
- type: manual
step_type: wait
step_description: "3"
# Open a specific app (usually unnecessary — the app under test opens automatically)
- type: manual
step_type: open_app
step_description: "com.apple.mobilesafari"
# Kill the app under test
- type: manual
step_type: kill_app
# Go to the home screen
- type: manual
step_type: go_home
# Navigate to a URL or deep link
- type: manual
step_type: navigate
step_description: "myapp://settings"
# Set device GPS location
- type: manual
step_type: set_location
step_description: "37.7749,-122.4194" # latitude,longitude
# Set device appearance
- type: manual
step_type: set_appearance
step_description: "dark" # "light" or "dark"
# Download a file to the device
- type: manual
step_type: download_file
step_description: "https://example.com/cert.pem"Control Flow
# If / Else
- type: if
condition: "A login prompt is visible"
then:
- type: instructions
step_description: "Tap 'Sign In'"
else:
- type: instructions
step_description: "Tap 'Continue'"
# While loop
- type: while
condition: "A 'Load More' button is visible"
body:
- type: instructions
step_description: "Tap 'Load More'"
- type: manual
step_type: wait
step_description: "2"Modules
Reusable groups of blocks live in modules. Import one by UUID:
- type: module_import
step_description: "Login Flow" # readable label
module_id: "<module-id>"Use revyl module list (CLI) to find module IDs, or revyl module insert <name> to print a ready-to-paste snippet.
Variables
Variables use Mustache-style {{name}} syntax. They can be defined up front, extracted at runtime, or pulled from org-level globals via {{global.name}}.
test:
metadata:
name: login-flow
platform: ios
variables:
email: test@example.com
password: TestPass123!
build:
name: my-ios-app
blocks:
- type: instructions
step_description: 'Type "{{email}}" in the email field'
- type: instructions
step_description: 'Type "{{password}}" in the password field'Variable names allow letters, numbers, hyphens, and underscores. They must not start or end with a hyphen or underscore.
Validation
The CLI validates YAML before it ever runs:
revyl test validate my-test.yamlThat same validator runs automatically during revyl test create --from-file and revyl test push. It checks: required fields, valid block types, valid manual step_type values, platform values, and variable definitions before use.
Authoring tests for AI agents? revyl schema --format llm prints the full
schema with best practices and step-generation guidance; MCP's get_schema
tool returns the same schema in the core and full profiles.