Appium and WebDriver

Run droidline webdriver and point Appium clients at it. Existing Appium scripts in Python, Java, JavaScript and other languages drive your phones through Droidline, without ADB.

droidline webdriver speaks the W3C WebDriver protocol with Appium's Android additions. Point an Appium client at it, and scripts written for Appium drive your phones through Droidline: same locators, same element commands, no ADB and no Appium server.

It is optional and runs only while the command runs. Your Droidline scripts do not need it.

Start it

The Droidline server must be running. In a second terminal:

droidline webdriver
WebDriver bridge on http://127.0.0.1:4723
Use it as the Appium server URL. Stop with Ctrl+C.

Use http://127.0.0.1:4723 wherever your client asks for the Appium server. --listen picks another address; keep it on 127.0.0.1, because the bridge has no password.

Connect

# pip install Appium-Python-Client
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy

opts = UiAutomator2Options()
opts.set_capability("appium:udid", "shelf-01")
opts.set_capability("appium:appPackage", "com.android.settings")
d = webdriver.Remote("http://127.0.0.1:4723", options=opts)

d.find_element(AppiumBy.XPATH, "//*[@text='Network & internet']").click()
d.find_element(AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("Wi-Fi")')
d.quit()
import { remote } from "webdriverio";

const d = await remote({
  hostname: "127.0.0.1",
  port: 4723,
  capabilities: { platformName: "Android", "appium:udid": "shelf-01" },
});
await d.$("~More options").click();
await d.deleteSession();

The Appium Python client above was tested against this bridge on a phone. Other clients that speak the same protocol, such as WebdriverIO, the Java and .NET clients, Robot Framework's AppiumLibrary and Appium Inspector, connect the same way. When a client sends a command the bridge does not have, it answers unknown command and names it.

Capabilities

CapabilityWhat it does
platformNameAndroid, or leave it out.
appium:udid or droidline:deviceThe phone, by ID or name. Without one, the only online phone.
appium:deviceNameUsed only if a paired phone has that name or ID; anything else is ignored.
appium:appPackage, appium:appActivityStart this app. Nothing is reset or cleared. mobile: clearApp clears the app's data, in device owner mode only.
appium:newCommandTimeoutEnd the session after this many seconds without a command. Default 60; 0 never.
droidline:leasetrue leases a free phone for the session and gives it back when the session ends.

Locators

StrategyMatches
xpathXPath over the page source, which has the same tags and attributes as UiAutomator2 (@text, @resource-id, @content-desc, @class, @bounds, the flags).
idresource-id, full or just the name after :id/.
accessibility idcontent-desc.
class nameThe class, such as android.widget.Button.
-android uiautomatornew UiSelector() with text, textContains, textStartsWith, textMatches, description and its variants, resourceId, className, packageName, the boolean flags, instance, childSelector and fromParent, plus new UiScrollable(...).scrollIntoView(...) and scrollTextIntoView(...).
-droidline queryA Droidline query as JSON, for example {"class":"android.widget.Switch","row":{"text":"Wi-Fi"}}.
nametext.

The implicit wait you set with implicitly_wait applies to every strategy; it is 0 until you set it, as in WebDriver.

What works

  • Elements: find one or many, also inside another element; click, send_keys, clear, text, get_attribute, rect, is_displayed, is_enabled, is_selected, and element screenshots. An element that left the screen raises a stale element error.
  • Page source, screenshots and the window size.
  • W3C actions with one finger: tap, long press and swipe. Key actions type text and press Enter, Backspace and Tab; Escape presses Back.
  • App and device calls: activate_app, query_app_state, press_keycode, hide_keyboard, is_keyboard_shown, current_activity, current_package, open_notifications, lock, unlock.
  • Scripts: mobile: clearApp (device owner mode only), mobile: activateApp, mobile: pressKey, mobile: clickGesture, mobile: longClickGesture, and mobile: droidline, which runs any Droidline command:
d.execute_script("mobile: droidline", {"cmd": "wait_idle"})
d.execute_script("mobile: droidline", {"cmd": "proxy_check"})

What is different from Appium

Droidline does not use ADB, so anything that needs it is missing: installing and uninstalling apps, mobile: shell, pushing and pulling files, logcat, and WEBVIEW contexts. Also:

  • send_keys replaces the field's text, as Droidline's input does.
  • Gestures use one finger; multi-touch actions fail.
  • appium:noReset and appium:fullReset are ignored. The bridge never clears data by itself.
  • terminate_app and mobile: terminateApp answer an unsupported operation error. To force stop an app, use the Settings recipe.
  • mobile: clearApp runs clear_data, so it works only in device owner mode.