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 |
info |
API level, model, display size and the package owning the focused window. |
press_back |
Send |
press_home |
Send |
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 |
set_orientation |
Rotate the display to |
set_display_rotation |
Rotate the display to the exact |
unfreeze_rotation |
Hand rotation back to the device's sensor (auto-rotate), without choosing a rotation. |
wake |
Turn the screen on ( |
sleep |
Turn the screen off ( |
dismiss_keyguard |
Dismiss a keyguard that has no PIN, pattern or password ( |
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 |
keyboard_shown |
Whether a soft keyboard (any input method's window) is on screen; |
hide_keyboard |
Hide the soft keyboard with one Back key, which the keyboard consumes; with no keyboard |
set_clipboard |
Put |
clipboard |
The device clipboard as text; |
set_animations |
Turn the window, transition and animator animations off (all three scales 0) or on |
set_dark_mode |
Dark theme on or off ( |
set_font_scale |
The system font scale, 0.5 to 2.0 (1.0 = default), until |
set_density |
Override the display density with |
set_network |
Switch airplane mode, Wi-Fi and mobile data on or off until |
set_system_locales |
Set the device's languages (Settings › Languages) until |
set_location |
Mock the device location until |
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 |
foreground_activity |
The activity on top of the screen (the resumed one, the focused one in multi-window), |
push_file |
Copy a file to |
pull_file |
The regular file at |
add_media |
Add a photo or video to the device's gallery until |
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 |
dismiss_notification |
Dismiss the one active notification that matches (as for |
type_text |
Type |
screenshot |
A PNG |
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 |
screen_snapshot |
The visible screen as ref-addressed |
resolve_ref |
The selector behind a |
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 |
generation
¶
generation: int
The server's session generation for this device; it changes when the driver is rebuilt.
timeouts
¶
timeouts: Timeouts
Default timeouts for actions, waits and app lifecycle calls on this device.
info
¶
info() -> DeviceInfo
API level, model, display size and the package owning the focused window.
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.
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.
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.