The .NET 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
dotnet add reference mobium/clients/dotnet/Mobium/Mobium.csproj
Connect
using var device = Device.Builder().Platform("android").App("com.android.settings").Start();
foreach (var el in device.Map()) Console.WriteLine($"{el.Ref} {el.Label}");
device below is a Mobium.Device from Device.Builder()...Start() or Device.Connect().
Every tool, by method
Connecting, ending, and errors
Connect
public static Device Connect()
public Device 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 DeviceBuilder.Start.
Connect to whichever device is running
using var dev = Device.Connect();
Console.WriteLine(dev.Current());
Connect to one device, with a named driver
using var dev = Device.Builder()
.OnDevice("emulator-5554")
.Driver("uiautomator2")
.Connect();
Builder
public static DeviceBuilder Builder()
Configures a connection or a session.
Configure a session before starting it
var builder = Device.Builder().Platform("ios").App("com.apple.Preferences");
using var dev = builder.Start();
Start
public Device 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
Device.Quit, or a using block, which quits:
using var device = Device.Builder().Platform("android").App("com.android.settings").Start();
device.Map();
Open a session and launch the app fresh
using var dev = Device.Builder().Platform("android").App("com.android.settings").Start();
dev.Tap("text=Network & internet");
Binary
public DeviceBuilder Binary(string path)
Pins the mobium executable, ahead of MOBIUM_BIN_PATH and PATH.
Use a mobium binary that is not on PATH
using var dev = Device.Builder().Binary("/opt/mobium/bin/mobium").Connect();
OnDevice
public DeviceBuilder OnDevice(string serial)
Targets one device by serial or UDID. Omit when only one is running.
Pick one of several devices
using var dev = Device.Builder().OnDevice("emulator-5554").Connect();
Driver
public DeviceBuilder Driver(string name)
public string Driver { get; }
Chooses the driver: uiautomator2 (default on Android),
uiautomator (installs nothing, slower, cannot type) or
wda (iOS simulators and iPhones).
Use the zero-install Android driver
using var dev = Device.Builder().Driver("uiautomator").Connect();
CallTimeout
public DeviceBuilder CallTimeout(TimeSpan timeout)
The longest any one call may take before the connection is given up, including the handshake. 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 fails saying so; connect again. The device's session
lives in the daemon, so reconnecting is cheap. Set it well above the
longest Until.Timeout you use.
Bound how long any one call may take
using var dev = Device.Builder()
.CallTimeout(TimeSpan.FromMinutes(2))
.Connect();
Session
public Session? Session { get; internal set; }
public DeviceBuilder Session(string name)
public sealed class Session
What DeviceBuilder.Start opened — device, platform,
driver — or null for a device from Connect.
Name the daemon session
using var dev = Device.Builder().Session("checkout").Connect();
Platform
public DeviceBuilder Platform(string name)
public string Platform { get; }
public string Platform { get; }
The platform for Start: android or ios.
ios picks the wda driver, so none need be named.
Start a session on an iOS simulator
using var dev = Device.Builder().Platform("ios").Start();
App
public sealed class App
public App(string id, string name, string version, bool system)
public DeviceBuilder App(string id)
One installed app.
Launch an app as the session starts
using var dev = Device.Builder().App("com.example.shop").Start();
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 — a using block ending after an explicit one —
does nothing. A program that exits without quitting or disposing
has the sessions it started ended for it: mobium sees the client go.
End the session, putting back what it changed
var dev = Device.Builder().App("com.android.settings").Start();
try { dev.Tap("text=Display"); }
finally { dev.Quit(); }
Dispose
public void Dispose()
public void Dispose()
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.
Let a using block quit the session
using (var dev = Device.Builder().App("com.android.settings").Start())
{
dev.Tap("text=Display");
} // quit here
Sessions
public IList<IDictionary<string, object?>> Sessions()
The sessions open on the daemon, each with device, platform and driver.
List the sessions open on the daemon
foreach (var s in device.Sessions())
Console.WriteLine($"{s["device"]} {s["platform"]} {s["driver"]}");
Call
public IDictionary<string, object?> Call(string tool, IDictionary<string, object?>? arguments)
Runs any tool by name, for anything this class does not wrap yet, and returns its structured answer.
Run any tool by name
var result = device.Call("app_battery", null);
Pass arguments the typed methods do not take
device.Call("app_sms", new Dictionary<string, object?> { ["text"] = "Hi", ["from"] = "5559876" });
Step
public static IDictionary<string, object?> Step(string tool, IDictionary<string, object?>? arguments = null)
One step for Batch: a tool and the arguments it
takes on its own.
Build steps for Batch
var step = Device.Step("app_tap", new Dictionary<string, object?> { ["target"] = "@e2" });
device.Batch(step, Device.Step("app_map"));
MobiumException
public class MobiumException : Exception
public MobiumException(string message) : this(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, 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 and 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. Tool names the tool that failed,
or is empty for a transport-level failure.
Catch any failure
try { device.Tap("text=Nope"); }
catch (MobiumException e)
{
Console.WriteLine($"{e.Tool} {e.Code}: {e.Message} ({e.Remedy})");
}
NoSuchElementException
public sealed class NoSuchElementException : MobiumException
A locator or ref matched nothing on screen. Worth scrolling for. Error code no_such_element.
Scroll when an element is not there
try { device.Tap("text=Continue"); }
catch (NoSuchElementException) { device.ScrollTo("text=Continue", "down"); }
AmbiguousLocatorException
public sealed class AmbiguousLocatorException : MobiumException
A locator matched more than one element. Narrow it; Mobium never guesses. Error code ambiguous_locator.
Fall back to a ref when a locator matches more than one
try { device.Tap("text=OK"); }
catch (AmbiguousLocatorException) { device.Tap(device.Find("text=OK").First().Ref); }
TimedOutException
public sealed class TimedOutException : MobiumException
A wait ran out. Named TimedOut so it does not collide with System.TimeoutException. Error code timeout.
Tell a timeout from other failures
try { device.WaitFor("text=Done", Until.Visible().Timeout(TimeSpan.FromSeconds(3))); }
catch (TimedOutException e) { Console.WriteLine(e.Retryable); }
UnsupportedException
public sealed class UnsupportedException : MobiumException
This driver or platform cannot do it, and says why. Retrying cannot help. Error code unsupported.
Skip what a platform cannot do
try { device.Press("back"); }
catch (UnsupportedException e) { Console.WriteLine(e.Remedy); } // iOS has no back button
NoSuchAlertException
public sealed class NoSuchAlertException : MobiumException
A dialog was expected and none is on screen. Error code no_such_alert.
Answer a dialog only if one is up
try { device.AnswerAlert(false); }
catch (NoSuchAlertException) { }
NoDeviceException
public sealed class NoDeviceException : MobiumException
Nothing to drive: no device matches, or none is connected. Error code no_device.
Report that nothing is running
try { device.Map(); }
catch (NoDeviceException e) { Console.WriteLine(e.Remedy); }
DeviceNotReadyException
public sealed class DeviceNotReadyException : MobiumException
The device is there and cannot be driven yet: locked, not trusted, Developer Mode off. Error code device_not_ready.
Unlock before launching
try { device.Launch("com.android.settings"); }
catch (DeviceNotReadyException) { device.ScreenLocked(false); device.Launch("com.android.settings"); }
NotConfirmedException
public sealed class NotConfirmedException : MobiumException
The command reported success and reading the state back disagreed. Error code not_confirmed.
Notice an action the device did not confirm
try { device.Check("@e4"); }
catch (NotConfirmedException e) { Console.WriteLine(e.Details.Count); }
Until
public sealed class Until
What to wait for, and how long.
Named Until rather than WaitFor because a type and a
method of the same name in the same scope cannot both be reached in
C#. Until.Hidden() reads better at the call site anyway.
device.WaitFor("text=Welcome"); // visible, 10s
device.WaitFor("role=progressbar", Until.Hidden()); // until it goes
device.WaitFor("@e4", Until.Text("Sent").Timeout(TimeSpan.FromSeconds(30)));
Pass a condition to WaitFor
device.WaitFor("@e4", Until.Text("Sent").Timeout(TimeSpan.FromSeconds(30)));
ExactText
public static Until ExactText(string expected)
Wait until its text is exactly this, where Text waits for a part.
Wait for an element's whole text
device.WaitFor("testid=status", Until.ExactText("Paid"));
Count
public static Until Count(int n)
Wait until the locator matches exactly this many elements on screen.
Wait for a list to hold three rows
device.WaitFor("role=row", Until.Count(3));
Not
public Until Not()
The opposite of this condition: Until.Text("Sending").Not() waits for it to change.
Wait for a condition to stop holding
device.WaitFor("testid=submit", Until.Enabled().Not());
Visible
public static Until Visible()
Wait for the element to be on screen. The default.
Wait for an element to be on screen
device.WaitFor("text=Welcome", Until.Visible());
Hidden
public static Until Hidden()
Wait for it to go away — a spinner, say.
Wait for a spinner to go
device.WaitFor("role=progressbar", Until.Hidden());
Value
public string Value { get; }
public static Until Value(string expected)
What a slider reads, as the app states it ("80%", "1.2"); empty for anything else.
Wait for a field's value
device.WaitFor("testid=email", Until.Value("someone@example.com"));
Enabled
public static Until Enabled()
Wait for a control to be enabled — a Submit the app enables once a form is valid.
Wait for a button to be enabled
var button = device.WaitFor("testid=submit", Until.Enabled());
Disabled
public bool Disabled { get; }
public static Until Disabled()
True for what the platform reports not enabled: an action on it waits, then is refused.
Wait for a button to be disabled
device.WaitFor("testid=submit", Until.Disabled());
Checked
public bool? Checked { get; }
public static Until Checked()
A checkbox, radio or switch's state; null for anything with no such state.
Wait for a checkbox to be checked
device.WaitFor("testid=termsCheck", Until.Checked());
Unchecked
public static Until Unchecked()
Wait for a checkbox, radio or switch to be unchecked.
Wait for a switch to be off
device.WaitFor("@e4", Until.Unchecked());
Focused
public static Until Focused()
Wait for a field to have keyboard focus.
Wait for a field to take focus
device.WaitFor("testid=search", Until.Focused());
Timeout
public Until Timeout(TimeSpan d)
How long before giving up. Ten seconds by default, two minutes at most.
Wait longer than the ten-second default
device.WaitFor("text=Ready", Until.Visible().Timeout(TimeSpan.FromSeconds(30)));
Types
| Type | What it is |
|---|---|
App | One installed app. |
Bounds | An on-screen rectangle in device pixels, on both platforms. |
Device | Drives mobile apps on Android emulators, Android phones, iOS simulators and iPhones. |
DeviceBuilder | Options for Device.Connect(). |
DeviceInfo | One attached device or simulator. |
Element | One actionable thing on screen. |
Location | Where a device believes it is. |
MobiumException | A tool reported that it could not do what was asked, or the connection to mobium failed. |
NoDeviceException | Nothing to drive: no device matches, or none is connected. |
DeviceNotReadyException | The device is there and cannot be driven yet: locked, not trusted, Developer Mode off. |
ToolchainMissingException | Something on this machine is missing: adb, Xcode, a signing certificate. |
NoSuchElementException | A locator or ref matched nothing on screen. |
AmbiguousLocatorException | A locator matched more than one element. |
ElementNotReachableException | The element was found and cannot be touched where it is, usually off screen. |
NoSuchContextException | A WebView context that is not there. |
NoSuchAlertException | A dialog was expected and none is on screen. |
UnsupportedException | This driver or platform cannot do it, and says why. |
NotConfirmedException | The command reported success and reading the state back disagreed. |
TimedOutException | A wait ran out. |
InvalidArgumentException | The request itself is wrong. |
DeviceServerException | The device side failed (a device server, or adb, simctl, devicectl, lockdown); a server's W3C code is Details["w3c"]. |
InternalException | A bug in Mobium. |
Session | What DeviceBuilder.Start opened: the device and how it is driven. |
Until | What to wait for, and how long. |