Advanced
Master scripting, HTTP requests, coordinate operations, and other advanced features.
JavaScript Scripting: evalScript / runScript / shell
Execute JavaScript code within your flow to implement complex logic.
evalScript: Execute Expressions Directly
# Increment counter by 1
- evalScript: "${counter = counter + 1}"
# Concatenate strings
- evalScript: "${fullName = firstName + ' ' + lastName}"
# Define an array
- evalScript: "${items = ['apple', 'banana', 'orange']}"
# Randomly select an array element
- evalScript: "${selected = items[Math.floor(Math.random() * items.length)]}"
# Complex calculations
- evalScript: "${score = Math.floor(Math.random() * 100) + 1}"runScript: Script with Variable Injection
- runScript:
script: "result = a + b"
env:
a: "10"
b: "20"
# After execution, result = 30shell: Shorthand for evalScript
shell is functionally identical to evalScript, just shorter to write (note: this is not an OS command line -- it executes JavaScript):
- shell: "counter = counter + 1"Differences Between evalScript, runScript, and shell
| evalScript | runScript | shell | |
|---|---|---|---|
| Syntax | evalScript: "${expression}" | runScript: { script: "...", env: {...} } | shell: "script" |
| Variable injection | Not supported | Supports env injection | Not supported |
| Use case | Simple one-line expressions | Scripts needing external variable injection | Shorthand for evalScript |
Built-in Available Objects
Standard JavaScript objects available in scripts:
# Math functions
- evalScript: "${x = Math.floor(Math.random() * 100)}"
- evalScript: "${y = Math.max(10, 20)}"
- evalScript: "${z = Math.pow(2, 8)}"
# String operations
- evalScript: "${upper = 'hello'.toUpperCase()}"
- evalScript: "${part = 'hello world'.substring(0, 5)}"
- evalScript: "${arr = 'a,b,c'.split(',')}"
# JSON operations
- evalScript: "${obj = JSON.parse('{\"name\":\"test\"}')}"
- evalScript: "${str = JSON.stringify(obj)}"
# Type checking
- evalScript: "${type = typeof counter}"assertTrue: Verify Script Conditions
- assertTrue: "${counter > 0}"
- assertTrue: "${status == 'success'}"Clipboard Operations
Note: The clipboard here is the engine's built-in clipboard, not the Android system clipboard.
Copy Text from an Element: copyTextFrom
- copyTextFrom:
text: "Username" # Copy the text content of the element displaying "Username"After copying, the text is stored in the copiedText variable and can be referenced via ${copiedText}.
Paste: pasteText
- pasteText # Input the previously copied textManually Set Clipboard: setClipboard
- setClipboard: "Custom content"
- pasteText # Inputs "Custom content"Complete Example: Copy and Use
- copyTextFrom: { id: "verification_code" }
- tapOn: "Verification code input"
- pasteTextRandom Input
Generate random data and input it -- useful for testing scenarios.
Random Text
- inputRandomText # 8 random letters (default)
- inputRandomText:
length: 16 # 16 random lettersRandom Number
- inputRandomNumber:
length: 6 # 6 random digits, e.g., "483729"Random Email
- inputRandomEmail # Generates an email like john.doe@example.comRandom Person Name
- inputRandomPersonName # Generates a random person nameHTTP Requests: httpRequest
Make HTTP requests within your flow to fetch external data.
All Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url | String | Required | -- | Request URL |
method | String | Optional | GET | HTTP method (GET, POST, etc.) |
headers | Object | Optional | -- | Request header key-value pairs |
body | Object | Optional | -- | Request body |
outputVariable | String | Optional | -- | Variable name to store the response |
jsonPath | String | Optional | -- | Path to extract from JSON response |
retry.times | Number | Optional | 1 | Maximum retry count |
retry.interval | Number or Array | Optional | 3000 | Retry interval (ms), supports [min, max] for random |
GET Request
- httpRequest:
url: "https://api.example.com/sms?phone=${PHONE}"
outputVariable: code # Store the result in the code variable
jsonPath: "data.code" # Extract the data.code field from JSONPOST Request
- httpRequest:
url: "https://api.example.com/login"
method: POST
headers:
Content-Type: "application/json"
Authorization: "Bearer ${TOKEN}"
body:
phone: "${PHONE}"
password: "${PASSWORD}"
outputVariable: result
jsonPath: "data.token"Request with Retry
Some APIs may not return results immediately (e.g., waiting for an SMS verification code); you can enable automatic retries:
- httpRequest:
url: "https://api.example.com/sms/latest?phone=${PHONE}"
outputVariable: smsCode
jsonPath: "data.code"
retry:
times: 10 # Retry up to 10 times (default: 1)
interval: [3000, 5000] # 3~5 second interval between retries (default: 3000 ms)Retry trigger condition: retries automatically when the jsonPath extraction result is empty or null.
Complete Example: Auto-fetch Verification Code
# 1. Request to send a verification code
- httpRequest:
url: "https://api.example.com/sms/send"
method: POST
body:
phone: "${PHONE}"
# 2. Wait a few seconds then query for the code
- sleep: 3000
# 3. Fetch the verification code (with retry)
- httpRequest:
url: "https://api.example.com/sms/latest?phone=${PHONE}"
outputVariable: code
jsonPath: "data.code"
retry:
times: 5
interval: [2000, 4000]
# 4. Enter the verification code
- inputText: "${code}"Coordinate Taps
When elements cannot be located by text or ID, you can tap directly using coordinates.
Absolute Coordinates (Pixels)
- tapOn:
point: "360,800" # Tap at x=360, y=800Percentage Coordinates (Screen Ratio)
- tapOn:
point: "50%,80%" # Tap at 50% of screen width, 80% of screen heightThe advantage of percentage coordinates: they are independent of screen resolution and behave consistently across different devices.
Relative Coordinates Within an Element
Tap at a specific position within the element's bounds:
# Tap the slider at 25% position (toward the left)
- tapOn:
text: "Volume"
point: "25%,50%" # 25% of element width, 50% of element heightCoordinate Swipe
- swipe:
start: "100,800"
end: "100,200" # Swipe from (100,800) to (100,200)
duration: 300
# Percentage coordinates
- swipe:
start: "50%,80%"
end: "50%,20%"
duration: 400Scrolling Within a Container
Scroll to find elements within a specific container, with the swipe range confined to the container's boundaries.
- scrollUntilVisible:
element:
text: "Target Product"
from: # Scroll within this container
id: "product_list"
direction: UP
timeout: "20000"Retry: retry
Automatically retry when a group of commands may occasionally fail.
- retry:
maxRetries: "2" # Retry up to 2 times after failure (3 attempts total)
commands:
- tapOn: "Submit"
- assertVisible: "Submission successful"If tapOn or assertVisible fails, execution restarts from tapOn, with up to 2 more retries.
When maxRetries is not specified, the default is 1 (1 retry after failure, 2 total attempts).
Limits:
maxRetrieshas a maximum value of 3.fileandcommandscannot be used together.
Device Control
Set GPS Location
- setLocation:
latitude: "39.9042" # Latitude (Beijing)
longitude: "116.4074" # LongitudeAirplane Mode
- setAirplaneMode:
enabled: true # Enable airplane mode
- setAirplaneMode:
enabled: false # Disable airplane mode
# Short form
- setAirplaneMode: true # Enable
- setAirplaneMode: false # DisableScreenshot: takeScreenshot
- takeScreenshot: /tmp/screen.pngSaves a screenshot of the current screen to the specified path.
Wait for Animation to End
Wait for on-screen animations or transition effects to complete.
- waitForAnimationToEnd # Default max wait: 15 seconds
- waitForAnimationToEnd:
timeout: 5000 # Custom timeout: 5 secondsOpen Link: openLink
Open a web page via URL, or jump directly to a specific page within an App using a deep link.
# Open a web page
- openLink: "https://example.com"
# Open an App deep link (jump directly to a specific page within the App)
- openLink:
link: "myapp://settings/profile"
autoVerify: true
browser: false # Don't open in a browserPermission Settings: setPermissions
Set App permissions in bulk.
- setPermissions:
appId: com.example.app
permissions:
android.permission.CAMERA: allow
android.permission.LOCATION: allow
android.permission.MICROPHONE: denyCan also be set during launchApp:
- launchApp:
appId: com.example.app
permissions:
android.permission.CAMERA: allowAuto-execute on Flow Start/Complete: onFlowStart / onFlowComplete
Configure commands to execute automatically before the flow starts and after it ends.
appId: com.example.app
onFlowStart:
- launchApp # Automatically launch the App before the flow starts
onFlowComplete:
- stopApp # Automatically stop the App after the flow ends (regardless of success or failure)
---
- tapOn: "Login"
- inputText: "${PASSWORD}"| Config | Execution Timing | Notes |
|---|---|---|
onFlowStart | Before command section executes | Used for initialization |
onFlowComplete | After command section executes | Executes regardless of success or failure, used for cleanup |
Advanced Practice: Automated Testing — E-commerce App Browse & Order
appId: com.example.shop
name: E-commerce App Automated Testing
---
# Pre-define test search keywords
- evalScript: "keywords = ['phone case', 'USB cable', 'earbuds', 'power bank', 'keyboard']"
- launchApp
- assertVisible: "Home"
# ── Phase 1: Browse product list ──
- repeat:
duration: "30000"
commands:
- swipe:
direction: UP
duration: [200, 500]
waitToSettleTimeoutMs: 0
- sleep: [2000, 4000]
# ── Phase 2: Search and view product details ──
- tapOn: "Search"
- inputText: "${keywords[Math.floor(Math.random() * keywords.length)]}"
- pressKey: Enter
- sleep: 2000
- repeat:
times: "3"
commands:
- swipe:
direction: UP
duration: [200, 500]
waitToSettleTimeoutMs: 0
- sleep: [2000, 4000]
# 50% chance of viewing details
- tapOn:
text: "View Details"
optional: true
chance: 0.5
- sleep: [1000, 3000]
# Go back to list
- back
# ── Phase 3: Simulate order flow ──
- runFlow:
commands:
- tapOn:
text: "Add to Cart"
optional: true
- tapOn:
text: "Cart"
optional: true
- assertVisible:
text: "Checkout"
optional: true
- takeScreenshot: /tmp/cart_result.png
- stopAppNeed a quick command lookup? → Command Cheat Sheet
