app.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #!/usr/bin/env python3
  2. import json
  3. import logging
  4. import os
  5. import subprocess
  6. import uuid
  7. from src import CHROME_DRIVER, CONFIG_FILE, ROOT
  8. from src import log
  9. log.init()
  10. logger = logging.getLogger('app')
  11. def symlink_force(target, link_name):
  12. try:
  13. os.symlink(target, link_name)
  14. except OSError as e:
  15. import errno
  16. if e.errno == errno.EEXIST:
  17. os.remove(link_name)
  18. os.symlink(target, link_name)
  19. def get_or_raise(env: str) -> str:
  20. """
  21. Check if needed environment variables are given.
  22. :param env: key
  23. :return: value
  24. """
  25. env_value = os.getenv(env)
  26. if not env_value:
  27. raise RuntimeError('The environment variable {0:s} is missing.'
  28. 'Please check docker image or Dockerfile!'.format(env))
  29. return env_value
  30. def convert_str_to_bool(str: str) -> bool:
  31. """
  32. Convert string to boolean.
  33. :param str: given string
  34. :return: converted string
  35. """
  36. try:
  37. return str.lower() in ('yes', 'true', 't', '1')
  38. except AttributeError as err:
  39. logger.error(err)
  40. def is_initialized(device_name) -> bool:
  41. config_path = os.path.join(ROOT, 'android_emulator', 'config.ini')
  42. if os.path.exists(config_path):
  43. logger.info('Found existing config file at {}.'.format(config_path))
  44. with open(config_path, 'r') as f:
  45. if any('hw.device.name={}'.format(device_name) in line for line in f):
  46. logger.info('Existing config file references {}. Assuming device was previously initialized.'.format(device_name))
  47. return True
  48. else:
  49. logger.info('Existing config file does not reference {}. Assuming new device.'.format(device_name))
  50. return False
  51. logger.info('No config file file was found at {}. Assuming new device.'.format(config_path))
  52. return False
  53. ANDROID_HOME = get_or_raise('ANDROID_HOME')
  54. ANDROID_VERSION = get_or_raise('ANDROID_VERSION')
  55. API_LEVEL = get_or_raise('API_LEVEL')
  56. PROCESSOR = get_or_raise('PROCESSOR')
  57. SYS_IMG = get_or_raise('SYS_IMG')
  58. IMG_TYPE = get_or_raise('IMG_TYPE')
  59. logger.info('Android version: {version} \n'
  60. 'API level: {level} \n'
  61. 'Processor: {processor} \n'
  62. 'System image: {img} \n'
  63. 'Image type: {img_type}'.format(version=ANDROID_VERSION, level=API_LEVEL, processor=PROCESSOR,
  64. img=SYS_IMG, img_type=IMG_TYPE))
  65. def prepare_avd(device: str, avd_name: str, dp_size: str):
  66. """
  67. Create and run android virtual device.
  68. :param device: Device name
  69. :param avd_name: Name of android virtual device / emulator
  70. """
  71. device_name_bash = device.replace(' ', '\ ')
  72. skin_name = device.replace(' ', '_').lower()
  73. # For custom hardware profile
  74. profile_dst_path = os.path.join(ROOT, '.android', 'devices.xml')
  75. if 'samsung' in device.lower():
  76. # profile file name = skin name
  77. profile_src_path = os.path.join(ROOT, 'devices', 'profiles', '{profile}.xml'.format(profile=skin_name))
  78. logger.info('Hardware profile resource path: {rsc}'.format(rsc=profile_src_path))
  79. logger.info('Hardware profile destination path: {dst}'.format(dst=profile_dst_path))
  80. symlink_force(profile_src_path, profile_dst_path)
  81. avd_path = '/'.join([ANDROID_HOME, 'android_emulator'])
  82. creation_cmd = 'avdmanager create avd -f -n {name} -b {img_type}/{sys_img} -k "system-images;android-{api_lvl};' \
  83. '{img_type};{sys_img}" -d {device} -p {path}'.format(name=avd_name, img_type=IMG_TYPE,
  84. sys_img=SYS_IMG,
  85. api_lvl=API_LEVEL, device=device_name_bash,
  86. path=avd_path)
  87. logger.info('Command to create avd: {command}'.format(command=creation_cmd))
  88. subprocess.check_call(creation_cmd, shell=True)
  89. skin_path = '/'.join([ANDROID_HOME, 'devices', 'skins', skin_name])
  90. config_path = '/'.join([avd_path, 'config.ini'])
  91. with open(config_path, 'a') as file:
  92. file.write('skin.path={sp}'.format(sp=skin_path))
  93. file.write('\ndisk.dataPartition.size={dp}'.format(dp=dp_size))
  94. logger.info('Skin was added in config.ini')
  95. def appium_run(avd_name: str):
  96. """
  97. Run appium server.
  98. :param avd_name: Name of android virtual device / emulator
  99. """
  100. DEFAULT_LOG_PATH = '/var/log/supervisor/appium.log'
  101. cmd = 'appium --log {log}'.format(log=os.getenv('APPIUM_LOG', DEFAULT_LOG_PATH))
  102. relaxed_security = convert_str_to_bool(str(os.getenv('RELAXED_SECURITY', False)))
  103. logger.info('Relaxed security? {rs}'.format(rs=relaxed_security))
  104. if relaxed_security:
  105. cmd += ' --relaxed-security'
  106. default_web_browser = os.getenv('BROWSER')
  107. cmd += ' --chromedriver-executable {driver}'.format(driver=CHROME_DRIVER)
  108. grid_connect = convert_str_to_bool(str(os.getenv('CONNECT_TO_GRID', False)))
  109. logger.info('Connect to selenium grid? {connect}'.format(connect=grid_connect))
  110. if grid_connect:
  111. # Ubuntu 16.04 -> local_ip = os.popen('ifconfig eth0 | grep \'inet addr:\' | cut -d: -f2 | awk \'{ print $1}\'').read().strip()
  112. local_ip = os.popen('ifconfig eth0 | grep \'inet\' | cut -d: -f2 | awk \'{ print $2}\'').read().strip()
  113. try:
  114. mobile_web_test = convert_str_to_bool(str(os.getenv('MOBILE_WEB_TEST', False)))
  115. appium_host = os.getenv('APPIUM_HOST', local_ip)
  116. appium_port = int(os.getenv('APPIUM_PORT', 4723))
  117. selenium_host = os.getenv('SELENIUM_HOST', '172.17.0.1')
  118. selenium_port = int(os.getenv('SELENIUM_PORT', 4444))
  119. browser_name = default_web_browser if mobile_web_test else 'android'
  120. create_node_config(avd_name, browser_name, appium_host, appium_port, selenium_host, selenium_port)
  121. cmd += ' --nodeconfig {file}'.format(file=CONFIG_FILE)
  122. except ValueError as v_err:
  123. logger.error(v_err)
  124. title = 'Appium Server'
  125. subprocess.check_call('xterm -T "{title}" -n "{title}" -e \"{cmd}\"'.format(title=title, cmd=cmd), shell=True)
  126. def create_node_config(avd_name: str, browser_name: str, appium_host: str, appium_port: int, selenium_host: str,
  127. selenium_port: int):
  128. """
  129. Create custom node config file in json format to be able to connect with selenium server.
  130. :param avd_name: Name of android virtual device / emulator
  131. :param appium_host: Host where appium server is running
  132. :param appium_port: Port number where where appium server is running
  133. :param selenium_host: Host where selenium server is running
  134. :param selenium_port: Port number where selenium server is running
  135. """
  136. config = {
  137. 'capabilities': [
  138. {
  139. 'platform': 'Android',
  140. 'platformName': 'Android',
  141. 'version': ANDROID_VERSION,
  142. 'browserName': browser_name,
  143. 'deviceName': avd_name,
  144. 'maxInstances': 1,
  145. }
  146. ],
  147. 'configuration': {
  148. 'cleanUpCycle': 2000,
  149. 'timeout': 30,
  150. 'proxy': 'org.openqa.grid.selenium.proxy.DefaultRemoteProxy',
  151. 'url': 'http://{host}:{port}/wd/hub'.format(host=appium_host, port=appium_port),
  152. 'host': appium_host,
  153. 'port': appium_port,
  154. 'maxSession': 6,
  155. 'register': True,
  156. 'registerCycle': 5000,
  157. 'hubHost': selenium_host,
  158. 'hubPort': selenium_port,
  159. 'unregisterIfStillDownAfter': 120000
  160. }
  161. }
  162. logger.info('Appium node config: {config}'.format(config=config))
  163. with open(CONFIG_FILE, 'w') as cf:
  164. cf.write(json.dumps(config))
  165. def run():
  166. """Run app."""
  167. device = os.getenv('DEVICE', 'Nexus 5')
  168. logger.info('Device: {device}'.format(device=device))
  169. custom_args=os.getenv('EMULATOR_ARGS', '')
  170. logger.info('Custom Args: {custom_args}'.format(custom_args=custom_args))
  171. avd_name = '{device}_{version}'.format(device=device.replace(' ', '_').lower(), version=ANDROID_VERSION)
  172. logger.info('AVD name: {avd}'.format(avd=avd_name))
  173. is_first_run = not is_initialized(device)
  174. dp_size = os.getenv('DATAPARTITION', '550m')
  175. if is_first_run:
  176. logger.info('Preparing emulator...')
  177. prepare_avd(device, avd_name, dp_size)
  178. logger.info('Run emulator...')
  179. if is_first_run:
  180. logger.info('Emulator was not previously initialized. Preparing a new one...')
  181. cmd = 'emulator/emulator @{name} -gpu swiftshader_indirect -accel on -wipe-data -writable-system -verbose {custom_args}'.format(name=avd_name, custom_args=custom_args)
  182. else:
  183. logger.info('Using previously initialized AVD...')
  184. cmd = 'emulator/emulator @{name} -gpu swiftshader_indirect -accel on -verbose -writable-system {custom_args}'.format(name=avd_name, custom_args=custom_args)
  185. appium = convert_str_to_bool(str(os.getenv('APPIUM', False)))
  186. if appium:
  187. subprocess.Popen(cmd.split())
  188. logger.info('Run appium server...')
  189. appium_run(avd_name)
  190. else:
  191. result = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE).communicate()
  192. if __name__ == '__main__':
  193. run()