Skip to content

Mobile Testing with Orbs

Complete guide to Android mobile automation using Orbs and Appium.


Table of Contents


Introduction

Orbs provides mobile automation capabilities through Appium integration, supporting Android devices and emulators. The framework handles:

  • Automatic Appium server management - Starts server if not running
  • Device detection and selection - Interactive device picker
  • Thread-safe execution - Parallel mobile test support
  • Smart element waiting - Automatic waits with retries
  • Unified API - Similar to web automation for easy learning

Prerequisites

System Requirements

  • Operating System: Windows, macOS, or Linux
  • Node.js: v14+ (installed automatically via orbs setup android)
  • npm: Latest version
  • Java JDK: 8 or higher
  • Android SDK: Android Studio or command-line tools

Environment Variables

Add to system PATH:

Windows:

JAVA_HOME=C:\Program Files\Java\jdk-11.0.x
ANDROID_HOME=C:\Users\<username>\AppData\Local\Android\Sdk

PATH=%JAVA_HOME%\bin;%ANDROID_HOME%\platform-tools;%ANDROID_HOME%\tools

Mac/Linux:

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home
export ANDROID_HOME=~/Library/Android/sdk
export PATH=$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH

Quick Start

1. Setup Android Environment

orbs setup android

This command will: * ✅ Install Node.js (if not present) * ✅ Install Appium globally * ✅ Install Appium UIAutomator2 driver * ✅ Verify all installations

2. Initialize Project

orbs init mobile-automation
cd mobile-automation

3. Connect Device

Connect Android device via USB or start emulator:

# Check connected devices
adb devices

# Start emulator (if using AVD)
emulator -avd Pixel_4_API_30

4. Select Device

orbs select-device

Interactive menu:

? Select device: 
  emulator-5554
❯ R58M40ABCDE

✅ Updated deviceName=R58M40ABCDE in settings/appium.properties

5. Configure Appium

Edit settings/appium.properties:

# Device configuration
deviceName=R58M40ABCDE
platformName=Android
platformVersion=12.0

# App configuration
appPackage=com.example.app
appActivity=.MainActivity

# Or use app file path
# app=/path/to/app.apk

# Appium server
appium_url=http://localhost:4723/wd/hub

# Additional capabilities
automationName=UiAutomator2
noReset=true
fullReset=false

6. Create Test Case

orbs create-testcase android_login

Edit testcases/android_login.py:

from orbs.keyword.mobile import Mobile

def test_android_login():
    """Test Android app login"""

    # Wait for app to load
    Mobile.wait_for_element("id=com.example.app:id/username", timeout=10)

    # Enter credentials
    Mobile.set_text("id=com.example.app:id/username", "testuser")
    Mobile.set_text("id=com.example.app:id/password", "password123")

    # Click login
    Mobile.tap("id=com.example.app:id/login_button")

    # Verify home screen
    Mobile.wait_for_element("id=com.example.app:id/home_screen", timeout=10)
    assert Mobile.element_visible("id=com.example.app:id/home_screen")

    print("✅ Android login test passed")

7. Run Test

orbs run testsuites/login.yml --platform=android

Configuration

Appium Properties

Location: settings/appium.properties

Basic Configuration

# Required
deviceName=emulator-5554
platformName=Android
platformVersion=11.0

# App identification (choose one)
appPackage=com.android.calculator2
appActivity=com.android.calculator2.Calculator

# Or use APK file
# app=/absolute/path/to/app.apk

Advanced Capabilities

# Automation engine
automationName=UiAutomator2

# Reset behavior
noReset=true          # Don't reset app state between sessions
fullReset=false       # Don't uninstall app

# Timeouts (milliseconds)
newCommandTimeout=300000

# Performance
skipServerInstallation=true
skipDeviceInitialization=false

# Logging
printPageSourceOnFindFailure=true

Appium Server Configuration

# Server URL
appium_url=http://localhost:4723/wd/hub

# Or remote server
# appium_url=http://192.168.1.100:4723/wd/hub

Platform Configuration

Location: settings/platform.properties

default_platform=android

Mobile Keywords

App Control

Mobile.launch(id=None, activity=None, reset=False)

Launch an application with optional package ID and activity.

# Launch from config
Mobile.launch()

# Launch specific app
Mobile.launch("com.android.chrome", "com.google.android.apps.chrome.Main")

# Launch with reset
Mobile.launch("com.swaglabsmobileapp", ".MainActivity", reset=True)

# Keyword args
Mobile.launch(id="com.android.chrome", activity=".Main", reset=True)

Mobile.launch_and_install(apk, id=None, activity=None, reset=False)

Install APK and launch the application.

# Install and launch
Mobile.launch_and_install("/apps/myapp.apk")

# Install with specific launch params
Mobile.launch_and_install(
    apk="/apps/chrome.apk",
    id="com.android.chrome",
    activity=".Main",
    reset=True
)

Mobile.activate_app(bundle_id)

Activate app by bundle ID.

Mobile.activate_app("com.android.chrome")

Mobile.terminate_app(bundle_id)

Terminate app by bundle ID.

Mobile.terminate_app("com.android.chrome")

Element Interaction

Mobile.tap(locator, timeout=10, retry_count=3)

Tap on an element with retry logic.

Mobile.tap("id=com.example.app:id/submit_button")
Mobile.tap("xpath=//android.widget.Button[@text='Login']")

Mobile.set_text(locator, text, timeout=10, clear_first=True, retry_count=3)

Enter text into an element with retry logic.

Mobile.set_text("id=com.example.app:id/email", "test@example.com")
Mobile.set_text("xpath=//android.widget.EditText[@content-desc='Username']", "admin")

Mobile.clear_text(locator, timeout=10)

Clear text from an element.

Mobile.clear_text("id=com.example.app:id/search_field")

Mobile.long_press(locator, duration=1000, timeout=10)

Long press on an element.

Mobile.long_press("id=com.example.app:id/item", duration=2000)

Element State

Mobile.element_visible(locator, timeout=10) -> bool

Check if element is visible.

if Mobile.element_visible("id=com.example.app:id/error_message"):
    print("Error displayed")

Mobile.element_exists(locator, timeout=10) -> bool

Check if element exists.

if Mobile.element_exists("id=com.example.app:id/submit"):
    Mobile.tap("id=com.example.app:id/submit")

Mobile.wait_for_element(locator, timeout=10)

Wait for element to be present.

Mobile.wait_for_element("id=com.example.app:id/loading", timeout=30)

Mobile.wait_for_visible(locator, timeout=10)

Wait for element to be visible.

Mobile.wait_for_visible("id=com.example.app:id/success_dialog")

Element Properties

Mobile.get_text(locator, timeout=10) -> str

Get text from element.

message = Mobile.get_text("id=com.example.app:id/notification")
assert message == "Upload successful"

Mobile.get_attribute(locator, attribute, timeout=10) -> str

Get attribute value.

enabled = Mobile.get_attribute("id=com.example.app:id/btn", "enabled")
content_desc = Mobile.get_attribute("xpath=//android.widget.Button", "content-desc")

Gestures

Mobile.swipe(start_x, start_y, end_x, end_y, duration=1000)

Swipe gesture from start to end coordinates.

# Swipe right
Mobile.swipe(100, 500, 900, 500, duration=800)

# Swipe up (scroll)
Mobile.swipe(500, 1500, 500, 500, duration=600)

Mobile.swipe_up(start_x=None, distance=500, duration=1000)

Swipe up from bottom of screen.

Mobile.swipe_up()
Mobile.swipe_up(distance=800)

Mobile.swipe_down(start_x=None, distance=500, duration=1000)

Swipe down from top of screen.

Mobile.swipe_down()
Mobile.swipe_down(distance=800)

Mobile.swipe_left(start_y=None, distance=300, duration=1000)

Swipe left.

Mobile.swipe_left()

Mobile.swipe_right(start_y=None, distance=300, duration=1000)

Swipe right.

Mobile.swipe_right()

Mobile.scroll_to_element(locator, max_scrolls=5, direction='up')

Scroll until element is found.

Mobile.scroll_to_element("id=com.example.app:id/settings")
Mobile.scroll_to_element("accessibility_id=Submit", direction="down")

Device Interaction

Mobile.back()

Press Android back button.

Mobile.back()

Mobile.hide_keyboard()

Hide on-screen keyboard.

Mobile.hide_keyboard()

Screen Capture

Mobile.take_screenshot(filepath)

Capture screenshot.

Mobile.take_screenshot("screenshots/login_screen.png")

Locator Strategies

Orbs supports multiple Android locator strategies:

Strategy Syntax Example
Resource ID id=resource-id id=com.example.app:id/button
XPath xpath=xpath-expression xpath=//android.widget.Button[@text='Login']
Accessibility ID accessibility_id=content-desc accessibility_id=Submit Button
Class Name class=class-name class=android.widget.Button
Android UIAutomator android_uiautomator=UiSelector android_uiautomator=new UiSelector().text("Login")

Examples:

# By resource ID (most stable)
Mobile.tap("id=com.android.calculator2:id/digit_5")

# By XPath
Mobile.tap("xpath=//android.widget.Button[@text='Calculate']")

# By content-desc (accessibility)
Mobile.tap("accessibility_id=Submit Form")

# By class
Mobile.tap("class=android.widget.EditText")

# By UIAutomator
Mobile.tap("android_uiautomator=new UiSelector().text('Login')")

Writing Test Cases

Python Test Case

from orbs.keyword.mobile import Mobile

def test_calculator_addition():
    """Test calculator addition"""

    # Wait for app to load
    Mobile.wait_for_element("id=com.android.calculator2:id/digit_1", timeout=10)

    # Perform calculation: 5 + 3 = 8
    Mobile.tap("id=com.android.calculator2:id/digit_5")
    Mobile.tap("id=com.android.calculator2:id/op_add")
    Mobile.tap("id=com.android.calculator2:id/digit_3")
    Mobile.tap("id=com.android.calculator2:id/eq")

    # Verify result
    result = Mobile.get_text("id=com.android.calculator2:id/result")
    assert result == "8", f"Expected 8, got {result}"

    print("✅ Calculator test passed")

def test_form_submission():
    """Test form submission flow"""

    # Fill form
    Mobile.set_text("id=com.example.app:id/name", "John Doe")
    Mobile.set_text("id=com.example.app:id/email", "john@example.com")
    Mobile.hide_keyboard()

    # Scroll to submit button
    Mobile.scroll_to_element("id=com.example.app:id/submit")

    # Submit form
    Mobile.tap("id=com.example.app:id/submit")

    # Wait for confirmation
    Mobile.wait_for_visible("id=com.example.app:id/success_message", timeout=10)

    # Verify message
    message = Mobile.get_text("id=com.example.app:id/success_message")
    assert "submitted successfully" in message.lower()

    print("✅ Form submission test passed")

Test Suite Example

testsuites/mobile_regression.yml:

name: Mobile Regression Suite
description: Android app regression tests

testcases:
  - path: testcases/android_login/test_android_login
    enable: true
  - path: testcases/calculator/test_calculator_addition
    enable: true
  - path: testcases/forms/test_form_submission
    enable: false

Run:

orbs run testsuites/mobile_regression.yml --platform=android

Device Management

List Connected Devices

adb devices

Output:

List of devices attached
emulator-5554          device
R58M40ABCDE            device

Select Device Interactively

orbs select-device

Specify Device in Test Run

orbs run testsuites/Mobile.yml --platform=android --deviceId=emulator-5554

Multiple Device Testing

Run tests on different devices in parallel:

# Terminal 1
orbs run testsuites/suite1.yml --platform=android --deviceId=emulator-5554

# Terminal 2
orbs run testsuites/suite2.yml --platform=android --deviceId=R58M40ABCDE

Best Practices

1. Use Resource IDs When Possible

✅ Stable:

Mobile.tap("id=com.example.app:id/login_button")

❌ Fragile:

Mobile.tap("xpath=/hierarchy/android.widget.FrameLayout[1]/android.widget.Button[3]")

2. Handle Keyboard

Always hide keyboard after text input:

Mobile.set_text("id=email_field", "test@example.com")
Mobile.hide_keyboard()

3. Use Explicit Waits

Mobile.wait_for_visible("id=success_message", timeout=15)

4. Page Object Pattern

# pages/login_page.py
from orbs.keyword.mobile import Mobile

class LoginPage:
    USERNAME = "id=com.example.app:id/username"
    PASSWORD = "id=com.example.app:id/password"
    LOGIN_BTN = "id=com.example.app:id/login_button"

    @staticmethod
    def login(username, password):
        Mobile.set_text(LoginPage.USERNAME, username)
        Mobile.set_text(LoginPage.PASSWORD, password)
        Mobile.hide_keyboard()
        Mobile.tap(LoginPage.LOGIN_BTN)

5. Error Handling

def test_with_retry():
    max_retries = 3
    for attempt in range(max_retries):
        try:
            Mobile.tap("id=unstable_element")
            break
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            print(f"Retry {attempt + 1}/{max_retries}")
            time.sleep(2)

6. Screenshot on Failure

def test_login():
    try:
        Mobile.tap("id=login_button")
        # test logic
    except Exception as e:
        Mobile.take_screenshot("screenshots/failure.png")
        raise

Troubleshooting

Appium Server Not Starting

Check installation:

appium --version

Reinstall:

orbs setup android

Start manually:

appium --address 0.0.0.0 --port 4723

ADB Not Found

Verify PATH:

adb version

Add to PATH (Windows):

C:\Users\<username>\AppData\Local\Android\Sdk\platform-tools

Device Not Detected

Check USB debugging: * Enable Developer Options on device * Enable USB Debugging * Accept debugging authorization prompt

Verify connection:

adb devices

Restart ADB:

adb kill-server
adb start-server

Element Not Found

Solutions: * Increase timeout * Use Appium Inspector to verify locators * Wait for app to load completely * Check if element is in different activity

Session Creation Failed

Check capabilities: * Verify appPackage and appActivity are correct * Ensure app is installed: adb shell pm list packages | grep example * Check platform version matches device

View logs:

adb logcat

Slow Test Execution

Optimize: * Reduce unnecessary waits * Use noReset=true to skip reinstall * Disable animations on device * Use faster emulator (x86 images with HAXM)


Advanced Topics

Device Information

# Get screen size
size = Mobile.get_device_size()
print(f"Width: {size['width']}, Height: {size['height']}")

# Get/Set orientation
orientation = Mobile.get_orientation()  # PORTRAIT or LANDSCAPE
Mobile.set_orientation("LANDSCAPE")

Driver Management

# Check driver status
status = Mobile.get_driver_status()

# Reset driver between tests
Mobile.reset_driver()

# Quit session
Mobile.quit()

Parallel Execution

Run multiple test suites in parallel using different devices:

# Use orchestration tool or CI/CD
pytest -n 2 --deviceId=emulator-5554,R58M40ABCDE

Next Steps