Table of Contents
Overview
Appium Load Balancer, also known as AppiumLB, is HeadSpin’s solution to more efficient device management when testing with Appium and Selenium tests. Identifying and tracking each individual test target, particularly when you want your tests to cover a variety of geographical regions and device SKUs, can be time-consuming and frustrating. AppiumLB does not function like a traditional load balancer program handling incoming network traffic; rather, it simulates that same balancing logic to API commands and device UDIDs or WebDriver URLs to cut down on time and effort spent on device or host management.
AppiumLB is a single, generic Web Driver URL granting access to any HeadSpin devices available to you. It functions somewhat like a proxy server in that all API commands will touch AppiumLB before touching your device hosts, and AppiumLB will select devices at random that meet your test criteria. Test devices are specified through the new <code class="dcode">headspin:selector</code> capability, using the selector syntax as described in the Selectors DSL docs. AppiumLB automatically picks the proper devices and start a new Appium session with that device's <code class="dcode">udid</code>, <code class="dcode">headspin:appiumVersion</code> and the original <code class="dcode">headspin:selector</code> capabilities. AppiumLB also automatically picks a proper browser device with <code class="dcode">browserName</code>, <code class="dcode">browserVersion</code> and the original <code class="dcode">headspin:selector</code> capabilities for a Selenium session. Using AppiumLB, your testing experience will have minimal incidents of tests failing due to offline or unresponsive devices, reserved devices that are unavailable, or user error in identifying devices. Every request, barring user error in its design, will gain some kind of response and cut down on test troubleshooting. Please note that, as described in the Capabilities section below, you can specify a particular device using the <code class="dcode">udid</code> capability.
Using AppiumLB
To use AppiumLB, set your Web Driver URL as <code class="dcode">https://appium-dev.headspin.io/v0/your-api-token/wd/hub</code> and make sure that the <code class="dcode">headspin:selector</code> capability reflects target devices for your test. The Web Driver URL is available as Load Balanced Driver URL in the automation configuration.
AppiumLB will look for a device or devices matching the selectors, lock the device for automation and start an Appium or a Selenium session against the device's host server. It then returns a response containing the <code class="dcode">sessionId</code> of the newly created session, where you can send Appium or Selenium commands.
Retry and avoid previously failed devices
AppiumLB will retry establishing a new session against available devices in the device pool up to the value of <code class="dcode">headspin:waitForDeviceOnlineTimeout</code>, <code class="dcode">newCommandTimeout</code> or <code class="dcode">headspin:newCommandTimeout</code> before responding to the client. Each retry will avoid previously failed devices if the cause is a device-side issue to make the session creation reliable. For instance, if a device fails due to an unexpected disconnection in the new session request, AppiumLB will avoid the device for selection for the next 10 minutes. AppiumLB can be configured to skip this avoidance behavior with the capability <code class="dcode">headspin:ignoreFailedDevice</code>.
If Appium fails to find an available device in your device pool with the given <code class="dcode">appium:udid</code> or <code class="dcode">headspin:selector</code>, it will respond with an error message that starts with No matched devices were found. It indicates the given udid or selector syntax was wrong or the syntax was correct but no matched devices existed in your device pool.
If AppiumLB fails to find an available device in your device pool, it will respond with an error message that starts with No available good condition devices ... "No available devices" indicates devices are offline, are already locked by a user, must be reserved by you, or failed against a new session request. AppiumLB will respond with an error message if the last retry fails in a new session request. <code class="dcode">headspin:retryNewSessionFailure</code> and <code class="dcode">headspin:waitForDeviceOnlineTimeout</code> help you to control the retry rule.
If no devices are available in the device pool after excluding previously failed devices then AppiumLB will select a device from the device pool without considering the previously failed devices. This behavior helps reduce obstacles in the case of your device pool containing few devices for the given selector(s). The selected device may manifest an error due to its previously failed state.
The error previously failed devices only indicates devices for which AppiumLB failed to create a new session due to a problem on the device. Errors due to other causes (such as invalid Appium capabilities) do not cause devices to be marked as "previously failed".
AppiumLB Capabilities
In addition to the capabilities in Appium Capabilities docs and Selenium Capabilities docs, AppiumLB also support these capabilities:
headspin:selector
- AppiumLB will select devices matching the characteristics in this selector. Read more about HeadSpin selectors in the Selectors Docs.
- The
headspin:selector
format can be JSON object or string as shown in the examples below. The string format can write more complex operations. Please check the available operators in Selectors as a query parameter section in Selectors Docs for more details.
- If a
udid
capability is provided, AppiumLB prioritizes udid
over headspin:selector
. It will select the device matching the udid
, regardless of the device status (i.e even if the device is locked or unavailable).
- The
udid
can also be a string formatted selector. Then, AppiumLB handles it in the same way as headspin:selector
.
- (Deprecated)
headspin:requestTimeout
- Defaults to
120
seconds.
- Please use
newCommandTimeout
or headspin:newCommandTimeout
in Appium or Selenium capabilities instead of headspin:requestTimeout
. AppiumLB will respect newCommandTimeout
and headspin:newCommandTimeout
for Appium and Selenium to configure request timeout between AppiumLB and each appium/selenium server.
headspin:waitForDeviceOnlineTimeout
- AppiumLB will wait for a device to be online until the timeout given in seconds. AppiumLB will wait while the device is in a locked or unavailable status. 86400 (1 day) is the maximum value.
- Defaults to
20
seconds or newCommandTimeout
or headspin:newCommandTimeout
in Appium Capabilities docs or Selenium Capabilities docs capabilities, if provided.
headspin:newSessionRequestProgress
- AppiumLB will send a progress message to the client every 10 seconds during a new session request proceeding in order to avoid the client side HTTP read timeout error. The client will get the whole response body after succeeding or failing the new session request. The response might have a
progress
key in the JSON response body.
- Defaults to
false
. The value is enabled automatically if waitForDeviceOnlineTimeout
was over 1 hour, but you can explicitly disable this feature by giving false
as the value. Once you enable this capability, the new session request will keep the HTTP session up to waitForDeviceOnlineTimeout
or newCommandTimeout
by sending progress messages.
headspin:retryNewSessionFailure
- How AppiumLB will handle a new session request when a new session request to a device got failed because of an error. The default value is
true
. Then, AppiumLB attempts to send a new session request against another available device up to newCommandTimeout
or headspin:newCommandTimeout
. If the value sets as false
, AppiumLB will reply the error message back to the client without any retry as addressed in Retry and avoid previously failed devices. The client can handle any error cases on their side. This false
capability will help when the client wants to use AppiumLB as a device selector but not for request handling.
headspin:deviceStrategy
- How AppiumLB will pick a device from available devices in the device pool.
- Available values are:
ranked
(default): Pick a device deterministically from the device pool.
random
: Pick a device randomly from the device pool.
headspin:ignoreFailedDevices
- AppiumLB will avoid sending a new session request against the list of previously failed devices, which got a failure in their new session request, to prevent continuous session creation failure. Each failed device will be selectable after 10 minutes.
- defaults to
true
.
headspin:skipVendorPrefixDirectconnect
- Prevent adding
appium:
and headspin:
prefixes of directConnect
feature in the response of new session request from AppiumLB. This capability is mostly for webdriverio specific. If you use webdriverio for Selenium automation over AppiumLB, the webdriverio instance thinks the session is isMobil
as true
by referencing the appium:
in the response while the session is not Appium for mobile devices. It could break some Selenium commands. This capability helps to prevent the behavior.
- defaults to
false
.
AppiumLB will restrict target devices if the given capabilities includes <code class="dcode">platformName</code>. If the capability specifies <code class="dcode">Android</code>, AppiumLB will choose a device from Android devices. The capability is necessary for Appium, but not for Selenium. <code class="dcode">browserName</code> and <code class="dcode">browserVersion</code> capabilities will be part of selector for Selenium to detect a browser device.
Examples
Appium
# Selector as JSON object
server_url = 'https://appium-dev.headspin.io/v0/your-api-token/wd/hub'
desired_capability = {
'platformName': 'android',
'automationName': 'uiautomator2',
'browserName': 'Chrome',
'deviceName': 'Android',
'headspin:selector': {
'sku': 'Moto X',
'geo': 'Mountain View',
'carrier': ['Sprint', 'WiFi']
},
'headspin:requestTimeout': 120
}
driver = webdriver.Remote(server_url, desired_capability)
driver.title
# Selector as string with more operators
server_url = 'https://appium-dev.headspin.io/v0/your-api-token/wd/hub'
desired_capability = {
'platformName': 'android',
'automationName': 'uiautomator2',
'browserName': 'Chrome',
'deviceName': 'Android',
'headspin:selector': 'os_version:>9 geos:"Mountain View, US"',
'headspin:requestTimeout': 120
}
driver = webdriver.Remote(server_url, desired_capability)
driver.title
require 'appium_lib_core'
server_url = 'https://appium-dev.headspin.io/v0/your-api-token/wd/hub'
desired_cap = {
platformName: :android,
automationName: 'uiautomator2',
browserName: 'Chrome',
deviceName: 'Android',
udid: 'particular-device-udid' # Can be selector by string like 'udid:"model: \"Nexus 5X\""'
'headspin:requestTimeout' => 120 # sec
}
core = Appium::Core.for url: server_url, desired_capabilities: desired_cap
driver = core.start_driver
driver.title
Selenium
server_url = 'https://appium-dev.headspin.io/v0/your-api-token/wd/hub'
desired_capability = {
'browserName': 'firefox',
}
driver = webdriver.Remote(server_url, desired_capability)
driver.title
require "selenium-webdriver"
server_url = 'https://appium-dev.headspin.io/v0/your-api-token/wd/hub'
desired_cap = {
'headspin:selector' => '(device_skus: "firefox", geos: "Mountain View, US")',
}
driver = Selenium::WebDriver.for :remote, url: server_url, desired_capabilities: desired_cap
driver.title
Resolving Errors
AppiumLB returns standard errors by Appium instances on proxy servers, and also the following:
HTTP status code |
Error (error ) |
Note |
How to Resolve |
400 |
invalid argument |
Request body is invalid in create session command |
Make sure your Appium or Selenium commands are correct. |
404 |
invalid session id |
Cannot find active session id which AppiumLB should proxy commands to |
Run your tests from session creation. |
404 |
unknown command |
The Appium or Selenium command is unknown |
Make sure your Appium or Selenium commands are correct. |
500 |
session not created |
Cannot find available devices in your device pool. |
Make sure there are available devices in your device pool matching the selector. Matching devices might be temporarily locked by other users or unavailable. Please read Retry and avoid previously failed devices to understand how AppiumLB behaves in this case. |
500 |
unknown error |
Something is wrong in AppiumLB |
Please contact us if it happens continuously. |
Advanced Usage
Increasing Network Efficiency With directConnect Capabilities
With AppiumLB, a request goes through an additional AppiumLB server and therefore takes a small amount of additional network time, usually of about 5-10 seconds but potentially up to a minute (see this issue of Appium Pro for more details). If this presents a problem, the <code class="dcode">directConnect</code> capabilities can be used to speed up network time. To use this, your Appium client must support the <code class="dcode">directConnect</code> capabilities and the <code class="dcode">directConnect</code> capability must be set to <code class="dcode">true</code>. Currently only limited clients support this capbility. Please work with your internal IT departments and HeadSpin contacts to determine whether directConnect is a possibility for your testing environment.
A successful request with <code class="dcode">directConnect</code> made to AppiumLB will receive a <code class="dcode">create session</code> response, with information about the session through the <code class="dcode">directConnect</code> capabilities. The capabilities contain hostname, port and path to the actual Appium server with the devices selected for the tests. They are:
appium:directConnectProtocol
and directConnectProtocol
- The connection protocol AppiumLB proxies to. Can be
https
or http
.
appium:directConnectPort
and directConnectPort
- The port number the AppiumLB proxies to
appium:directConnectHost
and directConnectHost
- The hostname the AppiumLB proxies to
appium:directConnectPath
and directConnectPath
- The base path the AppiumLB proxies to
headspin:directConnectDeviceAddress
and directConnectDeviceAddress
- The device the AppiumLB proxies to
- This value is HeadSpin specific attribute. This value is
device_address
as device_id@hostname
to detect the device in HeadSpin platform.
With this information, your Appium client can be configured to communicate with the test device directly.
Example Requests
Below are example requests sent with <code class="dcode">directConnect</code>.
Python
● Python client <code class="dcode">0.39+</code>
- documentation
- <code class="dcode">direct_connection</code> is enabled by default.
# Python
remote_url = 'https://appium-dev.headspin.io/v0/{your_api_token}/wd/hub'
direct_connection=True # This is necessary to enable the directConnect feature.
base_capability_chrome = {
'platformName': 'Android',
'browserName': 'Chrome',
'deviceName': 'Android',
'autoAcceptsAlerts': 'true',
'automationName': 'uiautomator2',
'headspin:selector': {
'geo': 'Mountain View'
}
}
driver = webdriver.Remote(self.remote_url.format(host=self.appiumlb, api_token=self.api_token),
caps, direct_connection=direct_connection)
# For Python client 2.3.0+
# from appium.options.android import UiAutomator2Options
# options = UiAutomator2Options().load_capabilities({
# 'platformVersion': '12',
# 'deviceName': 'Android',
# 'browserName': 'chrome',
# 'headspin:selector': {
# 'geo': 'Mountain View'
# }
# })
#
# self.driver = webdriver.Remote(self.remote_url.format(host=self.appiumlb, api_token=self.api_token),
# options=options, direct_connection=direct_connection
# )
driver.title
driver.capabilities['directConnectDeviceAddress'] # u'device_id@proxy-us-sf-16.headspin.io'
Ruby
● ruby_lib <code class="dcode">10.0.0+</code>
● ruby_lib_core <code class="dcode">3.0.1+</code>
- documentation
- <code class="dcode">direct_connect</code> is enabled by default.
# ruby_lib_core
caps = {
platformName: 'ios',
platformVersion: '11.0',
deviceName: 'iPhone Simulator',
automationName: 'XCUITest',
app: '/path/to/MyiOS.app'
}
appium_lib = { direct_connect: true }
# Ruby client sends a create session command to
# https://appium-dev.headspin.io/v0/your-api-token/wd/hub (AppiumLB)
@core = Appium::Core.for url: 'https://appium-dev.headspin.io/v0/your-api-token/wd/hub', capabilities: caps, appium_lib: appium_lib
driver = core.start_driver
driver.capabilities['directConnectDeviceAddress'] #=> 'device_id@proxy-us-sf-16.headspin.io'
driver.capabilities
#=> #<Selenium::WebDriver::Remote::Capabilities:0x00007f9bfe8ae7a0
# @capabilities=
# {:browser_name=>"Chrome",
# ...
# "directConnectProtocol"=>"https",
# "directConnectPort"=>7200,
# ...
# "directConnectHost"=>"proxy-us-sf-16.headspin.io",
# ...
# "directConnectPath"=>"/v0/your-token/wd/hub"}>
# Ruby client sends a get page source command to
# https://proxy-us-sf-16.headspin.io:7200/v0/your-api-token/wd/hub
driver.page_source
#=> "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>\r\n<hierarchy...
Java
● Java client <code class="dcode">8.2.1+</code>
AppiumClientConfig appiumClientConfig = AppiumClientConfig.defaultConfig()
.directConnect(true)
.baseUri(URI.create("https://appium-dev.headspin.io/v0/your-api-token/wd/hub"))
UiAutomator2Options options = new UiAutomator2Options();
AndroidDriver driver = new AndroidDriver(appiumClientConfig, options);
JavaScript (webdriverio)
● webdriverio <code class="dcode">v7.16.14+</code>
- enableDirectConnect option configures the availability.
- <code class="dcode">enableDirectConnect</code> is enabled by default.
Example Responses
Below are some example responses as W3C or MJOSNWP with a session creation command.
W3C create session response
{
"value": {
"capabilities": {
...
"platformName":"ios",
"automationName":"XCUITest",
"app":"your/test/app",
"platformVersion":"11.4",
"deviceName":"iPhone 8",
"udid":"8CB5BE32-3600-4007-B894-13E48EFB4D65",
...
"directConnectProtocol": "https",
"directConnectPort": 7200,
"directConnectHost": "proxy-us-sf-16.headspin.io",
"directConnectPath": "/v0/your-api-token/wd/hub",
"directConnectDeviceAddress": "device_id@proxy-us-sf-16.headspin.io"
},
"sessionId":"85447dd9-cfc2-4091-9851-9eb738681ff7"
}
}
MJSONWP create session response
{
"status": 0,
"value": {
...
"platformName":"ios",
"automationName":"XCUITest",
"app":"your/test/app",
"platformVersion":"11.4",
"deviceName":"iPhone 8",
"udid":"8CB5BE32-3600-4007-B894-13E48EFB4D65",
...
"directConnectProtocol": "https",
"directConnectPort": 7200,
"directConnectHost": "proxy-us-sf-16.headspin.io",
"directConnectPath": "/v0/your-api-token/wd/hub",
"directConnectDeviceAddress": "device_id@proxy-us-sf-16.headspin.io"
},
"sessionId": "8cb4b6d4-d9e7-4da0-b5da-04efbbd09af3"
}
Selenium Grid and AppiumLB
Selenium Grid (hub) and AppiumLB behave similarly.
Selenium Grid has a hub and nodes. A node is a Selenium driver or an Appium server hosting a device. The hub handles a new session request to a proper node in order to establish a session following the capabilities. The node responds to the new session request with a session ID. Then, the hub proxies requests that have the same session ID to the proper node. This helps clients handle various nodes behind the hub since they need to know only one WebDriver URL through the hub. Clients do not need to know each WebDriver URL behind the hub for each separate Selenium driver and Appium server.
The Selenium Grid hub and node behavior and relationships are similar to AppiumLB and available devices in your device pool. AppiumLB picks a device up from your device pool in a new session request. It has the ability to wait for a device to be available using the <code class="dcode">waitForDeviceOnlineTimeout</code> capability. Once the new session request succeeds, commands for the same session ID can reach the device until the session is expired. One unique feature in AppiumLB is its directConnect capabilities to reduce network latency between the client and the device after a new session request. This feature allows clients to communicate with each PBox directly after a new session request. With direct connect, only the first new session request may have additional network latency due to routing through the load balancer.
A client needs to keep the HTTP connection to get a response by Selenium Grid or AppiumLB. A new session request tends to have a longer timeout than other commands since it includes some device setup processes. Client read timeouts before getting the response for a new session request can cause a new session creation failure. <code class="dcode">newSessionWaitTimeout</code> in Selenium Grid is similar to <code class="dcode">waitForDeviceOnlineTimeout</code> in AppiumLB to attempt to find an available node or device to coordinate the new session request.