Skip to main content

The command line

mobium is one binary, and every command is one call to one of Mobium's tools — the same tools an MCP client and the five language clients call, so the command line cannot do something they cannot. This guide is how the commands fit together: the loop, sessions and the daemon, the flags every command takes, reading answers from a script, and exit statuses.

Every command and every line of output below is what mobium printed on 2026-09-28 against MobiumApp on an Android 15 emulator. Where output is trimmed or a path shortened, it says so. The full list of commands and what each takes is generated: API.md and FLAGS.md.

1. Which devices​

$ mobium --version
mobium version 0.1.0-dev

$ mobium devices
emulator-5554 device (android emulator, model: sdk_gphone64_arm64)

(Trimmed: the Mac also listed its iOS simulators, shut down, and a phone.) To start one, mobium boot <avd | simulator> boots an Android emulator by its AVD's name, or an iOS simulator by its name or UDID, and answers once it has booted. With one device running, no command needs to be told which; with several, --device <serial or udid> picks one, on every command.

2. A session​

A session is a device made ready — its automation server started — with, optionally, an app launched fresh:

$ mobium session start --platform android --app dev.mobium.mobiumapp
waiting for the UiAutomator2 server to start...
session started on emulator-5554 (android, uiautomator2); dev.mobium.mobiumapp was launched fresh and is in the foreground

$ mobium session status
open: emulator-5554 (android, uiautomator2)

Every command after it uses that session. session end closes it, and puts back anything it changed for the session — network conditions, accessibility settings — and stops the app it launched:

$ mobium session end
session ended on emulator-5554; anything it changed for the session is put back; dev.mobium.mobiumapp, which the session launched, was stopped

A session is not required: any command opens one on first use. Starting one says which device and app a run is about, and ending it is how a run cleans up after itself.

3. The loop: map, act, map​

map lists what on the screen can be acted on, each with a ref:

$ mobium map
@e1 homeList (list)
@e2 WebViews (button)
@e3 Login Demo (button)
@e4 OTP Demo (button)
@e5 Location Demo (button)
@e6 Pager Demo (button)
@e7 Interruption Demo (button)
@e8 Form Demo (button)
@e9 Gestures (button)
@e10 Motion Demo (button)
@e11 Crash Demo (button)
@e12 Storage Demo (button)
@e13 Dialog Demo (button)

Act on one, and map again — the screen has changed, and refs from before belong to the screen that is gone:

$ mobium tap label=Login Demo
tapped label=Login Demo at (540, 814)

$ mobium map
@e1 ScrollView (list)
@e2 Back (button)
@e3 username (input)
@e4 password (password)
@e5 Log In (button)
Before: map lists the buttonsAfter the tap: the fields, and password marked as one
MobiumApp&#39;s home screen, a column of demo buttonsThe Login Demo: username and password fields, and Log In

A password field is labeled by its id and given the role password; what is typed into one is never printed, by map or any other command.

map --diff answers only what changed since the last map of the device — + appeared, - went away, ~ changed label, checked state or place — which is what the action just did; its refs are the new map's either way.

4. Locators and refs​

Anything that takes a target takes either:

  • a ref from the last map — @e3 — short, and valid until the screen changes; or
  • a locator — testid=username, label=Login Demo, text=Sign in, role=button — which survives across screens and runs, and is what a script or a test should use. testid= is the one that survives a translation and a redesign.

A locator that matches more than one element is refused, never guessed at; append a role — label=Apps,role=button — or use a ref. Every action waits for its target to be ready before it touches it — auto-wait is the whole story.

5. Answers for a script: --json​

Every command's answer has a text half, for a person, and a structured half, which --json prints instead:

$ mobium fill testid=username mobium
typed "mobium" into testid=username

$ mobium --json find testid=username
{
"context": "NATIVE_APP",
"device": "emulator-5554",
"elements": [
{
"bounds": {
"x1": 42,
"x2": 1038,
"y1": 1027,
"y2": 1153
},
"label": "mobium",
"locator": {
"exact": true,
"kind": "testid",
"value": "username"
},
"ref": "@e3",
"role": "input"
}
]
}

Bounds are device pixels. Read the structured half from code, never the text, which is written for a person and may change.

6. When a command fails​

A failure says what happened and what to do about it, and exits with a status that says what kind of failure it was:

$ mobium tap testid=noSuchButton
error: no element matches testid=noSuchButton on the current screen — the screen may have changed, run app_map again

With --json, the same failure as data — its code, its remedy, whether retrying could help:

$ mobium --json tap testid=noSuchButton; echo $?
{
"error": "no element matches testid=noSuchButton on the current screen — the screen may have changed, run app_map again",
"code": "no_such_element",
"message": "no element matches testid=noSuchButton on the current screen — the screen may have changed, run app_map again",
"remedy": "run app_map again, or app_scroll_to if it may be off screen",
"retryable": false,
"details": {
"locator": "testid=noSuchButton"
}
}
4
Exit statusCodesMeaning
0—it did what was asked, and checked
2invalid_argumentthe command was wrong — nothing on any device could make it work
3no_device, device_not_ready, toolchain_missingno device, or one not ready: locked, a dialog up, a tool missing
4no_such_element, ambiguous_locator, element_not_reachable, no_such_context, no_such_alertthe target was not there, was two things, or could not be reached
5unsupportedthis device or driver cannot do it — retrying will not help
6timeoutit did not happen in time — the one kind worth retrying as it is
7not_confirmedthe command ran and reading back says it did not take — a finding
1device_server, internal, errorthe device side failed, Mobium has a bug, or the failure is unclassified — read the message

The codes are the same in every client, as exceptions, and on the MCP wire, in structuredContent. Adding a code is safe; renaming or removing one would break every client that catches it, so they do not change:

CodeMeans
no_devicenothing to drive
device_not_readythere, and not drivable yet: locked, not trusted, Developer Mode off
toolchain_missingmissing on this machine: adb, Xcode, a signing certificate, a device agent that would not download or build
no_such_elementa locator or ref matched nothing on screen; worth scrolling for
ambiguous_locatormatched more than one; narrow it — Mobium never guesses
element_not_reachablefound, and not touchable where it is
no_such_contexta WebView context that is not there
no_such_alerta dialog was expected and none is up
unsupportedthis backend or platform cannot, and says why
timeouta wait ran out
not_confirmedthe command said it worked and reading the state back disagreed
invalid_argumentthe request itself is wrong, or the command line was refused
device_serverthe device side failed — a device server, or adb, simctl, devicectl, lockdown — in a way no narrower code names; a server's own W3C code is in details.w3c
internala bug in Mobium
errorunclassified: a daemon too old to send a code, or a code a client does not know

Decide by the code, never by matching the message's words: the wording can improve, the code is the contract.

7. Several steps in one call: batch​

A known sequence can go as one call — from a file, or - for stdin. Every step is checked before the first runs, and it stops at the first failure, with that step's own error:

[
{"name": "app_tap", "arguments": {"target": "label=Login Demo"}},
{"name": "app_fill", "arguments": {"target": "testid=username", "text": "mobium"}},
{"name": "app_fill", "arguments": {"target": "testid=password", "text": "hunter2"}},
{"name": "app_tap", "arguments": {"target": "testid=loginBtn"}},
{"name": "app_wait_for", "arguments": {"target": "testid=welcomeText"}}
]
$ mobium batch steps.json
1. app_tap: tapped label=Login Demo at (540, 814)
2. app_fill: typed "mobium" into testid=username
3. app_fill: typed 7 characters into testid=password, a password field — not echoed
4. app_tap: tapped testid=loginBtn at (540, 1457)
5. app_wait_for: testid=welcomeText is visible after 1.622s

The Login Demo after the batch: &quot;Welcome, mobium! You are logged in.&quot;

The same steps, with assertions and a report, are a test: mobium test.

8. The daemon, and running several at once​

The first command starts a daemon in the background, which holds the device sessions and the refs between commands, and exits after 30 idle minutes:

$ mobium daemon status
Daemon running (pid 4156, up 8s)
version 0.1.0-dev
socket ~/.mobium/daemon/mobium.sock

(The home directory is shortened to ~ here and below.) One daemon serves one call at a time. Two runs at once — two terminals, two CI jobs, two devices — should each have their own, named by MOBIUM_SESSION:

$ MOBIUM_SESSION=ci-android mobium current
waiting for the UiAutomator2 server to start...
dev.mobium.mobiumapp

$ MOBIUM_SESSION=ci-android mobium daemon status
Daemon running (pid 4384, up 1s)
version 0.1.0-dev
socket ~/.mobium/daemon/mobium-ci-android.sock
session ci-android

mobium daemon stop stops one, ending its sessions — stop it before shutting a device down, never after (SHUTDOWN.md). mobium shutdown <serial | avd | udid | simulator> ends this daemon's session on the device, then shuts the emulator or simulator down and returns once it is gone; a real phone is refused. It ends only its own daemon's session, so stop any other daemon on the device first. Keep session names short: a name is part of a socket path, which the OS caps at about 104 bytes.

The flags every command takes​

FlagDoes
--device <serial or udid>which device, when more than one is running
--driver <name>uiautomator2 (Android's default), uiautomator (Android, installs nothing on the device), wda (iOS simulators and iPhones), or a third-party driver
--jsonthe structured answer instead of the text
--remote <[user@]host>drive another machine's devices, over SSH — the grid guide
-v, --verbosewhat mobium is doing, on stderr

mobium <command> --help has each command's own flags and examples, and mobium doctor checks the setup — the Android and iOS tools, the devices, the daemon — and names the fix for anything wrong.