UI Automation¶
adbflow provides a high-level UI automation API built on Android's uiautomator dump. Find elements by text, resource ID, class name, and other attributes, then interact with them.
Selectors¶
Selector is a fluent builder for matching UI nodes. Chain methods to narrow your search:
from adbflow.ui import Selector
# By text
Selector().text("Login")
Selector().text_contains("Log")
Selector().text_starts_with("Log")
# By resource ID
Selector().resource_id("com.example:id/button_login")
Selector().resource_id_contains("button_login")
# By class name
Selector().class_name("android.widget.Button")
# By content description (accessibility)
Selector().description("Close dialog")
Selector().description_contains("Close")
# By boolean attributes
Selector().clickable()
Selector().enabled()
Selector().scrollable()
Selector().checked()
Selector().focused()
# Combine multiple criteria
Selector().text("Submit").clickable().enabled()
# By package
Selector().package("com.example.app")
# By index (position among siblings)
Selector().class_name("android.widget.Button").index(2)
Structural Selectors¶
Find elements relative to other elements:
# Child of another selector
Selector().resource_id("com.example:id/list").child(
Selector().text("Item 1")
)
# Sibling of another selector
Selector().text("Label").sibling(
Selector().class_name("android.widget.EditText")
)
Finding Elements¶
async def find_examples(device):
# Find a single element (returns None if not found)
element = await device.ui.find_async(
Selector().text("Settings")
)
# Find all matching elements
buttons = await device.ui.find_all_async(
Selector().class_name("android.widget.Button").clickable()
)
for btn in buttons:
print(btn.get_text())
# Check existence without getting the element
exists = await device.ui.exists_async(
Selector().text("Error")
)
Waiting for Elements¶
wait_for_async polls the UI hierarchy until the element appears or the timeout expires:
async def wait_examples(device):
# Wait up to 10 seconds for element to appear
element = await device.ui.wait_for_async(
Selector().text("Welcome"),
timeout=10.0,
interval=0.5 # poll every 500ms
)
await element.tap_async()
If the element doesn't appear within the timeout, WaitTimeoutError is raised.
Interacting with Elements¶
Once you have a UIElement, you can tap, type, swipe, and more:
async def interact_examples(device):
element = await device.ui.wait_for_async(Selector().text("Search"))
# Tap
await element.tap_async()
# Long tap
await element.long_tap_async(duration_ms=1500)
# Type text (element should be focused)
await element.text_input_async("hello world")
# Swipe on the element
from adbflow.utils.types import SwipeDirection
await element.swipe_async(SwipeDirection.UP, distance=300)
# Read element properties
text = element.get_text()
bounds = element.get_bounds() # Rect
center = element.get_center() # Point
# Check if element still exists
still_there = await element.exists_async()
UI Hierarchy¶
Access the raw UI hierarchy tree for advanced use cases:
async def hierarchy_example(device):
# Get the root node
root = await device.ui.dump_async()
# Iterate all nodes
for node in root.iter_all():
if node.clickable and node.text:
print(f"Clickable: {node.text} at {node.bounds}")
# Force a fresh dump (bypass cache)
root = await device.ui.dump_async(force=True)
Common Patterns¶
Scroll to find an element¶
async def scroll_to_find(device, text, max_scrolls=5):
for _ in range(max_scrolls):
element = await device.ui.find_async(Selector().text(text))
if element:
return element
await device.gestures.swipe_direction_async(SwipeDirection.UP)
return None
Tap and verify¶
async def tap_and_verify(device):
button = await device.ui.wait_for_async(Selector().text("Submit"))
await button.tap_async()
# Verify navigation happened
await device.ui.wait_for_async(
Selector().text("Success"),
timeout=5.0
)
Fill a form¶
async def fill_form(device):
username = await device.ui.find_async(
Selector().resource_id("com.example:id/username")
)
await username.tap_async()
await username.text_input_async("user@example.com")
password = await device.ui.find_async(
Selector().resource_id("com.example:id/password")
)
await password.tap_async()
await password.text_input_async("secret123")
submit = await device.ui.find_async(
Selector().text("Login").clickable()
)
await submit.tap_async()
Tip
The UI hierarchy is cached for 2 seconds. After any interaction (tap, swipe, text input), the cache is automatically invalidated so the next query gets fresh data.