Skip to content

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.