Added feature to be able to connect with selenium grid
This commit is contained in:
parent
a802bdddee
commit
b37f31b01c
13
README.md
13
README.md
|
@ -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**
|
||||||
|
|
||||||
|
|
|
@ -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
50
service/appium.py
Normal 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))
|
|
@ -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()
|
||||||
|
|
|
@ -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']
|
||||||
|
|
17
service/tests/test_appium_config.py
Normal file
17
service/tests/test_appium_config.py
Normal 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)
|
Loading…
Reference in a new issue