Refactored code, added log and emulator skins

This commit is contained in:
butomo1989
2017-02-27 14:53:48 +01:00
parent 87ed3c6a1f
commit 30b2205987
130 changed files with 1303 additions and 264 deletions
+5
View File
@@ -0,0 +1,5 @@
import os
WORKDIR = os.path.dirname(__file__)
CONFIG_FILE = os.path.join(WORKDIR, 'nodeconfig.json')
LOGGING_FILE = os.path.join(WORKDIR, 'logging.conf')
+116
View File
@@ -0,0 +1,116 @@
import logging
import os
import re
import subprocess
logging.basicConfig()
logger = logging.getLogger('android')
def get_api_level(android_version):
"""
Get api level of android version.
:param android_version: android version
:type android_version: str
:return: api version
:rtype: str
"""
api_version = None
try:
packages = get_available_sdk_packages()
if packages:
item_pos = get_item_position(android_version, packages)
logger.info('package in position: {pos}'.format(pos=item_pos))
item = packages[item_pos]
item_info = item.split('-')
api_version = re.search('%s(.*)%s' % ('API', ','), item_info[1]).group(1).strip()
logger.info(
'API level: {api}'.format(api=api_version))
else:
raise RuntimeError('List of packages is empty!')
except IndexError as i_err:
logger.error(i_err)
return api_version
def install_package(android_path, emulator_file, api_level, sys_img):
"""
Install sdk package.
:param android_path: location where android SDK is installed
:type android_path: str
:param emulator_file: emulator file that need to be link
:type emulator_file: str
:param api_level: api level
:type api_level: str
:param sys_img: system image of emulator
:type sys_img: str
"""
# Link emulator shortcut
emu_file = os.path.join(android_path, 'tools', emulator_file)
emu_target = os.path.join(android_path, 'tools', 'emulator')
os.symlink(emu_file, emu_target)
# Install package based on given android version
cmd = 'echo y | android update sdk --no-ui -a -t android-{api},sys-img-{sys_img}-android-{api}'.format(
api=api_level, sys_img=sys_img)
logger.info('Android installation command : {install}'.format(install=cmd))
subprocess.check_call('xterm -e \"{cmd}\"'.format(cmd=cmd), shell=True)
def create_avd(android_path, avd_name, api_level):
"""
Create android virtual device.
:param android_path: location where android SDK is installed
:type android_path: str
:param avd_name: desire name
:type avd_name: str
:param api_level: api level
:type api_level: str
"""
# Link emulator skins
skins_rsc = os.path.join(android_path, 'skins')
skins_dst = os.path.join(android_path, 'platforms', 'android-{api}'.format(api=api_level), 'skins')
for skin_file in os.listdir(skins_rsc):
os.symlink(os.path.join(skins_rsc, skin_file), os.path.join(skins_dst, skin_file))
# Create android emulator
cmd = 'echo no | android create avd -f -n {name} -t android-{api}'.format(name=avd_name, api=api_level)
logger.info('Emulator creation command : {cmd}'.format(cmd=cmd))
subprocess.check_call('xterm -e \"{cmd}\"'.format(cmd=cmd), shell=True)
def get_available_sdk_packages():
"""
Get list of available sdk packages.
:return: List of available packages.
:rtype: bytearray
"""
cmd = ['android', 'list', 'sdk']
output_str = subprocess.check_output(cmd)
logger.info('List of Android SDK: ')
logger.info(output_str)
return [output.strip() for output in output_str.split('\n')] if output_str else None
def get_item_position(keyword, items):
"""
Get position of item in array by given keyword.
:return: item position
:rtype: int
"""
pos = 0
for p, v in enumerate(items):
if keyword in v:
pos = p
break # Get the first item that match with keyword
return pos
+84
View File
@@ -0,0 +1,84 @@
import json
import logging
import os
import subprocess
logger = logging.getLogger('appium')
def run(connect_to_grid, emulator_name, android_version):
"""
Run appium server.
:param connect_to_grid: option to connect with selenium grid
:type connect_to_grid: bool
:param emulator_name: name of emulator
:type emulator_name: str
:param android_version: android version
:type android_version: str
"""
cmd = 'appium'
if connect_to_grid:
from src import CONFIG_FILE
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))
create_node_config(CONFIG_FILE, emulator_name, android_version,
appium_host, appium_port, selenium_host, selenium_port)
cmd += ' --nodeconfig {file}'.format(file=CONFIG_FILE)
except ValueError as v_err:
logger.error(v_err)
subprocess.check_call('xterm -T "{name}" -n "{name}" -e \"{cmd}\"'.format(
name=emulator_name, cmd=cmd), shell=True)
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
}
}
logger.info('appium node config: {config}'.format(config=config))
with open(config_file, 'w') as cf:
cf.write(json.dumps(config))
+8
View File
@@ -0,0 +1,8 @@
import logging
import logging.config
from src import LOGGING_FILE
def init():
logging.config.fileConfig(LOGGING_FILE)
+38
View File
@@ -0,0 +1,38 @@
[loggers]
keys=root, android, appium, service
[handlers]
keys=console
[formatters]
keys=formatter
[logger_root]
level=INFO
handlers=console
[logger_android]
level=INFO
handlers=console
propagate=0
qualname=android
[logger_appium]
level=INFO
handlers=console
propagate=0
qualname=appium
[logger_service]
level=INFO
handlers=console
propagate=0
qualname=service
[handler_console]
class=StreamHandler
formatter=formatter
args=(sys.stdout,)
[formatter_formatter]
format=[%(process)2d] [%(levelname)5s] %(name)s - %(message)s
+59
View File
@@ -0,0 +1,59 @@
import logging
import os
from src import android, appium, log
logger = logging.getLogger('service')
# not using enum because need to install pip that will make docker image size bigger
TYPE_ARMEABI = 'armeabi'
TYPE_X86 = 'x86'
def start():
"""
Installation of needed sdk package, creation of android emulator and execution of appium server.
"""
# Get all needed environment variables
android_path = os.getenv('ANDROID_HOME', '/root')
logger.info('Android path: {path}'.format(path=android_path))
emulator_type = os.getenv('EMULATOR_TYPE', TYPE_ARMEABI).lower()
logger.info('Emulator type: {type}'.format(type=emulator_type))
android_version = os.getenv('ANDROID_VERSION', '4.2.2')
logger.info('Android version: {version}'.format(version=android_version))
connect_to_grid = str_to_bool(str(os.getenv('CONNECT_TO_GRID', False)))
logger.info('Connect to selenium grid? {input}'.format(input=connect_to_grid))
# Install needed sdk packages
emulator_type = TYPE_ARMEABI if emulator_type not in [TYPE_ARMEABI, TYPE_X86] else emulator_type
emulator_file = 'emulator64-x86' if emulator_type == TYPE_X86 else 'emulator64-arm'
logger.info('Emulator file: {file}'.format(file=emulator_file))
api_level = android.get_api_level(android_version)
sys_img = 'x86' if emulator_type == TYPE_X86 else 'armeabi-v7a'
logger.info('System image: {sys_img}'.format(sys_img=sys_img))
android.install_package(android_path, emulator_file, api_level, sys_img)
# Create android virtual device
avd_name = 'emulator_{version}'.format(version=android_version)
logger.info('AVD name: {avd}'.format(avd=avd_name))
android.create_avd(android_path, avd_name, api_level)
# Run appium server
appium.run(connect_to_grid, avd_name, android_version)
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__':
log.init()
start()
View File
View File
+33
View File
@@ -0,0 +1,33 @@
"""Unit test for android.py."""
from unittest import TestCase
import mock
from src import android
@mock.patch('src.android.get_available_sdk_packages')
class TestApiLevel(TestCase):
"""Unit test class to test method get_api_level."""
def setUp(self):
self.android_version = '4.2.2'
def test_get_api_level(self, mocked_packages):
mocked_packages.return_value = ['9- SDK Platform Android 4.4.2, API 19, revision 4',
'10- SDK Platform Android 4.3.1, API 18, revision 3',
'11- SDK Platform Android 4.2.2, API 17, revision 3']
api_level = android.get_api_level(self.android_version)
self.assertEqual(api_level, '17')
def test_empty_packages(self, mocked_packages):
mocked_packages.return_value = None
with self.assertRaises(RuntimeError):
android.get_api_level(self.android_version)
def test_index_error(self, mocked_packages):
mocked_packages.return_value = ['9 SDK Platform Android 4.4.2, API 19, revision 4',
'10 SDK Platform Android 4.3.1, API 18, revision 3',
'11 SDK Platform Android 4.2.2, API 17, revision 3']
android.get_api_level(self.android_version)
self.assertRaises(IndexError)
@@ -0,0 +1,28 @@
"""Unit test for android.py."""
from unittest import TestCase
import mock
from src import android
class TestAvailablePackages(TestCase):
"""Unit test class to test method get_available_sdk_packages."""
@mock.patch('subprocess.check_output')
def test_valid_output(self, mocked_output):
mocked_output.return_value = 'package 1 \n package 2'
output = android.get_available_sdk_packages()
self.assertEqual(['package 1', 'package 2'], output)
@mock.patch('subprocess.check_output')
def test_without_line_break(self, mocked_output):
mocked_output.return_value = 'package 1, package 2'
output = android.get_available_sdk_packages()
self.assertEqual(['package 1, package 2'], output)
@mock.patch('subprocess.check_output')
def test_empty_string(self, mocked_output):
mocked_output.return_value = None
output = android.get_available_sdk_packages()
self.assertEqual(None, output)
+28
View File
@@ -0,0 +1,28 @@
"""Unit test for android.py."""
from unittest import TestCase
import mock
from src import android
class TestAvd(TestCase):
"""Unit test class to test method create_avd."""
def setUp(self):
self.android_path = '/root'
self.avd_name = 'test'
self.api_level = 21
@mock.patch('os.symlink')
@mock.patch('subprocess.check_call')
def test_avd_creation(self, mocked_sys_link, mocked_suprocess):
with mock.patch('os.listdir') as mocked_list_dir:
mocked_list_dir.return_value = ['file1', 'file2']
self.assertFalse(mocked_list_dir.called)
self.assertFalse(mocked_sys_link.called)
self.assertFalse(mocked_suprocess.called)
android.create_avd(self.android_path, self.avd_name, self.api_level)
self.assertTrue(mocked_list_dir.called)
self.assertTrue(mocked_sys_link.called)
self.assertTrue(mocked_suprocess.called)
+25
View File
@@ -0,0 +1,25 @@
"""Unit test for android.py."""
from unittest import TestCase
import mock
from src import android
class TestInstallPackage(TestCase):
"""Unit test class to test method install_package."""
def setUp(self):
self.android_path = '/root'
self.emulator_file = 'emulator64-arm'
self.api_level = 21
self.sys_img = 'armeabi-v7a'
@mock.patch('os.symlink')
@mock.patch('subprocess.check_call')
def test_package_installation(self, mocked_sys_link, mocked_suprocess):
self.assertFalse(mocked_sys_link.called)
self.assertFalse(mocked_suprocess.called)
android.install_package(self.android_path, self.emulator_file, self.api_level, self.sys_img)
self.assertTrue(mocked_sys_link.called)
self.assertTrue(mocked_suprocess.called)
+27
View File
@@ -0,0 +1,27 @@
"""Unit test for android.py."""
from unittest import TestCase
from src import android
class TestItemPosition(TestCase):
"""Unit test class to test method get_item_position."""
def setUp(self):
self.items = ['android 4.1', 'android 4.2.2', 'android 4.3', 'android 4.4', 'android 4.4.2']
def test_valid_params(self):
keyword = '4.2'
output = android.get_item_position(keyword, self.items)
self.assertEqual(1, output)
def test_invalid_keyword(self):
keyword = 'fake'
output = android.get_item_position(keyword, self.items)
self.assertEqual(0, output)
def test_empty_array(self):
items = []
keyword = '4.2'
output = android.get_item_position(keyword, items)
self.assertEqual(0, output)
View File
+16
View File
@@ -0,0 +1,16 @@
"""Unit test for appium.py."""
import os
from unittest import TestCase
from src import CONFIG_FILE, appium
class TestAppiumConfig(TestCase):
"""Unit test class to test method create_node_config."""
def test_config_creation(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)
+47
View File
@@ -0,0 +1,47 @@
"""Unit test for appium.py."""
from unittest import TestCase
import os
import mock
from src import appium
@mock.patch('subprocess.check_call')
class TestAppiumConfig(TestCase):
"""Unit test class to test method run."""
def setUp(self):
self.emulator_name = 'test'
self.android_version = '4.2.2'
def test_without_selenium_grid(self, mocked_subprocess):
with mock.patch('src.appium.create_node_config') as mocked_config:
self.assertFalse(mocked_config.called)
self.assertFalse(mocked_subprocess.called)
appium.run(False, self.emulator_name, self.android_version)
self.assertFalse(mocked_config.called)
self.assertTrue(mocked_subprocess.called)
def test_with_selenium_grid(self, mocked_subprocess):
with mock.patch('src.appium.create_node_config') as mocked_config:
self.assertFalse(mocked_config.called)
self.assertFalse(mocked_subprocess.called)
appium.run(True, self.emulator_name, self.android_version)
self.assertTrue(mocked_config.called)
self.assertTrue(mocked_subprocess.called)
def test_invalid_integer(self, mocked_subprocess):
os.environ['APPIUM_PORT'] = 'test'
with mock.patch('src.appium.create_node_config') as mocked_config:
self.assertFalse(mocked_config.called)
self.assertFalse(mocked_subprocess.called)
appium.run(True, self.emulator_name, self.android_version)
self.assertFalse(mocked_config.called)
self.assertTrue(mocked_subprocess.called)
self.assertRaises(ValueError)
def tearDown(self):
if os.getenv('APPIUM_PORT'):
del os.environ['APPIUM_PORT']