Skip to content

Device

Device is one attached device: keys, the screen, rotation, device conditions, files, notifications, screenshots and waits of your own. device.app(...) and device.screen find elements. Guide: Keys, screen and rotation, Device conditions.

Device

All calls block. The client does not serialize calls on one device: issue them from one thread, and use one thread per Device for concurrency across devices. Nothing here caches UI state: App.element / Screen.element return a lazy selector that every action resolves again, and mutations fail with AMBIGUOUS/NOT_FOUND before any input when the selector does not match exactly one node.

Methods:

Name Description
app

Bind lifecycle and selectors to package_name.

info

API level, model, display size and the package owning the focused window.

press_back

Send KEYCODE_BACK.

press_home

Send KEYCODE_HOME.

press_key

Injects one Android key code (a mutation: never replayed on transport loss).

open_notifications

Open the notification shade (the system's accessibility action).

open_quick_settings

Open the quick settings panel; otherwise as open_notifications.

set_orientation

Rotate the display to orientation geometry, whatever the device's natural

set_display_rotation

Rotate the display to the exact rotation and freeze it there; otherwise as

unfreeze_rotation

Hand rotation back to the device's sensor (auto-rotate), without choosing a rotation.

wake

Turn the screen on (KEYCODE_WAKEUP; nothing happens when it is on). The keyguard

sleep

Turn the screen off (KEYCODE_SLEEP; nothing happens when it is off).

dismiss_keyguard

Dismiss a keyguard that has no PIN, pattern or password (wm dismiss-keyguard); with

await_permission_prompt

Wait until a runtime-permission dialog shows and return the choices it offers.

choose_permission

Tap the permission dialog's button for choice (NOT_FOUND before any input when

keyboard_shown

Whether a soft keyboard (any input method's window) is on screen; info() reports

hide_keyboard

Hide the soft keyboard with one Back key, which the keyboard consumes; with no keyboard

set_clipboard

Put text on the device clipboard as plain text (at most 4096 characters).

clipboard

The device clipboard as text; "" when it is empty or holds nothing that reads as

set_animations

Turn the window, transition and animator animations off (all three scales 0) or on

set_dark_mode

Dark theme on or off (cmd uimode night) until detach(), see

set_font_scale

The system font scale, 0.5 to 2.0 (1.0 = default), until detach(), see

set_density

Override the display density with dpi (100 to 1000), or with None go back to

set_network

Switch airplane mode, Wi-Fi and mobile data on or off until detach(), see

set_system_locales

Set the device's languages (Settings › Languages) until detach(), see

set_location

Mock the device location until detach().

set_stay_awake

Keep the screen on while the device is plugged in (USB, AC or wireless), or let it time

set_accessibility_display

Turn the accessibility display settings on or off until detach(); a None

foreground_activity

The activity on top of the screen (the resumed one, the focused one in multi-window),

push_file

Copy a file to device_path on the device until detach().

pull_file

The regular file at device_path on the device (at most 512 MiB): its bytes, or,

add_media

Add a photo or video to the device's gallery until detach(); return its device path.

await_toast

Wait for a toast and return it: one shown in the last 3.5 s (the longest a toast stays

await_notification

Wait until a matching notification is active (one already posted counts) and return

notifications

The active notifications, newest first (group summaries left out), as

open_notification

Open the one active notification that matches (as for await_notification) as a tap

dismiss_notification

Dismiss the one active notification that matches (as for await_notification) as a

type_text

Type value as real key events into whatever has input focus now.

screenshot

A PNG Screenshot of the screen, with its size. The server verifies the bytes

start_recording

Begin recording this device with scrcpy on the server host.

stop_recording

Stop and return a checksummed, bounded MP4, Matroska or Opus artifact.

dump_hierarchy

The diagnostic accessibility Hierarchy. Never used by selectors; keep it out of

screen_snapshot

The visible screen as ref-addressed ScreenNodes, each with a selector the daemon

resolve_ref

The selector behind a ScreenNode.ref (e7 or @e7) of any snapshot of this

driver_log

The driver instrumentation's recent output.

capture

Takes a screenshot, the hierarchy, the device info and the driver log at once, each

await_until

Host-side polling for conditions the driver cannot evaluate in one command

detach

Detaches the device. Returns the quarantine detail when the device could not be left

serial

serial: str

The device's ADB serial, e.g. emulator-5554.

generation

generation: int

The server's session generation for this device; it changes when the driver is rebuilt.

screen

screen: Screen

Whatever is visible, without an implicit package predicate.

timeouts

timeouts: Timeouts

Default timeouts for actions, waits and app lifecycle calls on this device.

detached

detached: bool

True once detach() ran; every later call on this device is rejected.

app

app(package_name: str) -> App

Bind lifecycle and selectors to package_name.

info

info() -> DeviceInfo

API level, model, display size and the package owning the focused window.

press_back

press_back() -> None

Send KEYCODE_BACK.

press_home

press_home() -> None

Send KEYCODE_HOME.

press_key

press_key(key_code: int) -> None

Injects one Android key code (a mutation: never replayed on transport loss).

open_notifications

open_notifications() -> None

Open the notification shade (the system's accessibility action).

Only whether the system accepted it is reported: wait for what the test needs in the shade, and press_back closes it.

open_quick_settings

open_quick_settings() -> None

Open the quick settings panel; otherwise as open_notifications.

set_orientation

set_orientation(orientation: Orientation) -> None

Rotate the display to orientation geometry, whatever the device's natural orientation, and freeze it there until unfreeze_rotation.

Fails only when Android refuses the request; the call then waits briefly for the display to turn. The foreground app may pin its own orientation and keep the display where it wants it: assert with info() (orientation, display_rotation). The rotation settings the device had before the session's first rotation call are restored when the device is detached.

set_display_rotation

set_display_rotation(rotation: DisplayRotation) -> None

Rotate the display to the exact rotation and freeze it there; otherwise as set_orientation.

unfreeze_rotation

unfreeze_rotation() -> None

Hand rotation back to the device's sensor (auto-rotate), without choosing a rotation.

wake

wake() -> None

Turn the screen on (KEYCODE_WAKEUP; nothing happens when it is on). The keyguard may still show: see dismiss_keyguard.

sleep

sleep() -> None

Turn the screen off (KEYCODE_SLEEP; nothing happens when it is off).

dismiss_keyguard

dismiss_keyguard() -> None

Dismiss a keyguard that has no PIN, pattern or password (wm dismiss-keyguard); with no keyguard showing nothing is sent. A secure keyguard raises CommandError (ACTION_REJECTED / KEYGUARD_SECURE) before any input: Tap never unlocks one. info() reports the state.

await_permission_prompt

await_permission_prompt(timeout: float | None = None) -> PermissionPrompt

Wait until a runtime-permission dialog shows and return the choices it offers.

Buttons are recognised by the permission controller's resource ids, never by label or position. Raises WaitTimeoutError (reason NO_PERMISSION_PROMPT) when none appears within timeout (default timeouts.wait).

choose_permission

choose_permission(choice: PermissionChoice, accuracy: LocationAccuracy | None = None, timeout: float | None = None) -> None

Tap the permission dialog's button for choice (NOT_FOUND before any input when the dialog does not offer it). With accuracy, the location dialog's Precise / Approximate radio is selected first; it must be offered too (PermissionPrompt.accuracies, API 31+ when the app asks for fine location), or nothing is tapped. Only the taps are reported: assert the outcome, e.g. the dialog gone and the app's state.

keyboard_shown

keyboard_shown() -> bool

Whether a soft keyboard (any input method's window) is on screen; info() reports the same.

hide_keyboard

hide_keyboard(timeout: float | None = None) -> None

Hide the soft keyboard with one Back key, which the keyboard consumes; with no keyboard showing nothing is sent, so Back never reaches the app. Only the key is reported: assert keyboard_shown() when it matters.

set_clipboard

set_clipboard(text: str, timeout: float | None = None) -> None

Put text on the device clipboard as plain text (at most 4096 characters).

clipboard

clipboard(timeout: float | None = None) -> str

The device clipboard as text; "" when it is empty or holds nothing that reads as text.

set_animations

set_animations(enabled: bool) -> None

Turn the window, transition and animator animations off (all three scales 0) or on (all 1) for this session.

Like every device condition below, the value the device had before the session's first change is restored on detach() (or by the next attach when the server died first), and the change is read back: a value the device did not take raises ServerError (reason DEVICE_SETTING). info().animations_enabled reports it.

set_dark_mode

set_dark_mode(enabled: bool) -> None

Dark theme on or off (cmd uimode night) until detach(), see set_animations. API 29+: below, ServerError (reason UNSUPPORTED_API). Read back with info().dark_mode.

set_font_scale

set_font_scale(scale: float) -> None

The system font scale, 0.5 to 2.0 (1.0 = default), until detach(), see set_animations. Read back with info().font_scale.

set_density

set_density(dpi: int | None) -> None

Override the display density with dpi (100 to 1000), or with None go back to the display's physical density, until detach(), see set_animations. Read back with info().density_dpi.

set_network

set_network(*, airplane_mode: bool | None = None, wifi: bool | None = None, mobile_data: bool | None = None) -> None

Switch airplane mode, Wi-Fi and mobile data on or off until detach(), see set_animations; a switch left None stays as it is.

These are the device's real switches (nothing is mocked): the app sees what it would see if a user flipped them, and turning Wi-Fi on says nothing about when it connects. Airplane mode is applied first, so set_network(airplane_mode=True, wifi=True) is airplane mode with Wi-Fi on. API 29+. A device reached over ADB on the network refuses Wi-Fi off or airplane mode on (ServerError, reason DEVICE_SETTING): that would cut Tap off. Read back with info().airplane_mode, wifi_enabled and mobile_data_enabled.

set_system_locales

set_system_locales(locales: Sequence[str]) -> None

Set the device's languages (Settings › Languages) until detach(), see set_animations: BCP-47 tags in preference order, 1 to 16 of them ("fr-FR", "en").

Every app that follows the system sees the change as a configuration change; an app with its own languages (App.set_locales) keeps them. Android has no shell command for this: the Tap driver app applies it as Settings' language picker does, and the result is read back (ServerError, reason DEVICE_SETTING, when the device reports another list). Read back with info().system_locales.

set_location

set_location(latitude: float, longitude: float, *, accuracy_m: float | None = None, altitude_m: float | None = None) -> None

Mock the device location until detach().

The gps and network providers (and fused, API 31+) report this fix, sent again every second so an app that starts listening later still gets one. latitude -90..90, longitude -180..180, accuracy_m in meters (> 0; None = 5), altitude_m in meters (None = none). The Tap driver app becomes the device's mock-location app and, when location is off, location is turned on; both are restored on detach(), which ends the mock. Calling it again moves the fix. Apps that read location through Google Play services see it as Play services relays the platform providers.

set_stay_awake

set_stay_awake(enabled: bool) -> None

Keep the screen on while the device is plugged in (USB, AC or wireless), or let it time out again, until detach(): what Developer options › Stay awake sets. A device on ADB over USB is plugged in, so a long test does not find the screen off. Read back with DeviceInfo.stay_awake.

set_accessibility_display

set_accessibility_display(*, high_contrast_text: bool | None = None, color_inversion: bool | None = None, bold_text: bool | None = None) -> None

Turn the accessibility display settings on or off until detach(); a None setting is left as it is (ValueError when all are None). high_contrast_text draws text with a black or white outline, color_inversion inverts the display's colors (screenshots stay uninverted: the inversion happens in the display pipeline), and bold_text makes the system font bold (API 31+, FailureReason.UNSUPPORTED_API below, before anything changes). The values are what Settings › Accessibility writes, read back (FailureReason.DEVICE_SETTING when the device did not take one). Read back with DeviceInfo.high_contrast_text, color_inversion and bold_text.

foreground_activity

foreground_activity() -> ForegroundActivity | None

The activity on top of the screen (the resumed one, the focused one in multi-window), or None when none is resumed: the keyguard is showing, or an activity is starting. Read on the host from dumpsys activity; changes nothing. Use it to check that a deep link or a notification opened the right screen.

push_file

push_file(device_path: str, source: bytes | bytearray | memoryview | str | PathLike[str]) -> None

Copy a file to device_path on the device until detach().

source is the content (bytes) or a file on this machine (str / PathLike), streamed in 1 MiB chunks; at most 512 MiB. device_path is absolute and normalised: /data/local/tmp/…, or shared storage (/sdcard/Download/…) for an app with storage access to read. Its directory must exist. A file already there is refused (ServerError, reason DEVICE_FILE) unless this device handle pushed it, so a test never overwrites the device's own files; the size is read back. The file is deleted on detach().

pull_file

pull_file(device_path: str) -> bytes
pull_file(device_path: str, to: str | PathLike[str]) -> None
pull_file(device_path: str, to: str | PathLike[str] | None = None) -> bytes | None

The regular file at device_path on the device (at most 512 MiB): its bytes, or, with to, written to that local file (replaced only once the whole file arrived).

add_media

add_media(source: bytes | bytearray | memoryview | str | PathLike[str], file_name: str | None = None) -> str

Add a photo or video to the device's gallery until detach(); return its device path.

source is the content (bytes, which needs file_name) or a local file (file_name defaults to its name). file_name is letters, digits, ., _, - or spaces with a photo extension (jpg, jpeg, png, gif, webp, heic, heif, bmp) or a video one (mp4, 3gp, webm, mkv, mov): the file goes to /sdcard/Pictures/Tap/ or /sdcard/Movies/Tap/ and the media scanner indexes it, so gallery apps and photo pickers list it (read back). A name already there is refused (ServerError, reason DEVICE_FILE) unless this device handle added it. Deleted, and dropped from the index, on detach(); nothing else in the gallery is touched.

await_toast

await_toast(text: str | None = None, mode: MatchMode = EXACT, *, package_name: str | None = None, timeout: float | None = None) -> Toast

Wait for a toast and return it: one shown in the last 3.5 s (the longest a toast stays up) or arriving within timeout (default timeouts.wait).

text matches with mode (any text when None); a toast from any package counts unless package_name names one (App.await_toast passes its own). Not consuming: the same toast can satisfy two calls in a row. Raises WaitTimeoutError (reason NO_TOAST) when none matches.

await_notification

await_notification(title: str | None = None, text: str | None = None, mode: MatchMode = EXACT, *, package_name: str | None = None, timeout: float | None = None) -> Notification

Wait until a matching notification is active (one already posted counts) and return the newest, within timeout (default timeouts.wait).

title and text match with mode (any when None); package_name narrows it to one app's notifications (App.await_notification passes its own). Notifications are read as data from a notification listener in the Tap driver app, not from the shade, so nothing opens on screen; the server gives the driver notification access for the session (taken back on detach()). Group summaries are left out. Raises WaitTimeoutError (reason NO_NOTIFICATION) when none matches.

notifications

notifications(timeout: float | None = None) -> list[Notification]

The active notifications, newest first (group summaries left out), as await_notification reads them.

open_notification

open_notification(title: str | None = None, text: str | None = None, mode: MatchMode = EXACT, *, package_name: str | None = None, action: str | None = None, timeout: float | None = None) -> None

Open the one active notification that matches (as for await_notification) as a tap on it in the shade does: send its content intent and, when it auto-cancels, remove it.

With action, press its action button with exactly that title instead; the notification stays. No match or several matches raise CommandError (NOT_FOUND / AMBIGUOUS), and a notification that opens nothing or has no such action ACTION_REJECTED (ACTION_NOT_OFFERED), before anything is sent. What the app does then is for the test to assert (an activity, say: foreground_activity()).

dismiss_notification

dismiss_notification(title: str | None = None, text: str | None = None, mode: MatchMode = EXACT, *, package_name: str | None = None, timeout: float | None = None) -> None

Dismiss the one active notification that matches (as for await_notification) as a swipe does. No match or several matches raise CommandError (NOT_FOUND / AMBIGUOUS), and an ongoing notification ACTION_REJECTED (NOT_CLEARABLE), before anything changes.

type_text

type_text(value: str, timeout: float | None = None) -> None

Type value as real key events into whatever has input focus now.

No target and no click (Element.type_text taps first). Unsupported characters are rejected before any input with INVALID_REQUEST/UNSUPPORTED_CHARACTERS; otherwise it reports whether every key event was accepted. Where the characters landed is for the test to assert.

screenshot

screenshot(timeout: float | None = None) -> Screenshot

A PNG Screenshot of the screen, with its size. The server verifies the bytes against the driver's checksum and the client checks the returned sha256 again. Keep it with Screenshot.save(path) or use Screenshot.bytes directly.

start_recording

start_recording(*, video: bool = True, audio_source: str | None = None, max_seconds: int | None = None) -> None

Begin recording this device with scrcpy on the server host.

The server runs tap start --scrcpy PATH (default scrcpy on its PATH). Defaults to silent H.264 MP4 video. Set audio_source to output (Android 11+, mutes device playback), playback (Android 13+, retains local audio but apps may opt out) or mic. With both tracks the result is Matroska; video=False records Opus audio only. Video is limited to 30 seconds/16 MiB, audio only to 60 seconds/3 MiB, and max_seconds ends the capture early. One recording per attached device; detach discards an unfinished one. Capture begins shortly after this returns, and a capture the device refuses is reported by stop_recording.

stop_recording

stop_recording() -> Recording

Stop and return a checksummed, bounded MP4, Matroska or Opus artifact.

dump_hierarchy

dump_hierarchy(timeout: float | None = None) -> Hierarchy

The diagnostic accessibility Hierarchy. Never used by selectors; keep it out of assertions.

screen_snapshot

screen_snapshot(timeout: float | None = None, *, selector_candidates: bool = False) -> ScreenSnapshot

The visible screen as ref-addressed ScreenNodes, each with a selector the daemon found to match only that node — for exploring an app, not for tests (it is built from the diagnostic hierarchy dump). Refs stay stable across snapshots of this attached device, and each node says whether it was added since the previous snapshot; nodes that are gone are in removed. Act on a node through its selector (device.screen.element(node.selector)) or resolve_ref: the device still requires exactly one match at action time. With selector_candidates each node also lists every selector that matched only it (ScreenNode.candidates), for an inspector that lets a person choose.

Experimental: not covered by the compatibility promise; it may change in any release.

resolve_ref

resolve_ref(ref: str) -> Selector

The selector behind a ScreenNode.ref (e7 or @e7) of any snapshot of this attached device. Raises ServerError with reason UNKNOWN_REF for a ref the daemon never issued or whose node has gone, and REF_NOT_ADDRESSABLE for a node without a selector.

Experimental: not covered by the compatibility promise; it may change in any release.

driver_log

driver_log() -> DriverLog

The driver instrumentation's recent output.

capture

capture(timeout: float = 30.0) -> Capture

Takes a screenshot, the hierarchy, the device info and the driver log at once, each within timeout seconds, and returns them as one Capture. Never raises for the device: a part that fails or runs out of time is None in the result, with its cause in Capture.failures. Use it wherever a test wants evidence (after a step, in an except); the pytest plugin calls it for every failed test.

await_until

await_until(description: str, condition: Callable[[], bool], timeout: float | None = None, poll_interval: float | None = None, observe: Callable[[], str | None] | None = None) -> None

Host-side polling for conditions the driver cannot evaluate in one command (cross-device, backend state). Prefer wait for UI conditions: it polls on the device.

detach

detach() -> str | None

Detaches the device. Returns the quarantine detail when the device could not be left clean (the server keeps it out of circulation), else None. Idempotent once it succeeded; a failed detach may be retried.

Timeouts

Per-device defaults in seconds. Every call also accepts an explicit timeout.

action

action: float

wait

wait: float

lifecycle

lifecycle: float

poll_interval

poll_interval: float