Appium Testing with DeviceLab

Run Appium tests on remote Android and iOS devices with DeviceLab. This guide covers WebDriver setup, capability configuration, and CI/CD integration. DeviceLab handles app installation and device allocation automatically - your existing tests work with minimal changes.

Overview

DeviceLab lets you run Appium tests on remote devices. The app file is transferred securely to the device node and automatically configured - you don’t need to handle app installation in your test code.

Key points:

  • Pass your app to the test node via --app flag
  • DeviceLab handles app installation automatically
  • Your tests connect to http://localhost:4723/wd/hub - no app path needed in test code

How It Works

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Your Machine  │     │  DeviceLab      │     │  Device Node    │
│                 │     │  Server         │     │  (Remote)       │
├─────────────────┤     ├─────────────────┤     ├─────────────────┤
│ 1. Run testNode │────▶│ 2. Routes to    │────▶│ 3. Receives APK │
│    with --app   │     │    device node  │     │    & starts     │
│                 │     │                 │     │    Appium       │
│                 │     │                 │     │                 │
│ 4. Run tests    │◀────│◀────────────────│◀────│ 5. Appium ready │
│    (localhost)  │     │   (tunneled)    │     │    on device    │
└─────────────────┘     └─────────────────┘     └─────────────────┘

Quick Start

Prerequisites

  • DeviceLab account with install key
  • Device node running with connected Android device
  • Java 8+ and Maven (for Java tests) OR Python 3+ (for Python tests)
  • Appium client library in your project

Step 1: Start Appium Server via DeviceLab

bash
curl -fsSL https://app.devicelab.dev/test-node/YOUR_ORG_KEY | sh -s -- \
  --framework appium \
  --app ./path/to/your/app.apk

Wait for: ✅ Appium server ready on http://localhost:4723

Step 2: Run Your Tests

In a separate terminal:

bash
# Java/Maven
mvn clean test

# Python
pytest

Writing Your Tests

Important: DO NOT Set App Capability

DeviceLab automatically handles the app. Your test code should NOT include:

java
// ❌ WRONG - Don't do this
caps.setCapability("app", "/path/to/app.apk");
caps.setCapability("appium:app", "/path/to/app.apk");

Java Example (Correct)

java
import io.appium.java_client.android.AndroidDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.net.URL;

public class BaseTest {
    protected AndroidDriver driver;

    public void setUp() throws Exception {
        DesiredCapabilities caps = new DesiredCapabilities();

        // ✅ Set these capabilities
        caps.setCapability("platformName", "Android");
        caps.setCapability("automationName", "UiAutomator2");

        // ❌ DO NOT set "app" - DeviceLab handles it automatically
        // caps.setCapability("app", "..."); // REMOVE THIS

        // Optional: Set app package/activity if needed
        caps.setCapability("appPackage", "com.example.myapp");
        caps.setCapability("appActivity", "com.example.myapp.MainActivity");

        // Connect to DeviceLab's local Appium server
        driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), caps);
    }

    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Python Example (Correct)

python
from appium import webdriver
from appium.options.android import UiAutomator2Options

class TestBase:
    def setup_method(self):
        options = UiAutomator2Options()

        # ✅ Set these capabilities
        options.platform_name = "Android"

        # ❌ DO NOT set "app" - DeviceLab handles it automatically
        # options.app = "/path/to/app.apk"  # REMOVE THIS

        # Optional: Set app package/activity if needed
        options.app_package = "com.example.myapp"
        options.app_activity = "com.example.myapp.MainActivity"

        # Connect to DeviceLab's local Appium server
        self.driver = webdriver.Remote(
            "http://localhost:4723/wd/hub",
            options=options
        )

    def teardown_method(self):
        if self.driver:
            self.driver.quit()

Capabilities Reference

Required Capabilities

Capability Value Description
platformName Android Target platform
automationName UiAutomator2 Automation engine

Optional Capabilities

Capability Example Description
appPackage com.example.app App package name
appActivity com.example.MainActivity Launch activity
noReset true Don’t reset app state
fullReset false Don’t uninstall app
newCommandTimeout 300 Session timeout (seconds)

Capabilities Set by DeviceLab (DO NOT Override)

These are automatically configured by DeviceLab:

Capability Description
appium:app Path to APK on device node
appium:udid Device serial number
appium:systemPort UiAutomator2 port (Android)
appium:wdaLocalPort WebDriverAgent port (iOS)
appium:mjpegServerPort Screenshot streaming port

Command Line Options

bash
curl -fsSL https://app.devicelab.dev/test-node/YOUR_ORG_KEY | sh -s -- \
  --framework appium \
  --app ./MyApp.apk \
  [OPTIONS]
Option Description Default
--framework appium Use Appium framework Required
--app <path> Path to APK/IPA file Required
--platform android Target platform android
--device-names <name> Specific device name Any available
--device-count <n> Number of devices 1

Sample Project Structure

my-appium-tests/
├── src/
│   └── test/
│       └── java/
│           ├── BaseTest.java      # Driver setup (NO app capability)
│           └── MyAppTest.java     # Your test cases
├── pom.xml                        # Maven dependencies
└── MyApp.apk                      # Your Android app

pom.xml Dependencies

xml
<dependencies>
    <dependency>
        <groupId>io.appium</groupId>
        <artifactId>java-client</artifactId>
        <version>8.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.15.0</version>
    </dependency>
    <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>7.8.0</version>
    </dependency>
</dependencies>

Troubleshooting

“App not found” Error

Cause: You set app capability in your test code, overriding DeviceLab’s configuration.

Fix: Remove app capability from your test code.

“Could not start session” Error

Cause: Appium server not ready or wrong URL.

Fix:

  1. Wait for ✅ Appium server ready message
  2. Use http://localhost:4723/wd/hub as server URL

“No device available” Error

Cause: No device node running or all devices busy.

Fix:

  1. Start a device node: curl -fsSL https://app.devicelab.dev/device-node/YOUR_ORG_KEY | sh
  2. Check dashboard for available devices

Connection Timeout

Cause: Network issues or server not responding.

Fix:

  1. Check if test node is still running
  2. Check firewall settings

CI/CD Integration

Use the DeviceLab Appium Action for GitHub Actions.

Basic Example

yaml
name: Mobile Testing
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Your build steps here...

      - name: Upload APK
        uses: actions/upload-artifact@v4
        with:
          name: app-release
          path: path/to/your/app.apk

  test:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4

      - name: Start DeviceLab Appium
        uses: izinga/devicelab_appium@v1
        with:
          devicelab-url: 'https://app.devicelab.dev/test-node/YOUR_ORG_KEY'
          apk-artifact-name: 'app-release'

      - name: Run Appium Tests
        run: |
          # Your tests connect to http://localhost:4723/wd/hub
          pytest tests/

Advanced Example

yaml
- name: Start DeviceLab Appium
  id: testnode
  uses: izinga/devicelab_appium@v1
  with:
    devicelab-url: 'https://app.devicelab.dev/test-node/YOUR_ORG_KEY'
    apk-artifact-name: 'my-app-build'
    apk-path: 'app-debug.apk'
    test-node-port: '4723'
    wait-timeout: '600'

- name: Run Tests
  run: |
    echo "Test node running at: ${{ steps.testnode.outputs.test-node-url }}"
    pytest tests/

- name: Cleanup
  if: always()
  run: |
    docker stop ${{ steps.testnode.outputs.container-name }} || true

Action Inputs

Input Required Default Description
devicelab-url Yes - DeviceLab test node URL
apk-artifact-name Yes - Name of artifact containing APK
apk-path No app-release.apk Path to APK within artifact
test-node-port No 4723 Port for test node server
wait-timeout No 300 Timeout in seconds

Action Outputs

Output Description
test-node-url URL of the running test node
container-name Docker container name

Requirements

  • Linux runner (Ubuntu recommended)
  • Docker available in the runner
  • APK artifact from a previous job

Best Practices

  1. Don’t set app path - Let DeviceLab handle it via --app flag
  2. Use localhost:4723/wd/hub - DeviceLab tunnels the connection
  3. Set reasonable timeouts - newCommandTimeout: 300 recommended
  4. Use appPackage/appActivity - For faster app launch
  5. Handle driver cleanup - Always call driver.quit() in teardown

Next Steps