A simple, easy-to-use, and stable Android automation library
Comprehensive UI element selection using UiAutomator selectors, coordinate-based targeting, and gesture operations. Supports clicking, text input, gestures, and element property inspection.
Select UI elements using UiAutomator selector attributes.
class Device:
def __call__(self, **kwargs) -> UiObject:
"""
Create UI element selector.
Parameters:
- text: Exact text match
- textContains: Partial text match
- textMatches: Regular expression text match
- textStartsWith: Text prefix match
- className: UI element class name
- classNameMatches: Regular expression class name match
- description: Content description match
- descriptionContains: Partial description match
- descriptionMatches: Regular expression description match
- descriptionStartsWith: Description prefix match
- resourceId: Resource ID match
- resourceIdMatches: Regular expression resource ID match
- packageName: Package name match
- packageNameMatches: Regular expression package name match
- checkable: Boolean checkable state
- checked: Boolean checked state
- clickable: Boolean clickable state
- longClickable: Boolean long clickable state
- scrollable: Boolean scrollable state
- enabled: Boolean enabled state
- focusable: Boolean focusable state
- focused: Boolean focused state
- selected: Boolean selected state
- index: Element index within parent
- instance: Element instance number
Returns:
UiObject for interaction
"""
def exists(self, **kwargs) -> bool:
"""Check if UI element exists using selector attributes"""Usage examples:
d = u2.connect()
# Select by text
button = d(text="OK")
login_btn = d(textContains="Login")
submit_btn = d(textMatches=r"Submit|Send")
# Select by resource ID
username_field = d(resourceId="com.example:id/username")
password_field = d(resourceId="com.example:id/password")
# Select by class and attributes
text_view = d(className="android.widget.TextView", clickable=True)
edit_text = d(className="android.widget.EditText", enabled=True)
# Select by index
first_item = d(className="android.widget.LinearLayout", index=0)
# Check existence
if d(text="Settings").exists:
print("Settings button found")Access UI element properties and attributes.
class UiObject:
@property
def exists(self) -> bool:
"""Check if element exists in current UI"""
@property
def info(self) -> Dict[str, Any]:
"""Get element attributes including bounds, text, className, etc."""
def screenshot(self, display_id: Optional[int] = None) -> Image.Image:
"""Take screenshot of element bounds"""Usage examples:
d = u2.connect()
# Get element info
element = d(resourceId="com.example:id/title")
if element.exists:
info = element.info
print(f"Text: {info['text']}")
print(f"Bounds: {info['bounds']}")
print(f"Class: {info['className']}")
print(f"Clickable: {info['clickable']}")
# Screenshot element
element_image = element.screenshot()
element_image.save("element.png")Direct interaction using screen coordinates.
class Device:
def click(self, x: Union[float, int], y: Union[float, int]):
"""
Click at coordinates.
Parameters:
- x: X coordinate (absolute pixels or relative 0-1)
- y: Y coordinate (absolute pixels or relative 0-1)
"""
def double_click(self, x, y, duration=0.1):
"""
Double click at coordinates.
Parameters:
- x, y: Coordinates
- duration: Delay between clicks in seconds
"""
def long_click(self, x, y, duration: float = 0.5):
"""
Long click at coordinates.
Parameters:
- x, y: Coordinates
- duration: Press duration in seconds
"""
@property
def pos_rel2abs(self):
"""Function to convert relative (0-1) to absolute pixel coordinates"""Usage examples:
d = u2.connect()
# Absolute coordinates
d.click(100, 200)
d.double_click(300, 400)
d.long_click(500, 600, duration=2.0)
# Relative coordinates (0-1 range)
d.click(0.5, 0.3) # Center horizontally, 30% from top
d.click(0.1, 0.9) # Near bottom-left corner
# Convert relative to absolute
convert = d.pos_rel2abs
abs_x, abs_y = convert(0.5, 0.5) # Get center coordinatesSwipe, drag, and multi-touch gestures.
class Device:
def swipe(self, fx, fy, tx, ty, duration: Optional[float] = None, steps: Optional[int] = None):
"""
Swipe from one point to another.
Parameters:
- fx, fy: From coordinates
- tx, ty: To coordinates
- duration: Swipe duration in seconds
- steps: Number of interpolation steps (overrides duration)
"""
def swipe_points(self, points: List[Tuple[int, int]], duration: float = 0.5):
"""
Multi-point swipe gesture.
Parameters:
- points: List of (x, y) coordinate tuples
- duration: Total gesture duration
"""
def drag(self, sx, sy, ex, ey, duration=0.5):
"""
Drag from start to end coordinates.
Parameters:
- sx, sy: Start coordinates
- ex, ey: End coordinates
- duration: Drag duration in seconds
"""
@property
def touch(self) -> TouchActions:
"""Touch gesture builder for complex multi-touch operations"""Usage examples:
d = u2.connect()
# Basic swipe gestures
d.swipe(100, 500, 100, 100) # Swipe up
d.swipe(500, 300, 100, 300) # Swipe left
d.swipe(100, 100, 500, 500, duration=2.0) # Slow diagonal swipe
# Multi-point swipe
points = [(100, 100), (200, 150), (300, 100), (400, 200)]
d.swipe_points(points, duration=1.5)
# Drag operation
d.drag(100, 100, 500, 500, duration=1.0)
# Complex touch gestures
d.touch.down(100, 100).move(200, 200).sleep(0.5).up(200, 200)Extended swipe functionality for common UI patterns.
class Device:
@cached_property
def swipe_ext(self) -> SwipeExt:
"""Extended swipe gestures for common UI patterns"""
class SwipeExt:
def up(self, scale: float = 0.8):
"""Swipe up gesture"""
def down(self, scale: float = 0.8):
"""Swipe down gesture"""
def left(self, scale: float = 0.8):
"""Swipe left gesture"""
def right(self, scale: float = 0.8):
"""Swipe right gesture"""Usage examples:
d = u2.connect()
# Extended swipe gestures
d.swipe_ext.up(scale=0.9) # Swipe up 90% of screen
d.swipe_ext.down() # Swipe down default 80%
d.swipe_ext.left(scale=0.7) # Swipe left 70% of screen
d.swipe_ext.right() # Swipe right default 80%Direct interaction with selected UI elements.
class UiObject:
def click(self, timeout: Optional[float] = None):
"""Click the UI element"""
def long_click(self, duration: float = 0.5):
"""Long click the UI element"""
def set_text(self, text: str):
"""Set text content of element"""
def clear_text(self):
"""Clear text content of element"""
def wait(self, timeout: Optional[float] = None) -> bool:
"""Wait for element to appear"""
def wait_gone(self, timeout: Optional[float] = None) -> bool:
"""Wait for element to disappear"""
def click_exists(self, timeout=0) -> bool:
"""Click element if it exists within timeout"""
def click_gone(self, maxretry=10, interval=1.0):
"""Click element repeatedly until it disappears"""
def bounds(self) -> Tuple[int, int, int, int]:
"""Get element bounds (left, top, right, bottom)"""
def center(self, offset=(0.5, 0.5)) -> Tuple[int, int]:
"""Get element center coordinates with optional offset"""
def drag_to(self, **kwargs):
"""Drag element to another element or coordinates"""Usage examples:
d = u2.connect()
# Element interaction
button = d(text="Submit")
button.click()
# Text input
username = d(resourceId="com.example:id/username")
username.clear_text()
username.set_text("john_doe")
# Wait for elements
loading = d(text="Loading...")
loading.wait(timeout=10) # Wait up to 10 seconds
loading.wait_gone(timeout=30) # Wait for loading to disappear
# Advanced element interaction
button = d(text="Optional Button")
if button.click_exists(timeout=5): # Click if exists within 5 seconds
print("Button clicked")
# Remove popup by clicking repeatedly
popup_close = d(resourceId="close_popup")
popup_close.click_gone(maxretry=5, interval=0.5) # Click until gone
# Get element position and size
element = d(resourceId="target_element")
bounds = element.bounds() # (left, top, right, bottom)
print(f"Element bounds: {bounds}")
center_x, center_y = element.center() # Get center point
print(f"Element center: ({center_x}, {center_y})")
# Get center with offset (0.8, 0.2 means 80% right, 20% down within element)
offset_point = element.center(offset=(0.8, 0.2))
# Drag element to another location
source = d(text="Drag Me")
target = d(text="Drop Here")
source.drag_to(target) # Drag to another element
source.drag_to(x=500, y=300) # Drag to coordinatesNavigate through UI hierarchy using parent-child relationships.
class UiObject:
def child(self, **kwargs) -> UiObject:
"""Find child element using selector attributes"""
def sibling(self, **kwargs) -> UiObject:
"""Find sibling element using selector attributes"""
def child_by_text(self, txt: str, **kwargs) -> UiObject:
"""Find child element by text match"""
def child_by_description(self, txt: str, **kwargs) -> UiObject:
"""Find child element by description match"""
def child_by_instance(self, inst: int, **kwargs) -> UiObject:
"""Find child element by instance number"""Usage examples:
d = u2.connect()
# Navigate through hierarchy
container = d(resourceId="container")
submit_button = container.child(text="Submit")
submit_button.click()
# Find specific child by text
menu = d(className="android.widget.LinearLayout")
settings_item = menu.child_by_text("Settings")
settings_item.click()
# Find sibling elements
current_item = d(text="Current Item")
next_item = current_item.sibling(text="Next Item")
next_item.click()Find elements relative to other elements on screen.
class UiObject:
def right(self, **kwargs) -> UiObject:
"""Find element to the right of current element"""
def left(self, **kwargs) -> UiObject:
"""Find element to the left of current element"""
def up(self, **kwargs) -> UiObject:
"""Find element above current element"""
def down(self, **kwargs) -> UiObject:
"""Find element below current element"""Usage examples:
d = u2.connect()
# Relative positioning
username_field = d(resourceId="username")
password_field = username_field.down(resourceId="password")
password_field.set_text("secret123")
# Find button to the right of a label
price_label = d(text="Price:")
edit_button = price_label.right(text="Edit")
edit_button.click()Scroll elements within containers with precise control.
class UiObject:
@property
def scroll(self):
"""Scroll gesture builder for element"""
@property
def fling(self):
"""Fling gesture builder for element"""
class ScrollBuilder:
def forward(self, steps: int = 10): """Scroll forward (up/left)"""
def backward(self, steps: int = 10): """Scroll backward (down/right)"""
def toBeginning(self, steps: int = 10): """Scroll to beginning"""
def toEnd(self, steps: int = 10): """Scroll to end"""
def to(self, **kwargs): """Scroll until element is visible"""
class FlingBuilder:
def forward(self): """Fling forward quickly"""
def backward(self): """Fling backward quickly"""
def toBeginning(self): """Fling to beginning"""
def toEnd(self): """Fling to end"""Usage examples:
d = u2.connect()
# Scroll within a list or container
scrollable_list = d(scrollable=True)
scrollable_list.scroll.forward(steps=5) # Scroll up 5 steps
scrollable_list.scroll.backward(steps=10) # Scroll down 10 steps
scrollable_list.scroll.toBeginning() # Scroll to top
scrollable_list.scroll.toEnd() # Scroll to bottom
# Scroll until specific element is visible
scrollable_list.scroll.to(text="Target Item")
# Fast fling gestures
scrollable_list.fling.forward() # Quick fling up
scrollable_list.fling.toEnd() # Quick fling to bottomMulti-finger gestures for complex interactions.
class UiObject:
def gesture(self, start1: Tuple[float, float], start2: Tuple[float, float],
end1: Tuple[float, float], end2: Tuple[float, float], steps: int = 100):
"""Two-finger gesture between specified points"""
def pinch_in(self, percent: int = 100, steps: int = 50):
"""Pinch in gesture (zoom out)"""
def pinch_out(self, percent: int = 100, steps: int = 50):
"""Pinch out gesture (zoom in)"""Usage examples:
d = u2.connect()
# Pinch gestures on image or map
image_view = d(className="ImageView")
image_view.pinch_out(percent=200, steps=30) # Zoom in 200%
image_view.pinch_in(percent=50, steps=30) # Zoom out 50%
# Custom two-finger gesture
map_view = d(resourceId="map_view")
# Two-finger drag from corners to center
map_view.gesture(start1=(0.1, 0.1), start2=(0.9, 0.9),
end1=(0.4, 0.4), end2=(0.6, 0.6), steps=50)Work with multiple matching elements using indexing and iteration.
class UiObject:
def __getitem__(self, index: int) -> UiObject:
"""Get element by index when multiple matches exist"""
def __len__(self) -> int:
"""Get count of matching elements"""
def __iter__(self):
"""Iterate over all matching elements"""
@property
def count(self) -> int:
"""Get count of matching elements"""Usage examples:
d = u2.connect()
# Work with multiple matching elements
buttons = d(className="android.widget.Button")
print(f"Found {len(buttons)} buttons") # Get count
print(f"Found {buttons.count} buttons") # Alternative count
# Access specific element by index
first_button = buttons[0] # First button
last_button = buttons[-1] # Last button
first_button.click()
# Iterate over all matching elements
for i, button in enumerate(buttons):
print(f"Button {i}: {button.get_text()}")
if button.get_text() == "Submit":
button.click()
breakInstall with Tessl CLI
npx tessl i tessl/pypi-uiautomator2