Skip to content

ProbeScript Syntax

ProbeScript is a natural language test syntax with indent-based blocks (like Python). Test files use the .probe extension.

Every test starts with test followed by a quoted name, with steps indented below:

test "user sees welcome screen"
open the app
wait 3 seconds
see "Welcome"
don't see "Error"

Add tags with @ after the test declaration:

test "critical login flow"
@smoke @critical
open the app
tap "Sign In"
see "Dashboard"

Run tagged tests with probe test tests/ --tag smoke.

ProbeScript supports multiple strategies for identifying widgets:

SelectorSyntaxExample
Text match"text"tap "Submit"
Widget key#keyNametap #loginButton
Widget type<TypeName>tap <ElevatedButton>
Ordinal1st "Item", 2nd "Item"tap 2nd "Add"
Positional"text" in "Container"tap "Edit" in "Settings"
type "hello@world.com" into "Email"
type "secret123" into the "Password" field
see "Dashboard" # text is visible
don't see "Error" # text is NOT visible
see 3 "Item" # exactly 3 matches
see "Submit" is enabled # widget state
see "Terms" is checked # checkbox state
see "Price" contains "$9.99" # partial text match
tap "Button"
double tap "Image"
long press "Item"
swipe left
swipe up on "Card"
scroll down
scroll up on "ListView"
drag "Item A" to "Item B"
wait 5 seconds
wait until "Dashboard" appears
wait until "Loading" disappears
wait for the page to load
wait for network idle
if "Accept Cookies" appears
tap "Accept Cookies"

With an else branch:

if "Welcome Back" appears
tap "Continue"
else
tap "Sign In"
repeat 3 times
swipe left
wait 1 second

For anything ProbeScript doesn’t cover natively, use a dart: block:

dart:
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
when the app calls POST "/api/auth/login"
respond with 503 and body "{ \"error\": \"Service Unavailable\" }"
take screenshot "checkout_page" # save PNG to screenshots folder
compare screenshot "baseline" # compare against visual regression baseline
dump tree # dump widget tree for debugging
save logs # save app logs
go back # device back button
rotate landscape # rotate device
shake # simulate device shake gesture
log "checkpoint reached" # print to test output
pause # 1-second pause
clear app data # wipe data and relaunch
restart the app # force-stop and relaunch (preserves data)
kill the app # force-stop only (no relaunch)
open the app # launch the app (CLI-side) and reconnect
copy "user@example.com" to clipboard
paste from clipboard # stores result in <clipboard> variable
type "<clipboard>" into "Email" # use the pasted value
set location 37.7749, -122.4194 # set GPS coordinates (lat, lng)
verify external browser opened # assert url_launcher was called

Make real HTTP requests to APIs (runs on the CLI, not the device):

call GET "https://api.example.com/health"
call POST "https://api.example.com/seed" with body "{\"env\":\"test\"}"
call PUT "https://api.example.com/users/1" with body "{\"name\":\"updated\"}"
call DELETE "https://api.example.com/sessions"

Responses are stored in variables:

  • <response.status> — HTTP status code (e.g., 200)
  • <response.body> — response body as a string

Generate random data for form-heavy tests:

type "<random.email>" into "Email" # e.g., user_x7k2m@test.probe
type "<random.name>" into "Name" # e.g., Alice Johnson
type "<random.phone>" into "Phone" # e.g., +1-555-042-7831
type "<random.uuid>" into "Reference" # UUID v4
type "<random.number(1,100)>" into "Age" # random int in range
type "<random.text(8)>" into "Code" # random alphanumeric string
allow permission "notifications"
deny permission "camera"
grant all permissions
revoke all permissions

See App Lifecycle for details on how these work across platforms.

Skip an action silently when the target widget is not found:

tap "Aceptar" if visible # tap only if present, skip otherwise
tap "Cerrar" if visible # useful for dismissing optional dialogs
clear "Search" if visible # clear field only if it exists
type "text" into "Field" if visible
long press "Item" if visible
double tap "Element" if visible

The if visible suffix works with tap, type, clear, long press, and double tap. If the widget is not found, the step is silently skipped (no error). Connection errors are still propagated.