Appium Linux Driver
This is Appium driver for automating linux applications using Linux's AtSpi2 framework. The driver operates in scope of W3C WebDriver protocol with several custom extensions to cover operating-system specific scenarios. Yaoshi technical consultant sponses this project and contributed the underlying linux automation framework. The automation framework is distributed as debian installer.
Requirements
Running a linux automated test requires to install the following three components:
- The forked version of Appium: https://github.com/fantonglang/appium
- This linux driver: https://github.com/fantonglang/appium-linux-driver
- Yaoshi's automation framework
Install and run forked appium
git clone https://github.com/fantonglang/appium
cd appium
yarn install
node ./
The linux driver is referenced as dependency in appium, so no need to install it explicitly.
Yaoshi automation framework
It has been tested on Ubuntu 20.04/18.04 LTS. Here is the download link for installer. Install it with the following instructions:
sudo apt-get install gdebi-core
sudo gdebi stdspalinux-ubuntu_18_04.deb
We have plan to publish the installer for deepin, and also docker image for test automation in the cloud native way.
Any requests, such as support another linux distribution; extend user interaction gestures; build a commercial product with better performance and more capabilities, please always feel free to contact me:
Name | ||
---|---|---|
Fan Tong | zhao_sun@hotmail.com | S597653509 |
UI Inspector
A custom UI inspector is provided for users. It can:
- List all the running windows with their names. Window name is used as parameter to switch between windows.
- Show XML UI hierachy of a window, and validate XPath. The user experience is inspired by Appium Inspector.
Capabilities
Capability Name | Description |
---|---|
platformName | Should be set to linux
|
automationName | Must always be set to atspi2 . Values of automationName are compared case-insensitively. |
appium:appName | The path of the application to automate, for example /bin/yelp , or short-hand yelp because /bin is in the path environment variable. This is a required capability. If the given application is not installed then an error will be thrown on session startup. If the application is already running then it will be killed and restarted. |
Element Attributes
Linux driver supports the following element attributes:
Name | Description | Example |
---|---|---|
rect | Coordinates of bounding element rectangle | {x: 1, y: 2, width: 100, height: 200} |
size | Size of bounding element rectangle | {width: 100, height: 200} |
name | Element's name value. Could be empty | 'my name' |
text | Element's text value. Could be empty | 'my text' |
More attributes could be visited via the WebElement.get_property(name: str)
API. Here is an example source of UI element: <tree-item name="package.json" pid="2575" toolkit="Chromium__1.0" states="[ENABLED,FOCUSABLE,SELECTABLE,SELECTED,SENSITIVE,SHOWING,VISIBLE]" rect="[48,746,368,22]" text-align="left" explicit-name="true" class="monaco-list-row focused selected" display="block" posinset="9" level="2" xml-roles="treeitem" tag="div" id="list_id_2_37" setsize="11" />
.
To get the states of an element, use the property states
. e.g. to judge if the element is selected or not, write:
if contains(el.get_property('states'), 'SELECTED'):
print('element is selected')
Element attribute values could be retrieved from the XML page source. UI inspector can also visualize element's attributes. All attributes which present in the XML page source could be used for elements location, including but not limited to the attributes in the table.
Element Location
Linux driver supports the following location strategies:
Name | Description | Example |
---|---|---|
name | The name strategy uses element's name attribute for lookup. |
driver.find_element(AppiumBy.NAME, 'Find') |
xpath | For elements lookup Xpath strategy the driver uses the same XML tree that is generated by page source API. Only Xpath 1.0 is supported (based on xpath.js). | driver.find_element(AppiumBy.XPATH, "//document-web") |
Check the integration tests for more examples on different location strategies usage.
Other Appium Standard APIs (Python webdriver client)
Element: Set Text / Set Value
Enter text or value in the input element
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
keys | string | yes | The string value to enter in the input element. | 'example text' |
Example
el.set_text('example text')
el.set_value(12)
Element: Click
Perform click gesture on an element
Arguments
No Arguments
Example
el.click()
Press Key Code
Perform press key code gesture. Key code like 'F1', 'F2', 'Delete', 'Return', 'Up', 'Down', 'Left', 'Right'. Combination key can be performed by combining the metastate like 'CONTROL', 'ALT', 'SHIFT'.
The press key code gesture can also simulate press alphabat keys like 'a', 'b', 'c', ..., 'z'. But then the keycode field should use negative value. e.g. the ascii code for 'a' is 97, then the keycode value is -97.
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
keycode | int | yes | Refer to keycode enumeration | 65535(DELETE), -97(a) |
metastate | int | no | Refer to metastate enumeration | 0(None), 64(META), 8(ALT),4(CONTROL), 1(SHIFT) |
Example
driver.press_keycode(65473, 8) # alt + f4
driver.press_keycode(-118, 4) # control + v
Keycode Enumeration
name | keycode | name | keycode |
---|---|---|---|
NOT_A_KEY | 9999 | a | -97 |
BACKSPACE | 65288 | b | -98 |
DELETE | 65535 | c | -99 |
RETURN | 65293 | d | -100 |
TAB | 65289 | e | -101 |
ESCAPE | 65307 | f | -102 |
UP | 65362 | g | -103 |
DOWN | 65364 | h | -104 |
RIGHT | 65363 | i | -105 |
LEFT | 65361 | j | -106 |
HOME | 65360 | k | -107 |
END | 65367 | l | -108 |
PAGEUP | 65365 | m | -109 |
PAGEDOWN | 65366 | n | -110 |
F1 | 65470 | o | -111 |
F2 | 65471 | p | -112 |
F3 | 65472 | q | -113 |
F4 | 65473 | r | -114 |
F5 | 65474 | s | -115 |
F6 | 65475 | t | -116 |
F7 | 65476 | u | -117 |
F8 | 65477 | v | -118 |
F9 | 65478 | w | -119 |
F10 | 65479 | x | -120 |
F11 | 65480 | y | -121 |
F12 | 65481 | z | -122 |
F13 | 65482 | ||
F14 | 65483 | ||
F15 | 65484 | ||
F16 | 65485 | ||
F17 | 65486 | ||
F18 | 65487 | ||
F19 | 65488 | ||
F20 | 65489 | ||
F21 | 65490 | ||
F22 | 65491 | ||
F23 | 65492 | ||
F24 | 65493 | ||
META | 65515 | ||
LMETA | 65515 | ||
RMETA | 65516 | ||
ALT | 65513 | ||
LALT | 65513 | ||
RALT | 65514 | ||
CONTROL | 65507 | ||
LCONTROL | 65507 | ||
RCONTROL | 65508 | ||
SHIFT | 65505 | ||
LSHIFT | 65505 | ||
RSHIFT | 65506 | ||
CAPSLOCK | 65510 | ||
SPACE | 32 | ||
INSERT | 65379 | ||
PRINTSCREEN | 65377 | ||
MENU | 9999 | ||
NUMPAD_0 | 65456 | ||
NUMPAD_1 | 65457 | ||
NUMPAD_2 | 65458 | ||
NUMPAD_3 | 65459 | ||
NUMPAD_4 | 65460 | ||
NUMPAD_5 | 65461 | ||
NUMPAD_6 | 65462 | ||
NUMPAD_7 | 65463 | ||
NUMPAD_8 | 65464 | ||
NUMPAD_9 | 65465 | ||
NUMPAD_LOCK | 65407 | ||
NUMPAD_DECIMAL | 65454 | ||
NUMPAD_PLUS | 78 | ||
NUMPAD_MINUS | 74 | ||
NUMPAD_MUL | 55 | ||
NUMPAD_DIV | 98 | ||
NUMPAD_CLEAR | 9999 | ||
NUMPAD_ENTER | 96 | ||
NUMPAD_EQUAL | 61 | ||
AUDIO_VOLUME_MUTE | 269025042 | ||
AUDIO_VOLUME_DOWN | 269025041 | ||
AUDIO_VOLUME_UP | 269025043 | ||
AUDIO_PLAY | 269025044 | ||
AUDIO_STOP | 269025045 | ||
AUDIO_PAUSE | 269025073 | ||
AUDIO_PREV | 269025046 | ||
AUDIO_NEXT | 269025047 | ||
AUDIO_REWIND | 269025086 | ||
AUDIO_FORWARD | 269025175 | ||
AUDIO_REPEAT | 269025176 | ||
AUDIO_RANDOM | 269025177 | ||
LIGHTS_MON_UP | 269025026 | ||
LIGHTS_MON_DOWN | 269025027 | ||
LIGHTS_KBD_TOGGLE | 269025028 | ||
LIGHTS_KBD_UP | 269025029 | ||
LIGHTS_KBD_DOWN | 269025030 |
Metastate Enumeration
name | metastate |
---|---|
None | 0 |
META | 64 |
ALT | 8 |
CONTROL | 4 |
SHIFT | 1 |
Long Press Key Code
The same with press key code, except for pressing the key for a longer time (0.5s)
Element: Get Property / Attribute
Get the element's property value.
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
name | string | yes | the property name | 'states' |
Example
name = el.get_property('name') # get the name property value
name = el.get_attribute('name') # get the name property value, equivalent to get_property
Element: Clear
Clear the (input) element's text
Arguments
No arguments
Example
el.clear()
Element: Screenshot
Take screenshot of the element
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
filename | string | yes | the path to save the screenshot | '.pics/a.png' |
Example
el.screenshot('.pics/a.png')
Screenshot
Take screenshot of the window
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
filename | string | yes | the path to save the screenshot | '.pics/a.png' |
Example
driver.get_screenshot_as_file('.pics/a.png')
Platform-Specific Extensions
Beside of standard W3C APIs the driver provides the following custom command extensions to execute platform specific scenarios:
linux: getDisplaySize
Get the width and height of the display
Arguments
No arguments
Example
size = driver.execute_script('linux: getDisplaySize')
print(size) # outputs: {'width': 1440, 'height': 900}
linux: mouseMove
Move the mouse to a given absolute coordinates.
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
x | int | yes | the x coordinate | 100 |
y | int | yes | the y coordinate | 100 |
Example
driver.execute_script('linux: mouseMove', {"x": 100, "y": 100})
linux: mouseSwipe
Mouse down at (sx, sy), and move to (ex, ey), and then mouse up.
Effectively swipe from (sx, sy) to (ex, ey)
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
sx | int | yes | swipe from x coordinate | 500 |
sy | int | yes | swipe from y coordinate | 100 |
ex | int | yes | swipe to x coordinate | 500 |
ey | int | yes | swipe to y coordinate | 700 |
Example
driver.execute_script('linux: mouseSwipe', {"sx": 500, "sy": 100, "ex": 500, "ey": 700})
linux: rightClick
Right click on a given absolute coordinates.
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
x | int | yes | the x coordinate | 100 |
y | int | yes | the y coordinate | 100 |
Example
driver.execute_script('linux: rightClick', {"x": 100, "y": 100})
linux: doubleClick
Double click on a given absolute coordinates.
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
x | int | yes | the x coordinate | 100 |
y | int | yes | the y coordinate | 100 |
Example
driver.execute_script('linux: doubleClick', {"x": 100, "y": 100})
linux: mouseScroll
Simulate mouse scroll horizontally and vertically
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
moveLeftSteps | int | no | how much scrolls left, when the value is negative, scrolls right, when the value is 0, doesn't scroll horizontally | |
moveUpSteps | int | no | how much scrolls up, when the value is negative, scrolls down, when the value is 0, doesn't scroll vertically. |
Example
driver.execute_script('linux: mouseScroll', {"moveLeftSteps": 0, "moveUpSteps": -1}) # scrolls down
linux: copy
Copy a given string to the system clipboard
Arguments
Name | Type | Required | Description | Example |
---|---|---|---|---|
str | string | yes | the string to be copied | 'sample copy text' |
Example
driver.execute_script('linux: copy', {"str": 'sample copy text'})
linux: getClipboard
Get the clipboard content
Arguments
No Arguments
Example
content = driver.execute_script('linux: getClipboard')
Application Under Test Concept
The Linux driver has the concept of Application Under Test
. This is the app, whose path has been passed as appName
capability.
If this application is unexpectedly terminated during test session execution then an exception is going to be thrown upon any following session command invocation. In such case the driver assumes the application under test is crashed and it is impossible to proceed.
Also, the Application Under Test
is going to be terminated when the testing session quits.
Examples
# Python3 + PyTest
import pytest
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
@pytest.fixture()
def driver():
drv = webdriver.Remote('http://localhost:4723/wd/hub', {
# automationName capability presence is mandatory for this Linux Driver to be selected
'automationName': 'atspi2',
'platformName': 'linux',
'bundleId': 'yelp',
})
yield drv
drv.quit()
def test_edit_text(driver):
find_el = driver.find_element(by=AppiumBy.NAME, value='Find')
find_el.click()
edit_field = driver.find_element(by=AppiumBy.XPATH, value='(//text[@name="Search"])[1]')
edit_field.send_keys('hello world')
edit_field = driver.find_element(by=AppiumBy.XPATH, value='(//text[@name="Search"])[1]')
assert edit_field.text == 'hello world'
edit_field.clear()
edit_field = driver.find_element(by=AppiumBy.XPATH, value='(//text[@name="Search"])[1]')
assert edit_field.text == ''
Parallel Execution
Parallel execution of multiple Linux driver instances is highly discouraged. Only one UI test must be running at the same time.
Development & Testing
This module uses the same development tools as the other Appium drivers.
Check out the source. Then run:
npm install
gulp watch
Execute npm run test
to run unit tests and npm run e2e-test
to run integration tests.