Package tlib :: Package base :: Module AndroidHelper |
[hide private]
[frames] | no frames]

# Source Code for Module tlib.base.AndroidHelper

      1  import os
      2  import requests
      3  import time
      4  import pytest
      5  import re
      6  from _pytest import runner
      7  from tlib.base import TestHelper
      8  import jsonpath_rw
      9  import logging
     10  from tlib.base.LogHelper import get_tlib_logger, get_adb_logger
     11
     12  DEVICE_UNPLUGGED = "disconnected"     # Device is not connected
     13  DEVICE_OFFLINE = "offline"            # Device is connected but offline
     14  DEVICE_UNAUTHORIZED = "unauthorized"  # Unauthorized
     15  DEVICE_ONLINE = "online"              # Device is connected and accessible
     16
     17
     18 -def is_webdriver_running(tlib_logger, adb_logger, log=True):
     19      """
     20      Function to validate if webdriver is running and a connection can be established
     21
     22      @param tlib_logger: TLib logger
     23      @type tlib_logger: logging.Logger
     24      @param adb_logger: ADB logger
     25      @type adb_logger: logging.Logger
     26      @param log: When true, log debugging information
     27      @type log: bool
     28      """
     29      try:
     30          if log:
     31              tlib_logger.debug("Checking if Webdriver is running")
     32          response = requests.get("http://localhost:8080/wd/hub/status", timeout=10)
     33      except requests.ConnectionError as e:
     34          if log:
     35              adb_logger.debug("Connection to Webdriver failed:\n%s" % e)
     36          return False
     37
     38      return response.status_code == 200
     39
     40
     41 -def get_device_status(tlib_logger, adb_logger, serial_id):
     42      """
     43      Get status of a device using it's serial id\n
     44      Serial id can be either a ID (for devices connected to USB) or an IP and port (For devices connected via IP)
     45
     46      @param tlib_logger: TLib logger
     47      @type tlib_logger: logging.Logger
     48      @param adb_logger: ADB logger
     49      @type adb_logger: logging.Logger
     50      @param serial_id: Device's serial number
     51      @type serial_id: str
     52      """
     53
     54      out = TestHelper.run_command(adb_logger, ["adb", "devices"], fail_on_error=False)
     55      if re.search(serial_id, out[0]) is None:
     56          tlib_logger.debug("Device {serial_id} is not connected".format(serial_id=serial_id))
     57          return DEVICE_UNPLUGGED
     58      elif re.search("{serial_id}\s+offline".format(serial_id=serial_id), out[0]) is not None:
     59          tlib_logger.debug("Device {serial_id} is offline".format(serial_id=serial_id))
     60          return DEVICE_OFFLINE
     61      elif re.search("{serial_id}\s+unauthorized".format(serial_id=serial_id), out[0]) is not None:
     62          tlib_logger.debug("Device {serial_id} is offline".format(serial_id=serial_id))
     63          return DEVICE_UNAUTHORIZED
     64      elif re.search("{serial_id}\s+device".format(serial_id=serial_id), out[0]) is not None:
     65          tlib_logger.debug("Device {serial_id} is online".format(serial_id=serial_id))
     66          return DEVICE_ONLINE
     67      else:
     68          tlib_logger.error("Unknown device status\n%s" % out[0])
     69          raise RuntimeError("Unknown device status\n%s" % out[0])
     70
     71
     72 -def setup_ip_connection(tlib_logger, adb_logger, serial_id):
     73      """
     74      Connects to an android device via IP and waits for the connection to be established.
     75
     76      @param tlib_logger: TLib logger
     77      @type tlib_logger: logging.Logger
     78      @param adb_logger: ADB logger
     79      @type adb_logger: logging.Logger
     80      @param serial_id: Device's serial number
     81      @type serial_id: str
     82      """
     83      # Validate is device is already connected
     84      tlib_logger.debug("Setting up IP connection to device {serial_id}".format(serial_id=serial_id))
     85      status = get_device_status(tlib_logger, adb_logger, serial_id)
     86
     87      if status == DEVICE_ONLINE:
     88          tlib_logger.debug("Device is already online".format(serial_id=serial_id))
     89          return
     90
     91      # If device is offline or unauthorized, disconnect
     92      if status == DEVICE_OFFLINE:
     93          tlib_logger.warn("Device {serial_id} is offline, disconnecting and retrying".format(serial_id=serial_id))
     94          terminate_ip_connection(tlib_logger, adb_logger, serial_id)
     95      elif status == DEVICE_UNAUTHORIZED:
     96          terminate_ip_connection(tlib_logger, adb_logger, serial_id)
     97          tlib_logger.error("Device {serial_id} is not authorized, aborting".format(serial_id=serial_id))
     98          raise RuntimeError("Device {serial_id} is not authorized, aborting".format(serial_id=serial_id))
     99      elif status == DEVICE_UNPLUGGED:
    100          tlib_logger.warn("Device {serial_id} is disconecting, resetting connection".format(serial_id=serial_id))
    101          terminate_ip_connection(tlib_logger, adb_logger, serial_id)
    102
    103      timeout = 0.5
    104      out = None
    105      #We wait up to 3 seconds
    106      for i in range(1, int(3 / timeout)):
    107          out = TestHelper.run_command(adb_logger, ["adb", "connect", serial_id])
    108          log_adb_output(adb_logger, out)
    109
    110          if get_device_status(tlib_logger, adb_logger, serial_id) == DEVICE_ONLINE:
    111              tlib_logger.debug("Connected to device")
    112              return
    113          else:
    114              tlib_logger.debug("Not yet connected")
    115              time.sleep(timeout)
    116
    117      #If we're here, it means connection failed
    118      adb_logger.error(r"Timeout while connecting to device {serial_id}\nADB output:\n{out}".
    119                       format(serial_id=serial_id, out=out))
    120      # noinspection PyUnresolvedReferences
    121      raise RuntimeError(r"Timeout while connecting to device {serial_id}\nADB output:\n{out}".
    122                  format(serial_id=serial_id, out=out))
    123
    124
    125 -def terminate_ip_connection(tlib_logger, adb_logger, serial_id):
    126      """
    127      Disconnects from device
    128
    129      @param tlib_logger: TLib logger
    130      @type tlib_logger: logging.Logger
    131      @param adb_logger: ADB logger
    132      @type adb_logger: logging.Logger
    133      @param serial_id: Device's serial number
    134      @type serial_id: str
    135      """
    136      tlib_logger.debug("Disconnecting from device {serial_id}".format(serial_id=serial_id))
    137      out = TestHelper.run_command(adb_logger, ["adb", "disconnect", serial_id])
    138      log_adb_output(adb_logger, out)
    139
    140
    141 -def close_webdriver(tlib_logger, adb_logger, serial_id):
    142      """
    143      Stops Webdriver app o the device
    144
    145      @param tlib_logger: TLib logger
    146      @type tlib_logger: logging.Logger
    147      @param adb_logger: ADB logger
    148      @type adb_logger: logging.Logger
    149      @param serial_id: Device's serial number
    150      @type serial_id: str
    151      """
    152      tlib_logger.debug("Closing Webdriver on {serial_id}".format(serial_id=serial_id))
    153      if serial_id is None or serial_id == "":
    154          out = TestHelper.run_command(adb_logger,
    155                                       "adb shell am force-stop org.openqa.selenium.android.app", shell=True)
    156      else:
    157          out = TestHelper.run_command(adb_logger,
    158                                       "adb -s %s shell am force-stop org.openqa.selenium.android.app" % serial_id,
    159                                       shell=True)
    160      log_adb_output(adb_logger, out)
    161
    162
    163 -def setup_port_forwarding(tlib_logger, adb_logger, serial_id):
    164      """
    165      Setup port forwarding between computer and device.
    166
    167      @param tlib_logger: TLib logger
    168      @type tlib_logger: logging.Logger
    169      @param adb_logger: ADB logger
    170      @type adb_logger: logging.Logger
    171      @param serial_id: Device's serial number
    172      @type serial_id: str
    173      """
    174      tlib_logger.info("Setting up port forwarding")
    175      if serial_id is None or serial_id == "":
    176          out = TestHelper.run_command(adb_logger, "adb forward tcp:8080 tcp:8080", shell=True)
    177      else:
    178          out = TestHelper.run_command(adb_logger, "adb -s %s forward tcp:8080 tcp:8080" % serial_id, shell=True)
    179      log_adb_output(adb_logger, out)
    180
    181
    182 -def teardown_port_forwarding(tlib_logger, adb_logger):
    183      """
    184      Terminates all port forwarding connections
    185
    186      @param tlib_logger: TLib logger
    187      @type tlib_logger: logging.Logger
    188      @param adb_logger: ADB logger
    189      @type adb_logger: logging.Logger
    190      """
    191      tlib_logger.info("Tearing down port forwarding")
    192      out = TestHelper.run_command(adb_logger, "adb forward --remove-all", shell=True)
    193      log_adb_output(adb_logger, out)
    194
    195
    196 -def start_adb_server(tlib_logger, adb_logger):
    197      """
    198      Stops adb on the machine\n
    199      This can be required by TeamCity so some folders are not locked
    200
    201      @param tlib_logger: TLib logger
    202      @type tlib_logger: logging.Logger
    203      @param adb_logger: ADB logger
    204      @type adb_logger: logging.Logger
    205      """
    206      tlib_logger.info("Starting ADB")
    207      out = TestHelper.run_command(adb_logger, "adb start-server", fail_on_error=False)
    208      log_adb_output(adb_logger, out)
    209
    210
    211 -def stop_adb_server(tlib_logger, adb_logger):
    212      """
    213      Stops adb on the machine\n
    214      This can be required by TeamCity so some folders are not locked
    215
    216      @param tlib_logger: TLib logger
    217      @type tlib_logger: logging.Logger
    218      @param adb_logger: ADB logger
    219      @type adb_logger: logging.Logger
    220      """
    221      tlib_logger.info("Stopping ADB")
    222      out = TestHelper.run_command(adb_logger, "adb kill-server", fail_on_error=False)
    223      log_adb_output(adb_logger, out)
    224
    225
    226 -def start_webdriver(tlib_logger, adb_logger, serial_id):
    227      """
    228      Starts Webdriver app on  the device
    229
    230      @param tlib_logger: TLib logger
    231      @type tlib_logger: logging.Logger
    232      @param adb_logger: ADB logger
    233      @type adb_logger: logging.Logger
    234      @param serial_id: Device's serial number
    235      @type serial_id: str
    236      """
    237      tlib_logger.info("Starting webdriver on the device")
    238      if serial_id is None or serial_id == "":
    239          out = TestHelper.run_command(adb_logger, "adb shell am start -a android.intent.action.MAIN -n "
    240                                       "org.openqa.selenium.android.app/.MainActivity -e debug true", shell=True)
    241      else:
    242          out = TestHelper.run_command(adb_logger, "adb -s %s shell am start -a android.intent.action.MAIN "
    243                                                   "-n org.openqa.selenium.android.app/.MainActivity -e debug true" %
    244                                       serial_id, shell=True)
    245      log_adb_output(adb_logger, out)
    246
    247
    248 -def wait_for_connection_to_webdriver(tlib_logger, adb_logger):
    249      """
    250      Waits up to 3 seconds for a connection to Webdriver
    251
    252      @param tlib_logger: TLib logger
    253      @type tlib_logger: logging.Logger
    254      @param adb_logger: ADB logger
    255      @type adb_logger: logging.Logger
    256      """
    257      #Check connection to webdriver can be established. Retry 3 times
    258      tlib_logger.debug("Waiting for connection to Webdriver")
    259      timeout = 0.5
    260      for i in range(1, int(3 / timeout)):
    261          if is_webdriver_running(tlib_logger, adb_logger, False):
    262              tlib_logger.debug("Webdriver started successfully")
    263              break
    264          tlib_logger.debug("Can't connect to Webdriver, retrying in {timeout} seconds".format(timeout=timeout))
    265          time.sleep(timeout)
    266
    267      if not is_webdriver_running(tlib_logger, adb_logger, False):
    268          tlib_logger.error("Couldn't start Webdriver. Make sure it's installed and running\n"
    269                            "See https://code.google.com/p/selenium/wiki/AndroidDriver#Setup_the_Emulator "
    270                            "for more details")
    271          # noinspection PyUnresolvedReferences
    272          raise RuntimeError("Couldn't start Webdriver. Make sure it's installed and running\n"
    273                      "See https://code.google.com/p/selenium/wiki/AndroidDriver#Setup_the_Emulator for more details")
    274
    275
    276  # noinspection PyUnresolvedReferences
    277 -def setup_webdriver(tlib_logger, adb_logger, serial_id):
    278      """
    279      Connects to a device and starts webdriver
    280
    281      @param tlib_logger: TLib logger
    282      @type tlib_logger: logging.Logger
    283      @param adb_logger: ADB logger
    284      @type adb_logger: logging.Logger
    285      @param serial_id: Device's serial number
    286      @type serial_id: str
    287      """
    288      #Try to connect to Webdriver and exit if success
    289      if is_webdriver_running(tlib_logger, adb_logger, log=False):
    290          tlib_logger.debug("Already connected to device")
    291          return
    292
    293      tlib_logger.info("Connecting to Webdriver")
    294
    295      #Connect to device and setup port forwarding.
    296      if TestHelper.is_valid_ip(serial_id):
    297          setup_ip_connection(tlib_logger, adb_logger, serial_id)
    298
    299      setup_port_forwarding(tlib_logger, adb_logger, serial_id)
    300
    301      #Try again to connect to Webdriver and exit if success
    302      if is_webdriver_running(tlib_logger, adb_logger):
    303          tlib_logger.debug("Connected to Webdriver")
    304          return
    305
    306      # Webdriver not running, start it
    307      start_webdriver(tlib_logger, adb_logger, serial_id)
    308
    309      #Ensure we're connected
    310      wait_for_connection_to_webdriver(tlib_logger, adb_logger)
    311
    312      tlib_logger.info("Connection to Webdriver established")
    313
    314
    315  # noinspection PyUnresolvedReferences
    316 -def teardown_webdriver(tlib_logger, adb_logger, serial_id):
    317      """
    318      Closes webdriver and disconnects from device
    319
    320      @param tlib_logger: TLib logger
    321      @type tlib_logger: logging.Logger
    322      @param adb_logger: ADB logger
    323      @type adb_logger: logging.Logger
    324      @param serial_id: Device's serial number
    325      @type serial_id: str
    326      """
    327      #close selenium WebDriver
    328      tlib_logger.info("Disconnecting from Webdriver")
    329      close_webdriver(tlib_logger, adb_logger, serial_id)
    330
    331      # Terminates IP connection if an IP was given
    332      if TestHelper.is_valid_ip(serial_id):
    333          terminate_ip_connection(tlib_logger, adb_logger, serial_id)
    334
    335      #Stop adb service so it won't lock files required by TeamCity
    336      try:
    337          stop_adb_server(tlib_logger, adb_logger)
    338      except runner.Failed:
    339          #If ADB fails to stop, don't abort test
    340          adb_logger.warn("Error stopping ADB server")
    341
    342      tlib_logger.info("Disconnected from Webdriver")
    343
    344
    345  # noinspection PyUnresolvedReferences
    346 -def start_selendroid_server(tlib_logger, adb_logger, app_path):
    347      """
    348      Start selendroid server and point to apk file
    349
    350      @param tlib_logger: TLib logger
    351      @type tlib_logger: logging.Logger
    352      @param adb_logger: Adb logger
    353      @type adb_logger: logging.Logger
    354      @param app_path: Location of the apk file
    355      @type app_path: str
    356      """
    357      tlib_logger.info("Starting selendroid server")
    358      stop_adb_server(tlib_logger, adb_logger)
    359      start_adb_server(tlib_logger, adb_logger)
    360      process = TestHelper.run_command(tlib_logger,
    361                                       'java -jar "%s" -aut "%s"' % (TestHelper.selendroid_server_jar(), app_path),
    362                                       shell=False,
    363                                       wait_until_complete=False)
    364
    365      trial_left = 20
    366      while not is_selendroid_running(tlib_logger, adb_logger) and trial_left > 0:
    367          time.sleep(0.5)
    368          trial_left -= 1
    369      if trial_left > 0:
    370          return process
    371      raise RuntimeError("Connection to Selendroid failed after 20 tries")
    372
    373
    374 -def is_selendroid_running(tlib_logger, adb_logger):
    375      """
    376      Function to validate if selendroid/appium is running and a connection can be established
    377
    378      @param tlib_logger: TLib logger
    379      @type tlib_logger: logging.Logger
    380      @param adb_logger: ADB logger
    381      @type adb_logger: logging.Logger
    382      @param log: When true, log debugging information
    383      @type log: bool
    384      """
    385      try:
    386          tlib_logger.debug("Checking if Selendroid server is running")
    387          response = requests.get("http://localhost:4444/wd/hub/status", timeout=10)
    388      except requests.ConnectionError as e:
    389          adb_logger.debug("Connection to Selendroid failed:\n%s" % e)
    390          return False
    391
    392      return response.status_code == 200
    393
    394
    395 -def get_android_app_id():
    396      """
    397      @raise RuntimeError: Error connecting to selendroid or converting JSON payload
    398      """
    399      try:
    400          response = requests.get("http://localhost:4444/wd/hub/status")
    401      except Exception as e:
    402          raise RuntimeError("Error connecting to selendroid\n%s" % e.message)
    403
    404      try:
    405          response_json = response.json()
    406      except Exception as e:
    407          raise RuntimeError("Error converting response to json.\nResposne:%s\nError:\n%s" % (response, e.message))
    408
    409      for app in response_json['value']['supportedApps']:
    410          if not app['appId'].startswith('io.selendroid.androiddriver'):
    411              return app['appId']
    412      return None
    413
    414 -def log_adb_output(logger, out):
    415      """
    416      Logs ADB output
    417
    418      @param logger: ADB logger
    419      @type logger: logging.Logger
    420      """
    421      if out[0] is not None and out[0] != '':
    422          logger.debug("\n" + out[0])
    423
    424      if out[1] is not None and out[1] != '':
    425          logger.debug("\n" + out[1])
    426
    427 -def get_android_version():
    428      """
    429      Stops adb on the machine\n
    430      This can be required by TeamCity so some folders are not locked
    431
    432      @param tlib_logger: TLib logger
    433      @type tlib_logger: logging.Logger
    434      @param adb_logger: ADB logger
    435      @type adb_logger: logging.Logger
    436      """
    437      tlib_logger = get_tlib_logger()
    438      adb_logger = get_adb_logger()
    439
    440      start_adb_server(tlib_logger, adb_logger)
    441      tlib_logger.info("Starting ADB")
    442      out = TestHelper.run_command(adb_logger, "adb shell getprop ro.build.version.release", fail_on_error=False)
    443      log_adb_output(adb_logger, out)
    444      return out
    445
    446  # noinspection PyUnresolvedReferences
    447 -def start_appium_server(tlib_logger, adb_logger):
    448      """
    449      Start appium server listening on port 4444 (default)
    450
    451      @param tlib_logger: TLib logger
    452      @type tlib_logger: logging.Logger
    453      @param adb_logger: Adb logger
    454      @type adb_logger: logging.Logger
    455      @param app_path: Location of the apk file
    456      @type app_path: str
    457      """
    458      def appium_libs():
    459          return os.path.join("lib", "server", "main.js")
    460
    461      def node_js():
    462          return os.path.join("C:\\", "appium", "node.exe")
    463
    464      def working_dir():
    465          return os.path.abspath(os.path.join("C:\\", "appium", "node_modules", "appium"))
    466
    467      def appium_command_line():
    468          command_line = "{node_js} {appium} --address 127.0.0.1 --port 4444 --no-reset".format(node_js=node_js(),
    469                                                                                     appium=appium_libs())
    470          return command_line
    471
    472
    473      tlib_logger.info("Starting appium server")
    474
    475      if not os.path.isfile(node_js()):
    476          tlib_logger.error("Appium is not installed on c:\appium")
    477          raise RuntimeError("Appium is not installed on c:\appium\n"
    478                             "See https://wiki.ypg.com/pages/viewpage.action?pageId=163352061#Python+TLib-InstallTLib "
    479                             "for more information")
    480
    481      process = TestHelper.run_command(tlib_logger,
    482                                         appium_command_line(),
    483                                         shell=False,
    484                                         cwd=working_dir(),
    485                                         wait_until_complete=False)
    486
    487
    488      trial_left = 20
    489      while not is_selendroid_running(tlib_logger, adb_logger) and trial_left > 0:
    490          time.sleep(0.5)
    491          trial_left -= 1
    492
    493      if trial_left > 0:
    494
    495          return process
    496      return None
    497