Skip to main content

The Java client

It speaks to the same tool layer as the CLI and the MCP server, by spawning mobium pipe, so the mobium binary has to be on PATH or named by MOBIUM_BIN_PATH. The quick start goes from nothing to a running script.

Install​

git clone https://github.com/mobiumdev/mobium.git
cd mobium/clients/java && ./mvnw -q install -DskipTests

Connect​

try (Mobium device = Mobium.builder().platform("android").app("com.android.settings").start()) {
for (Element el : device.map()) System.out.println(el.ref() + " " + el.label());
}

device below is a dev.mobium.Mobium from Mobium.builder()...start() or Mobium.connect().

Every tool, by method​

MethodTool
accessibility, setAccessibilityapp_accessibility
alert, answerAlertapp_alert
appearanceapp_appearance
auditapp_audit
backgroundapp_background
batchapp_batch
batteryapp_battery
biometricapp_biometric
bootapp_boot
incomingCallapp_call
checkapp_check
clearDataapp_clear_data
clipboard, setClipboardapp_clipboard
contextapp_context
contextsapp_contexts
clearCookies, cookies, setCookiesapp_cookies
crash, crashesapp_crashes
currentapp_current
devicesapp_devices
addDialogRule, clearDialogRules, dialogRulesapp_dialogs
doctorapp_doctor
download, downloads, pullPathapp_download
dragapp_drag
evalapp_eval
fillapp_fill
findapp_find
grantapp_grant
hitTestapp_hit_test
hookapp_hook
installapp_install
keyboardapp_keyboard
launch, launchWithGrayBox, launchWithHitTestapp_launch
appsapp_list_apps
appLocaleapp_locale
clearLocation, followGpx, followRoute, location, setLocationapp_location
screenLockedapp_lock
deviceLogs, logsapp_logs
longPressapp_long_press
map, mapDiffapp_map
network, resetNetwork, setOffline, shapeNetworkapp_network
notifications, postNotification, shadeapp_notifications
openUrlapp_open_url
orientation, orientationLockedapp_orientation
back, pressapp_press
pressDragapp_press_drag
pressTapapp_press_tap
recordapp_record
resetPermissionsapp_reset_permissions
revokeapp_revoke
rotateapp_rotate
screenapp_screen
screenshotapp_screenshot
scrollToapp_scroll_to
close, quit, sessionsapp_session
shakeapp_shake
shutdownapp_shutdown
smsapp_sms
sourceapp_source
appStateapp_state
clearStorage, setStorage, storageapp_storage
swipeapp_swipe
doubleTap, tap, tapFingersapp_tap
terminateapp_terminate
textapp_text
deviceTimeapp_time
timezoneapp_timezone
trace, traceStart, traceStopapp_trace
typeapp_type
uninstallapp_uninstall
pushPath, uploadapp_upload
waitForapp_wait_for
zoomapp_zoom

Connecting, ending, and errors​

builder​

public static Builder builder()

Configures a connection or a session.

@return a builder, which connects or starts when asked

Configure, then start or connect

try (Mobium phone = Mobium.builder().platform("android").app("com.example.shop").start()) {
phone.tap("text=Sign in");
}

start​

public Mobium start()

Connects and opens the session on the device: the device-side server is started now and, given an app, it is launched and in front when this returns.

Nothing requires it -- every call opens a session on first use -- but it puts the slow first start (installing UiAutomator2, building WebDriverAgent on an iPhone) where it was asked for. End it with Mobium#quit(), or in try-with-resources, which quits:

try (Mobium device = Mobium.builder().platform("android").app("com.android.settings").start()) {
device.map();
}
}```

@return a device with its session open

**Open a session and launch the app fresh**

```java
try (Mobium phone = Mobium.builder().platform("ios").app("com.example.shop").start()) {
Session s = phone.session();
System.out.println(s.device() + " " + s.driver());
} // try-with-resources quits: the session ends here

connect​

public static Mobium connect()
public Mobium connect()

Connects to mobium, for whichever device is running. It does not touch the device: the session there opens on the first call that needs it, or with Builder#start().

@return a connection; close it when done

Connect without touching the device

try (Mobium phone = Mobium.connect()) {
System.out.println(phone.current());
} // close() leaves the session open for whoever started it

Connect with options

try (Mobium phone = Mobium.builder().device("emulator-5554").connect()) {
phone.map();
}

platform​

public Builder platform(String name)

The platform for start(): "android" or "ios". "ios" picks the wda driver, so none need be named.

@param name "android" or "ios" @return this builder

Pick iOS when both are attached

try (Mobium phone = Mobium.builder().platform("ios").start()) {
phone.launch("com.apple.Preferences");
}

app​

public Builder app(String id)

An app for start() to launch once the session is up.

@param id a package name (Android) or bundle id (iOS) @return this builder

Launch an app as the session starts

try (Mobium phone = Mobium.builder().app("com.android.settings").start()) {
phone.waitFor("text=Network & internet");
}

binary​

public Builder binary(String path)

Pins the mobium executable, ahead of MOBIUM_BIN_PATH and PATH.

@param path the mobium executable @return this builder

Use a mobium binary that is not on PATH

try (Mobium phone = Mobium.builder().binary("/opt/mobium/bin/mobium").connect()) {
System.out.println(phone.doctor());
}

device​

public Builder device(String serial)

Targets one device by serial or UDID. Omit when only one is running.

@param serial a serial (Android) or UDID (iOS) @return this builder

Target one of several devices

try (Mobium phone = Mobium.builder().device("emulator-5556").start()) {
phone.press("home");
}

driver​

public Builder driver(String name)

Chooses the driver: uiautomator2 (default on Android), uiautomator (installs nothing, slower, cannot type) or wda (iOS simulators and iPhones).

@param name "uiautomator2", "uiautomator" or "wda" @return this builder

Use the zero-install Android driver

try (Mobium phone = Mobium.builder().driver("uiautomator").connect()) {
System.out.println(phone.text());
}

callTimeout​

public Builder callTimeout(Duration timeout)

The longest any one call may take before the connection is given up, the handshake included. Unlimited by default, because the first session on an iPhone builds WebDriverAgent and that takes minutes.

A call that runs out ends the connection: there is one pipe, replies are told apart only by id, and the late answer to an abandoned call would be read as the answer to the next one. Every call after throws, saying so; connect again. The device's session lives in the daemon, so reconnecting is cheap. Set it well above the longest WaitFor#timeout you use.

@param timeout how long, positive @return this builder

Bound every call

try (Mobium phone = Mobium.builder().callTimeout(Duration.ofMinutes(2)).connect()) {
phone.map(); // a call past two minutes ends the connection
}

session​

public Builder session(String name)
public Session session()

A daemon of this connection's own, by name, as MOBIUM_SESSION sets one. One daemon serves one call at a time across every device, so parallel runs on different devices should each name one: sharing, an emulator's calls waited behind a simulator's, 22.3s against 0.3s. Keep it short; it is part of a socket path.

@param name the daemon's name @return this builder

Name a session of your own

try (Mobium phone = Mobium.builder().session("checkout").connect()) {
phone.map();
}

Read what start() opened

try (Mobium phone = Mobium.builder().start()) {
Session s = phone.session(); // null from connect()
System.out.println(s.platform() + " " + s.device());
}

quit​

public void quit()

Ends the session on the device and closes the connection. The teardown is the daemon's own: accessibility settings put back, a recording or route stopped, WebViews detached, the device-side server stopped, and the app start() launched, if any, stopped too. Quitting a session that is not open succeeds, and a second quit -- say, try-with-resources closing after an explicit one -- does nothing. A program that exits without quitting or closing has the sessions it started ended for it: mobium sees the client go.

End the session explicitly

Mobium phone = Mobium.builder().app("com.example.shop").start();
try {
phone.tap("text=Sign in");
} finally {
phone.quit(); // puts back what the session changed
}

close​

public void close()
public void close()

Asks mobium to exit and waits for it, killing it after ten seconds. Safe to call more than once, and from another thread while a call is waiting: that call then fails, saying the connection was closed.

Close a connection, leaving the session open

Mobium phone = Mobium.connect();
phone.tap("@e2");
phone.close(); // safe to call twice

sessions​

public List<Map<String, Object>> sessions()

The sessions open on the daemon.

@return each with device, platform and driver

List the sessions open on the daemon

for (Map<String, Object> s : device.sessions()) {
System.out.println(s.get("device") + " " + s.get("platform"));
}

call​

public Map<String, Object> call(String tool, Map<String, Object> arguments)

Runs any tool by name, for anything this class does not wrap yet, and returns its structured answer.

@param tool the tool's name, such as "app_map" @param arguments the tool's arguments, as its schema names them @return the tool's structured answer

Call any tool by name

Map<String, Object> result = device.call("app_tap", Map.of("target", "@e2", "fingers", 2));

step​

public static Map<String, Object> step(String tool, Map<String, Object> arguments)

One step for batch(List): a tool and the arguments it takes on its own.

@param tool the tool, e.g. "app_tap" @param arguments its arguments, or null for none @return the step

Build steps for a batch

var steps = List.of(
Mobium.step("app_launch", Map.of("app", "com.android.settings")),
Mobium.step("app_map", null));
device.batch(steps);

MobiumException​

public class MobiumException extends RuntimeException
public MobiumException(String message)
public MobiumException(String message, String tool)

A tool reported that it could not do what was asked, or the connection to Mobium failed.

Every tool failure carries a stable code() code, the same in every client and on the wire (the error codes in the mobium repository's docs/guides/cli.md), and each code has a subclass — NoSuchElementException, UnsupportedException and so on — so a test catches the kind it can handle and lets the rest through. remedy() says what to do about it, retryable() whether the same call can succeed if made again. A code this client does not know, a daemon too old to send one, and a failure of the connection itself are plain MobiumExceptions.

Unchecked on purpose. Every call in this client can fail — a device can be unplugged mid-flow — and forcing a try/catch around each one would make a readable test unreadable without making it safer.

Read what failed and what to do

try {
device.tap("text=Continue");
} catch (MobiumException e) {
System.err.println(e.tool() + " " + e.code() + ": " + e.remedy());
if (!e.retryable()) throw e;
}

details​

public Map<String, Object> details()

Machine-readable facts: the locator, the W3C code a device server sent.

@return the facts, never null

Find the step a batch stopped at

try {
device.batch(List.of(Mobium.step("app_tap", Map.of("target", "text=Sign in"))));
} catch (MobiumException e) {
System.err.println("stopped at step " + e.details().get("step"));
}

NoSuchElementException​

public final class NoSuchElementException extends MobiumException

A locator or ref matched nothing on screen. Worth scrolling for.

Shares its simple name with java.util.NoSuchElementException; import this one by name rather than through a wildcard.

Error code no_such_element. See MobiumException.

Scroll when a tap finds nothing

try {
device.tap("text=Continue");
} catch (dev.mobium.NoSuchElementException e) { // not java.util's
device.scrollTo("text=Continue", "down");
}

TimedOutException​

public final class TimedOutException extends MobiumException

A wait ran out. Named TimedOut so it does not collide with java.util.concurrent.TimeoutException.

Error code timeout. See MobiumException.

Handle a wait that never held

try {
device.waitFor("text=Welcome", WaitFor.visible().timeout(Duration.ofSeconds(3)));
} catch (TimedOutException e) {
System.err.println(e.getMessage()); // says what was on screen instead
}

AmbiguousLocatorException​

public final class AmbiguousLocatorException extends MobiumException

A locator matched more than one element. Narrow it; Mobium never guesses.

Error code ambiguous_locator. See MobiumException.

Narrow a locator that matched twice

try {
device.tap("text=OK");
} catch (AmbiguousLocatorException e) {
device.tap(device.find("text=OK").get(0).ref());
}

UnsupportedException​

public final class UnsupportedException extends MobiumException

This driver or platform cannot do it, and says why. Retrying cannot help.

Error code unsupported. See MobiumException.

Skip what a platform lacks

try {
device.press("back");
} catch (UnsupportedException e) { // iOS has no back button
System.err.println(e.remedy());
}

DeviceNotReadyException​

public final class DeviceNotReadyException extends MobiumException

The device is there and cannot be driven yet: locked, not trusted, Developer Mode off.

Error code device_not_ready. See MobiumException.

Unlock before launching

try {
device.launch("com.example.shop");
} catch (DeviceNotReadyException e) {
device.screenLocked(false);
device.launch("com.example.shop");
}

NoDeviceException​

public final class NoDeviceException extends MobiumException

Nothing to drive: no device matches, or none is connected.

Error code no_device. See MobiumException.

Report that nothing is attached

try {
device.map();
} catch (NoDeviceException e) {
System.err.println("start an emulator first: " + e.remedy());
}

NoSuchAlertException​

public final class NoSuchAlertException extends MobiumException

A dialog was expected and none is on screen.

Error code no_such_alert. See MobiumException.

Answer a dialog only if one is up

try {
device.answerAlert(false);
} catch (NoSuchAlertException e) {
// nothing was up
}

NoSuchContextException​

public final class NoSuchContextException extends MobiumException

A WebView context that is not there.

Error code no_such_context. See MobiumException.

Switch to a WebView that may not be there

try {
device.context("WEBVIEW_com.example.shop");
} catch (NoSuchContextException e) {
System.err.println(device.contexts());
}

ElementNotReachableException​

public final class ElementNotReachableException extends MobiumException

The element was found and cannot be touched where it is, usually off screen.

Error code element_not_reachable. See MobiumException.

Handle a target that cannot be reached

try {
device.tap("text=Delete");
} catch (ElementNotReachableException e) {
System.err.println(e.remedy());
}

NotConfirmedException​

public final class NotConfirmedException extends MobiumException

The command reported success and reading the state back disagreed.

Error code not_confirmed. See MobiumException.

Handle a change the device did not confirm

try {
device.timezone("Asia/Tokyo");
} catch (NotConfirmedException e) {
System.err.println(e.getMessage());
}

InvalidArgumentException​

public final class InvalidArgumentException extends MobiumException

The request itself is wrong.

Error code invalid_argument. See MobiumException.

A null is refused before anything is sent

try {
device.tap(null);
} catch (InvalidArgumentException e) {
System.err.println(e.getMessage()); // names the argument
}

ToolchainMissingException​

public final class ToolchainMissingException extends MobiumException

Something on this machine is missing: adb, Xcode, a signing certificate.

Error code toolchain_missing. See MobiumException.

Report a missing toolchain

try {
device.launch("com.example.shop");
} catch (ToolchainMissingException e) {
System.err.println(e.remedy());
}

DeviceServerException​

public final class DeviceServerException extends MobiumException

The device side failed (a device server, or adb, simctl, devicectl, lockdown); a server's W3C code is details().get("w3c").

Error code device_server. See MobiumException.

Retry once when the device server says so

try {
device.tap("@e2");
} catch (DeviceServerException e) {
if (e.retryable()) device.tap("@e2");
}

InternalException​

public final class InternalException extends MobiumException

A bug in Mobium.

Error code internal. See MobiumException.

Report an internal failure

try {
device.map();
} catch (InternalException e) {
System.err.println("mobium bug: " + e.getMessage());
}

visible​

public static WaitFor visible()

Wait for the element to be on screen. The default.

@return the condition

Wait for an element to appear

Element e = device.waitFor("text=Sign in", WaitFor.visible());

hidden​

public static WaitFor hidden()

Wait for it to go away — a spinner, say.

@return the condition

Wait for a spinner to go

device.waitFor("role=progressbar", WaitFor.hidden());

exactText​

public static WaitFor exactText(String expected)

Wait until its text is exactly this, where text waits for a part.

@param expected the whole text @return the condition

Wait for an exact text

device.waitFor("testid=total", WaitFor.exactText("$12.00"));

value​

public static WaitFor value(String expected)

Wait until a field holds exactly this value; "" waits for it to be empty. A password field is refused, since its value is never read.

@param expected the whole value the field must hold @return the condition

Wait for a field's value

device.waitFor("testid=email", WaitFor.value("hello@example.com"));

enabled​

public static WaitFor enabled()

Wait for a control to be enabled — a Submit the app enables once a form is valid.

@return the condition

Wait for a button to become tappable

device.waitFor("text=Continue", WaitFor.enabled());

disabled​

public static WaitFor disabled()

Wait for a control to be disabled.

@return the condition

Wait for a button to be disabled

device.waitFor("text=Submit", WaitFor.disabled());

checked​

public static WaitFor checked()

Wait for a checkbox, radio or switch to be checked.

@return the condition

Wait for a checkbox to be checked

device.waitFor("testid=termsCheck", WaitFor.checked());

unchecked​

public static WaitFor unchecked()

Wait for a checkbox, radio or switch to be unchecked.

@return the condition

Wait for a switch to be off

device.waitFor("@e4", WaitFor.unchecked());

focused​

public static WaitFor focused()

Wait for a field to have keyboard focus.

@return the condition

Wait for a field to take focus

device.waitFor("testid=search", WaitFor.focused());

count​

public static WaitFor count(int n)

Wait until the locator matches exactly this many elements on screen.

@param n how many, 0 or more @return the condition

Wait for exactly three rows

device.waitFor("role=cell", WaitFor.count(3));

not​

public WaitFor not()

The opposite of this condition: text("Sending").not() waits for the text to change.

@return a copy of this condition, negated

Wait for a text to stop matching

device.waitFor("testid=status", WaitFor.text("Loading").not());

timeout​

public WaitFor timeout(Duration d)

How long before giving up. Ten seconds by default, two minutes at most.

@param d how long, at most two minutes @return a copy of this condition with the timeout set

Wait longer than ten seconds

device.waitFor("text=Done", WaitFor.visible().timeout(Duration.ofSeconds(30)));

centerX​

public int centerX()

The x coordinate a tap targets.

@return the horizontal center

Tap the middle of an element's bounds

Bounds b = device.find("testid=map").get(0).bounds();
device.tap(b.centerX(), b.centerY());

width​

public int width()

How wide it is.

@return the width, in device pixels

Measure an element

Bounds b = device.waitFor("testid=banner").bounds();
System.out.println(b.width() + "x" + b.height() + " px");

Element​

public record Element(String ref, String label, String role, String locator, Bounds bounds, String context, Boolean checked, boolean selected, String value, boolean disabled)

One actionable thing on screen.

@param ref the @e1 handle, valid only for the screen it came from @param label what a person would call it @param role button, input, list and so on; empty if Mobium could not say @param locator how the ref resolves on a later screen, as kind=value @param bounds where it is, in device pixels @param context the WebView it came from, empty for native elements @param checked a checkbox, radio or switch's state; null for anything with no such state, which is a different answer from unchecked @param selected true for what the platform reports chosen: the current tab, the chosen segment of a segmented control @param value what a slider reads, as the app states it ("80%", "1.2"); empty for anything else @param disabled true for what the platform reports not enabled: an action on it waits for it to be enabled, and is refused if it stays disabled

Read what map hands out

Element e = device.find("text=Sign in").get(0);
System.out.println(e.ref() + " " + e.role() + " " + e.bounds() + " checked=" + e.checked());

Location​

public final class Location

Where a device believes it is.

mock and mocking answer different questions and disagree after a clear: the first says this fix was injected, the second says a test provider is installed now. Android keeps its last known position after the provider supplying it is removed, so a read straight after clearing reports an injected fix from a provider that no longer exists.

known is false when the platform cannot report a position at all, as on iOS, which is different from the device having none.

Read where the device believes it is

Location here = device.location();
if (here.mock) System.out.println(here.latitude + ", " + here.longitude);

Types​

TypeWhat it is
AmbiguousLocatorExceptionA locator matched more than one element.
AppOne installed app.
BoundsAn on-screen rectangle in device pixels, on both platforms.
DeviceInfoOne attached device or simulator.
DeviceNotReadyExceptionThe device is there and cannot be driven yet: locked, not trusted, Developer Mode off.
DeviceServerExceptionThe device side failed (a device server, or adb, simctl, devicectl, lockdown); a server's W3C code is details().get("w3c").
ElementOne actionable thing on screen.
ElementNotReachableExceptionThe element was found and cannot be touched where it is, usually off screen.
InternalExceptionA bug in Mobium.
InvalidArgumentExceptionThe request itself is wrong.
LocationWhere a device believes it is.
MobiumDrives mobile apps on Android emulators, Android phones, iOS simulators and iPhones.
BuilderOptions for Mobium#connect() and Builder#start().
MobiumExceptionA tool reported that it could not do what was asked, or the connection to Mobium failed.
NoDeviceExceptionNothing to drive: no device matches, or none is connected.
NoSuchAlertExceptionA dialog was expected and none is on screen.
NoSuchContextExceptionA WebView context that is not there.
NoSuchElementExceptionA locator or ref matched nothing on screen.
NotConfirmedExceptionThe command reported success and reading the state back disagreed.
SessionWhat Mobium.Builder#start() opened: the device and how it is driven.
TimedOutExceptionA wait ran out.
ToolchainMissingExceptionSomething on this machine is missing: adb, Xcode, a signing certificate.
UnsupportedExceptionThis driver or platform cannot do it, and says why.
WaitForWhat to wait for, and how long.