Added feature to be able to connect with selenium grid

This commit is contained in:
butomo1989 2017-02-02 15:09:09 +01:00
parent a802bdddee
commit b37f31b01c
6 changed files with 170 additions and 13 deletions

View file

@ -17,7 +17,8 @@ Features
1. Android emulator
2. noVNC
3. Appium server
4. Browser application for mobile website testing
4. Able to connect to selenium grid
5. Browser application for mobile website testing
- Chrome version 55 (for x86 and armeabi)
- Firefox version 51 (for x86 and armeabi)
@ -29,14 +30,20 @@ Quick Start
2. Run docker-appium with command:
```bash
docker run -d -p 6080:6080 -p 4723:4723 -v <path_of_apk_that_want_to_be_tested>:/target_apk -e ANDROID_VERSION=<target_android_version> -e EMULATOR_TYPE=<emulator_type> --name appium-container butomo1989/docker-appium
docker run -d -p 6080:6080 -p 4723:4723 -v <path_of_apk_that_want_to_be_tested>:/target_apk -e ANDROID_VERSION=<target_android_version> -e EMULATOR_TYPE=<emulator_type> -e CONNECT_TO_GRID=<True/False> --name appium-container butomo1989/docker-appium
```
An Example:
```bash
docker run -d -p 6080:6080 -p 4723:4723 -v $PWD/example/sample_apk:/target_apk -e ANDROID_VERSION=4.2.2 -e EMULATOR_TYPE=armeabi --name appium-container butomo1989/docker-appium
docker run -d -p 6080:6080 -p 4723:4723 -v $PWD/example/sample_apk:/target_apk -e ANDROID_VERSION=4.2.2 -e EMULATOR_TYPE=armeabi -e CONNECT_TO_GRID=False --name appium-container butomo1989/docker-appium
```
**Optional arguments for CONNECT\_TO\_GRID=True**
-e APPIUM_HOST="<host_ip_address>": if appium is running under different host. default value: 127.0.0.1
-e APPIUM_PORT=<port_number>: if appium is running under different port. default port: 4723
-e SELENIUM_HOST="<host_ip_address>": if selenium hub is running under different host. default value: 127.0.0.1
-e SELENIUM_PORT=<port_number>: if selenium hub is running under different port. default port: 4444
**Note: use flag *--privileged* and *EMULATOR_TYPE=x86* for ubuntu OS to make emulator faster**

View file

@ -0,0 +1,4 @@
import os
WORKDIR = os.path.dirname(__file__)
CONFIG_FILE = os.path.join(WORKDIR, 'nodeconfig.json')

50
service/appium.py Normal file
View file

@ -0,0 +1,50 @@
import json
def create_node_config(config_file, emulator_name, android_version, appium_host, appium_port,
selenium_host, selenium_port):
"""
Create custom node config file in json format to be able to connect with selenium server.
:param config_file: config file
:type config_file: str
:param emulator_name: emulator name
:type emulator_name: str
:param android_version: android version of android emulator
:type android_version: str
:param appium_host: host where appium server is running
:type appium_host: str
:param appium_port: port number where where appium server is running
:type appium_port: int
:param selenium_host: host where selenium server is running
:type selenium_host: str
:param selenium_port: port number where selenium server is running
:type selenium_port: int
"""
config = {
'capabilities': [
{
'platform': 'Android',
'platformName': 'Android',
'version': android_version,
'browserName': emulator_name,
'maxInstances': 1,
}
],
'configuration': {
'cleanUpCycle': 2000,
'timeout': 30000,
'proxy': 'org.openqa.grid.selenium.proxy.DefaultRemoteProxy',
'url': 'http://{host}:{port}/wd/hub'.format(host=appium_host, port=appium_port),
'host': appium_host,
'port': appium_port,
'maxSession': 6,
'register': True,
'registerCycle': 5000,
'hubHost': selenium_host,
'hubPort': selenium_port
}
}
with open(config_file, 'w') as cf:
cf.write(json.dumps(config))

View file

@ -3,8 +3,12 @@ import os
import re
import subprocess
import appium
from service import CONFIG_FILE
logging.basicConfig()
logger = logging.getLogger('android_appium')
logger = logging.getLogger('main')
# not using enum because need to install pip that will make docker image size bigger
TYPE_ARMEABI = 'armeabi'
@ -18,7 +22,8 @@ def run():
"""
# Get android version package
android_version = os.getenv('ANDROID_VERSION', '4.2.2')
os.environ['emulator_name'] = 'emulator_{version}'.format(version=android_version)
emulator_name = 'emulator_{version}'.format(version=android_version)
os.environ['EMULATOR_NAME'] = emulator_name
# Get emulator type
types = [TYPE_ARMEABI, TYPE_X86]
@ -33,16 +38,35 @@ def run():
subprocess.check_call('Xvfb ${DISPLAY} -screen ${SCREEN} ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH} & '
'sleep ${TIMEOUT}', shell=True)
# Start noVNC, installation of android packages, emulator creation and appium
# Start noVNC
vnc_cmd = 'openbox-session & x11vnc -display ${DISPLAY} -nopw -ncache 10 -forever & ' \
'./noVNC/utils/launch.sh --vnc localhost:${LOCAL_PORT} --listen ${TARGET_PORT}'
# Option to connect with selenium server
connect_to_grid = str_to_bool(str(os.getenv('CONNECT_TO_GRID', False)))
logger.info('Connect with selenium grid? {input}'.format(input=connect_to_grid))
appium_cmd = 'appium'
if connect_to_grid:
try:
appium_host = os.getenv('APPIUM_HOST', '127.0.0.1')
appium_port = int(os.getenv('APPIUM_PORT', 4723))
selenium_host = os.getenv('SELENIUM_HOST', '172.17.0.1')
selenium_port = int(os.getenv('SELENIUM_PORT', 4444))
appium.create_node_config(CONFIG_FILE, emulator_name, android_version,
appium_host, appium_port, selenium_host, selenium_port)
appium_cmd += ' --nodeconfig {file}'.format(file=CONFIG_FILE)
except ValueError as v_err:
logger.error(v_err)
# Start installation of android packages, emulator creation and appium in a terminal
android_cmd = get_android_bash_commands(android_version, emulator_type)
if android_cmd:
cmd = '({vnc}) & (xterm -T "Android-Appium" -n "Android-Appium" -e \"{android} && ' \
'/bin/echo $emulator_name && appium\")'.format(vnc=vnc_cmd, android=android_cmd)
'/bin/echo $EMULATOR_NAME && {appium}\")'.format(
vnc=vnc_cmd, android=android_cmd, appium=appium_cmd)
else:
logger.warning('There is no android packages installed!')
cmd = '({vnc}) & (xterm -e \"appium\")'.format(vnc=vnc_cmd)
cmd = '({vnc}) & (xterm -e \"{appium}\")'.format(vnc=vnc_cmd, appium=appium_cmd)
subprocess.check_call(cmd, shell=True)
@ -64,7 +88,7 @@ def get_item_position(keyword, items):
"""
Get position of item in array by given keyword.
:return: Item position.
:return: item position
:rtype: int
"""
pos = 0
@ -129,6 +153,18 @@ def get_android_bash_commands(android_version, emulator_type):
return bash_command
def str_to_bool(str):
"""
Convert string to boolean.
:param str: given string
:type str: str
:return: converted string
:rtype: bool
"""
return str.lower() in ('yes', 'true', 't', '1')
if __name__ == '__main__':
logger.setLevel(logging.INFO)
run()

View file

@ -1,4 +1,6 @@
"""Unit test for start.py."""
import os
from unittest import TestCase
import mock
@ -9,14 +11,47 @@ from service import start
class TestService(TestCase):
"""Unit test class to test method run."""
@mock.patch('service.start.get_android_bash_commands')
def setUp(self):
os.environ['ANDROID_VERSION'] = '4.2.2'
os.environ['EMULATOR_TYPE'] = start.TYPE_X86
os.environ['CONNECT_TO_GRID'] = str(False)
@mock.patch('subprocess.check_call')
def test_service(self, mocked_bash_cmd, mocked_subprocess):
self.assertFalse(mocked_bash_cmd.called)
@mock.patch('service.start.get_android_bash_commands')
def test_without_selenium_server(self, mocked_subprocess, mocked_bash_cmd):
self.assertFalse(mocked_subprocess.called)
self.assertFalse(mocked_bash_cmd.called)
start.run()
self.assertTrue(mocked_bash_cmd.called)
self.assertTrue(mocked_subprocess.called)
self.assertTrue(mocked_bash_cmd.called)
@mock.patch('subprocess.check_call')
@mock.patch('service.appium.create_node_config')
@mock.patch('service.start.get_android_bash_commands')
def test_with_selenium_server(self, mocked_subprocess, mocked_config, mocked_bash_cmd):
os.environ['CONNECT_TO_GRID'] = str(True)
self.assertFalse(mocked_subprocess.called)
self.assertFalse(mocked_config.called)
self.assertFalse(mocked_bash_cmd.called)
start.run()
self.assertTrue(mocked_subprocess.called)
self.assertTrue(mocked_config.called)
self.assertTrue(mocked_bash_cmd.called)
@mock.patch('subprocess.check_call')
@mock.patch('service.appium.create_node_config')
@mock.patch('service.start.get_android_bash_commands')
def test_invalid_integer(self, mocked_subprocess, mocked_config, mocked_bash_cmd):
os.environ['CONNECT_TO_GRID'] = str(True)
os.environ['APPIUM_PORT'] = 'test'
self.assertFalse(mocked_subprocess.called)
self.assertFalse(mocked_config.called)
self.assertFalse(mocked_bash_cmd.called)
start.run()
self.assertTrue(mocked_subprocess.called)
self.assertFalse(mocked_config.called)
self.assertTrue(mocked_bash_cmd.called)
self.assertRaises(ValueError)
@mock.patch('service.start.get_android_bash_commands')
@mock.patch('subprocess.check_call')
@ -28,3 +63,11 @@ class TestService(TestCase):
start.run()
self.assertTrue(mocked_subprocess.called)
self.assertTrue(mocked_logger_warning.called)
def tearDown(self):
del os.environ['ANDROID_VERSION']
del os.environ['EMULATOR_TYPE']
if os.getenv('CONNECT_TO_GRID') == str(True):
del os.environ['CONNECT_TO_GRID']
if os.getenv('APPIUM_PORT'):
del os.environ['APPIUM_PORT']

View file

@ -0,0 +1,17 @@
"""Unit test for appium.py."""
import os
from unittest import TestCase
from service import CONFIG_FILE
from service import appium
class TestAppiumConfig(TestCase):
"""Unit test class to test method create_node_config."""
def test_create_node_config(self):
self.assertFalse(os.path.exists(CONFIG_FILE))
appium.create_node_config(CONFIG_FILE, 'emulator_name', '4.2.2', '127.0.0.1', 4723, '127.0.0.1', 4444)
self.assertTrue(os.path.exists(CONFIG_FILE))
os.remove(CONFIG_FILE)