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

# Source Code for Module tlib.base.AndroidHelper

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