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 1. Android emulator
2. noVNC 2. noVNC
3. Appium server 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) - Chrome version 55 (for x86 and armeabi)
- Firefox version 51 (for x86 and armeabi) - Firefox version 51 (for x86 and armeabi)
@ -29,14 +30,20 @@ Quick Start
2. Run docker-appium with command: 2. Run docker-appium with command:
```bash ```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: An Example:
```bash ```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** **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 re
import subprocess import subprocess
import appium
from service import CONFIG_FILE
logging.basicConfig() 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 # not using enum because need to install pip that will make docker image size bigger
TYPE_ARMEABI = 'armeabi' TYPE_ARMEABI = 'armeabi'
@ -18,7 +22,8 @@ def run():
""" """
# Get android version package # Get android version package
android_version = os.getenv('ANDROID_VERSION', '4.2.2') 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 # Get emulator type
types = [TYPE_ARMEABI, TYPE_X86] 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} & ' subprocess.check_call('Xvfb ${DISPLAY} -screen ${SCREEN} ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH} & '
'sleep ${TIMEOUT}', shell=True) '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 & ' \ vnc_cmd = 'openbox-session & x11vnc -display ${DISPLAY} -nopw -ncache 10 -forever & ' \
'./noVNC/utils/launch.sh --vnc localhost:${LOCAL_PORT} --listen ${TARGET_PORT}' './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) android_cmd = get_android_bash_commands(android_version, emulator_type)
if android_cmd: if android_cmd:
cmd = '({vnc}) & (xterm -T "Android-Appium" -n "Android-Appium" -e \"{android} && ' \ 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: else:
logger.warning('There is no android packages installed!') 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) 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. Get position of item in array by given keyword.
:return: Item position. :return: item position
:rtype: int :rtype: int
""" """
pos = 0 pos = 0
@ -129,6 +153,18 @@ def get_android_bash_commands(android_version, emulator_type):
return bash_command 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__': if __name__ == '__main__':
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
run() run()

View file

@ -1,4 +1,6 @@
"""Unit test for start.py.""" """Unit test for start.py."""
import os
from unittest import TestCase from unittest import TestCase
import mock import mock
@ -9,14 +11,47 @@ from service import start
class TestService(TestCase): class TestService(TestCase):
"""Unit test class to test method run.""" """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') @mock.patch('subprocess.check_call')
def test_service(self, mocked_bash_cmd, mocked_subprocess): @mock.patch('service.start.get_android_bash_commands')
self.assertFalse(mocked_bash_cmd.called) def test_without_selenium_server(self, mocked_subprocess, mocked_bash_cmd):
self.assertFalse(mocked_subprocess.called) self.assertFalse(mocked_subprocess.called)
self.assertFalse(mocked_bash_cmd.called)
start.run() start.run()
self.assertTrue(mocked_bash_cmd.called)
self.assertTrue(mocked_subprocess.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('service.start.get_android_bash_commands')
@mock.patch('subprocess.check_call') @mock.patch('subprocess.check_call')
@ -28,3 +63,11 @@ class TestService(TestCase):
start.run() start.run()
self.assertTrue(mocked_subprocess.called) self.assertTrue(mocked_subprocess.called)
self.assertTrue(mocked_logger_warning.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)