App

class App(source)

One app on a device: its elements, its lifecycle and the waits on it. Obtain with Device.app, which performs no I/O.

element and await add the package as one more selector predicate, so only this app's nodes match (in any of its windows); Device.screen matches anywhere. Lifecycle calls are executed by the host server (ADB plus verified postconditions); the client only names the package and the timeouts, and install uploads the APK from this machine.

val app = device.app("com.example.shop")
app.coldLaunch()
app.element(res("search")).setText("socks")
app.await(text("3 results")).visible()

All I/O methods are suspend and require an owning scope (tapScope/tapTest); the constructor (Device.app) only binds values, so it stays non-suspend. Per-call withDeadlineAfter is applied server-side; caller cancellation promptly cancels the RPC. Every call is admitted through the device's command gate, so a started Device.detach or an invalidated connection rejects it locally and an in-flight call delays the session close.

Properties

Link copied to clipboard
Link copied to clipboard

Functions

Link copied to clipboard
fun await(selector: Selector, timeout: Duration = device.timeouts.wait): ElementWait

A wait on selector restricted to this package's nodes.

Link copied to clipboard
suspend fun awaitAnimationEnd(stableFor: Duration = 500.milliseconds, timeout: Duration = device.timeouts.wait)

Maestro's waitForAnimationToEnd, on request only: this package's window pixels have not changed (beyond 0.5 %) for stableFor. Costs one screenshot per 100 ms while waiting.

Link copied to clipboard
suspend fun awaitIdle(timeout: Duration = device.timeouts.wait, stableFor: Duration = Timeouts.IDLE_STABLE_FOR)

Waits until the app's TapSynchronization reports no busy work for stableFor. Requires the app's E2E build to ship sync-sdk and be signed like the driver. Experimental: synchronization is still being designed (ExperimentalTapApi).

Link copied to clipboard
suspend fun awaitNotification(title: String? = null, text: String? = null, mode: MatchMode = MatchMode.EXACT, timeout: Duration = device.timeouts.wait): Notification

Device.awaitNotification for this package's notifications only.

Link copied to clipboard
suspend fun awaitScreenStable(stableFor: Duration = 500.milliseconds, timeout: Duration = device.timeouts.wait, signal: StabilitySignal = StabilitySignal.ALL)

Waits on the device until this package's focused window has stopped changing for stableFor according to signal: the accessibility tree (StabilitySignal.TREE), the window pixels (StabilitySignal.PIXELS, 0.5 % tolerance) or both (default). Content-changed events restart the quiet period. Use it explicitly after an action that starts an animation or a transition; no command waits for this implicitly. A screen that keeps changing (indeterminate spinner, ticker, video) times out with SCREEN_CHANGING. awaitSettled and awaitAnimationEnd are the two single-signal shorthands. Only a device WAIT_TIMEOUT becomes WaitTimeoutException; other failures are CommandExceptions.

Link copied to clipboard
suspend fun awaitSettled(stableFor: Duration = 500.milliseconds, timeout: Duration = device.timeouts.wait)

Maestro's waitForAppToSettle, on request only: this package's accessibility hierarchy has not changed for stableFor. Cheap (no screenshots); sees layout, text and state changes but not pure drawing (a canvas animation, video).

Link copied to clipboard
suspend fun awaitToast(text: String? = null, mode: MatchMode = MatchMode.EXACT, timeout: Duration = device.timeouts.wait): Toast

Device.awaitToast for this package's toasts only: on Android 11+ a text toast is drawn by SystemUI but still reported under the app that posted it.

Link copied to clipboard
suspend fun awaitVisible(timeout: Duration = device.timeouts.wait)

Waits on the device until this package owns the focused window. Throws WaitTimeoutException only when the device reports WAIT_TIMEOUT; any other failure (driver unhealthy, transport lost, ...) is a CommandException.

Link copied to clipboard
suspend fun background()

Sends the app to the background with the Home key, as a user would; the process keeps running. Bring it back with foreground.

Link copied to clipboard
suspend fun clearData(timeout: Duration = device.timeouts.action)

pm clear: data, cache, and runtime permissions are gone; the app is left stopped, as after forceStop.

Link copied to clipboard
suspend fun coldLaunch(activity: String? = null, timeout: Duration = device.timeouts.lifecycle, extras: Map<String, Any> = emptyMap()): AppProcess

Verified force-stop, launch (with its extras), then the new process identity.

Link copied to clipboard
fun element(selector: Selector): Element

A lazy element: selector restricted to this package's nodes. Performs no I/O.

Link copied to clipboard
suspend fun forceStop(timeout: Duration = device.timeouts.action)

am force-stop plus proof that no process and no activity of the package remain.

Link copied to clipboard
suspend fun foreground(timeout: Duration = device.timeouts.lifecycle)

Brings the app to the front the way the launcher does: its launcher intent with NEW_TASK | RESET_TASK_IF_NEEDED, so a running task resumes where it was instead of gaining a second copy of the launcher activity, and a stopped app starts. Returns when am start -W reports the launch complete; wait for the screen you need.

Link copied to clipboard
suspend fun grantPermission(permission: String)

pm grant a runtime permission, e.g. android.permission.CAMERA.

Link copied to clipboard
suspend fun install(apk: Path, timeout: Duration = device.timeouts.lifecycle)

adb install -r -t of apk, verified. apk is a file on this machine: its bytes are streamed to the server in 1 MiB chunks, so the server may run elsewhere.

Link copied to clipboard
suspend fun isInstalled(): Boolean

Whether the package is installed.

Link copied to clipboard
suspend fun isPermissionGranted(permission: String): Boolean

Whether permission is granted to the package now, as dumpsys package lists it: the read-back for grantPermission, revokePermission, clearData and the permission dialog.

Link copied to clipboard
suspend fun isRunning(): Boolean

Whether any process of the package is alive.

Link copied to clipboard
suspend fun launch(activity: String? = null, timeout: Duration = device.timeouts.lifecycle, extras: Map<String, Any> = emptyMap())

Starts activity (or the launcher activity) with am start -W and returns when Android reports the launch complete. Nothing about the UI is assumed: wait for what the test needs (awaitVisible, awaitScreenStable, an element wait).

Link copied to clipboard
suspend fun locales(): List<String>

The app's own languages now (canonical BCP-47 tags); empty when it follows the system. API 33+.

Link copied to clipboard
suspend fun openLink(uri: String, anyApp: Boolean = false, timeout: Duration = device.timeouts.lifecycle): String?

Opens uri (a deep link or app link, e.g. myapp://orders/42 or https://example.com/x) with an ACTION_VIEW intent restricted to this package, so no chooser or browser can take it; pass anyApp = true to let Android resolve it like a tap on a link elsewhere would. Returns the activity Android reported starting (package/.Activity) when it reported one. Nothing about the screen is checked: wait for what the link should show.

Link copied to clipboard
suspend fun process(timeout: Duration = device.timeouts.action): AppProcess

Current single process identity (PID + start token); waits briefly for it to exist.

Link copied to clipboard
suspend fun revokePermission(permission: String)

pm revoke a runtime permission, proven by dumpsys package. Android kills the app's process when one of its runtime permissions is revoked.

Link copied to clipboard
suspend fun setLocales(locales: List<String>)

The app's own languages (Android's per-app language, API 33+), as BCP-47 tags in preference order ("fr-FR", "en"); an empty list makes it follow the system again. The tags are checked and canonicalized by the server (INVALID_ARGUMENT for a malformed one) and the change is read back. Like the Device conditions, the app's languages before the session's first change come back on Device.detach. Below API 33: ServerException (FailureReason.UNSUPPORTED_API). Android applies it as a configuration change: the app's activities are recreated unless it handles the change itself.

Link copied to clipboard
open override fun toString(): String
Link copied to clipboard
suspend fun uninstall()

pm uninstall, verified.