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.
Selectors
Section titled “Selectors”ProbeScript supports multiple strategies for identifying widgets:
| Selector | Syntax | Example |
|---|---|---|
| Text match | "text" | tap "Submit" |
| Widget key | #keyName | tap #loginButton |
| Widget type | <TypeName> | tap <ElevatedButton> |
| Ordinal | 1st "Item", 2nd "Item" | tap 2nd "Add" |
| Positional | "text" in "Container" | tap "Edit" in "Settings" |
Text Input
Section titled “Text Input”type "hello@world.com" into "Email"type "secret123" into the "Password" fieldAssertions
Section titled “Assertions”see "Dashboard" # text is visibledon't see "Error" # text is NOT visiblesee 3 "Item" # exactly 3 matchessee "Submit" is enabled # widget statesee "Terms" is checked # checkbox statesee "Price" contains "$9.99" # partial text matchGestures
Section titled “Gestures”tap "Button"double tap "Image"long press "Item"swipe leftswipe up on "Card"scroll downscroll up on "ListView"drag "Item A" to "Item B"Wait Commands
Section titled “Wait Commands”wait 5 secondswait until "Dashboard" appearswait until "Loading" disappearswait for the page to loadwait for network idleConditionals
Section titled “Conditionals”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 secondDart Escape Hatch
Section titled “Dart Escape Hatch”For anything ProbeScript doesn’t cover natively, use a dart: block:
dart: final prefs = await SharedPreferences.getInstance(); await prefs.clear();HTTP Mocking
Section titled “HTTP Mocking”when the app calls POST "/api/auth/login" respond with 503 and body "{ \"error\": \"Service Unavailable\" }"Utility Commands
Section titled “Utility Commands”take screenshot "checkout_page" # save PNG to screenshots foldercompare screenshot "baseline" # compare against visual regression baselinedump tree # dump widget tree for debuggingsave logs # save app logsgo back # device back buttonrotate landscape # rotate deviceshake # simulate device shake gesturelog "checkpoint reached" # print to test outputpause # 1-second pauseApp Lifecycle
Section titled “App Lifecycle”clear app data # wipe data and relaunchrestart 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 reconnectClipboard
Section titled “Clipboard”copy "user@example.com" to clipboardpaste from clipboard # stores result in <clipboard> variabletype "<clipboard>" into "Email" # use the pasted valueDevice Location
Section titled “Device Location”set location 37.7749, -122.4194 # set GPS coordinates (lat, lng)External Browser
Section titled “External Browser”verify external browser opened # assert url_launcher was calledHTTP Calls
Section titled “HTTP Calls”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
Data Generators
Section titled “Data Generators”Generate random data for form-heavy tests:
type "<random.email>" into "Email" # e.g., user_x7k2m@test.probetype "<random.name>" into "Name" # e.g., Alice Johnsontype "<random.phone>" into "Phone" # e.g., +1-555-042-7831type "<random.uuid>" into "Reference" # UUID v4type "<random.number(1,100)>" into "Age" # random int in rangetype "<random.text(8)>" into "Code" # random alphanumeric stringPermissions
Section titled “Permissions”allow permission "notifications"deny permission "camera"grant all permissionsrevoke all permissionsSee App Lifecycle for details on how these work across platforms.
Conditional Actions
Section titled “Conditional Actions”Skip an action silently when the target widget is not found:
tap "Aceptar" if visible # tap only if present, skip otherwisetap "Cerrar" if visible # useful for dismissing optional dialogsclear "Search" if visible # clear field only if it existstype "text" into "Field" if visiblelong press "Item" if visibledouble tap "Element" if visibleThe 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.