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 -def stop_rogue_selenium(tlib_logger):
345 """ 346 If the selenium server is running without a head, kill it 347 """ 348 import wmi 349 c = wmi.WMI() 350 351 for process in c.Win32_Process(): 352 if "java" in process.name and "selendroid" in process.CommandLine: 353 tlib_logger.info("Trying to kill a Rogue Selenium Server before starting a new one") 354 res = os.system("taskkill /F /PID %s"%process.ProcessId) 355 if res != 0: 356 raise RuntimeError("Could not kill the Rogue Selenium Server") 357 break
358 359 360 # noinspection PyUnresolvedReferences
361 -def start_selendroid_server(tlib_logger, adb_logger, app_path, server_port=38080):
362 """ 363 Start selendroid server and point to apk file 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 app_path: Location of the apk file 370 @type app_path: str 371 """ 372 tlib_logger.info("Starting selendroid server") 373 stop_adb_server(tlib_logger, adb_logger) 374 start_adb_server(tlib_logger, adb_logger) 375 376 stop_rogue_selenium(tlib_logger) 377 process = TestHelper.run_command(tlib_logger, 378 'java -jar "%s" -selendroidServerPort %s -aut "%s"' % (TestHelper.selendroid_server_jar(), 379 server_port, 380 app_path), 381 shell=False, 382 wait_until_complete=False) 383 384 trial_left = 20 385 while not is_selendroid_running(tlib_logger, adb_logger) and trial_left > 0: 386 time.sleep(0.5) 387 trial_left -= 1 388 if trial_left > 0: 389 return process 390 raise RuntimeError("Connection to Selendroid failed after 20 tries")
391 392
393 -def is_selendroid_running(tlib_logger, adb_logger):
394 """ 395 Function to validate if selendroid/appium is running and a connection can be established 396 397 @param tlib_logger: TLib logger 398 @type tlib_logger: logging.Logger 399 @param adb_logger: ADB logger 400 @type adb_logger: logging.Logger 401 @param log: When true, log debugging information 402 @type log: bool 403 """ 404 try: 405 tlib_logger.debug("Checking if Selendroid server is running") 406 response = requests.get("http://localhost:4444/wd/hub/status", timeout=10) 407 except requests.ConnectionError as e: 408 adb_logger.debug("Connection to Selendroid failed:\n%s" % e) 409 return False 410 except requests.exceptions.Timeout as e: 411 adb_logger.debug("Connection to Selendroid timed out:\n%s" % e) 412 return False 413 414 return response.status_code == 200
415 416
417 -def get_android_app_id():
418 """ 419 @raise RuntimeError: Error connecting to selendroid or converting JSON payload 420 """ 421 try: 422 response = requests.get("http://localhost:4444/wd/hub/status") 423 except Exception as e: 424 raise RuntimeError("Error connecting to selendroid\n%s" % e.message) 425 426 try: 427 response_json = response.json() 428 except Exception as e: 429 raise RuntimeError("Error converting response to json.\nResposne:%s\nError:\n%s" % (response, e.message)) 430 431 for app in response_json['value']['supportedApps']: 432 if not app['appId'].startswith('io.selendroid.androiddriver'): 433 return app['appId'] 434 return None
435
436 -def log_adb_output(logger, out):
437 """ 438 Logs ADB output 439 440 @param logger: ADB logger 441 @type logger: logging.Logger 442 """ 443 if out[0] is not None and out[0] != '': 444 logger.debug("\n" + out[0]) 445 446 if out[1] is not None and out[1] != '': 447 logger.debug("\n" + out[1])
448
449 -def get_android_version():
450 """ 451 Stops adb on the machine\n 452 This can be required by TeamCity so some folders are not locked 453 454 @param tlib_logger: TLib logger 455 @type tlib_logger: logging.Logger 456 @param adb_logger: ADB logger 457 @type adb_logger: logging.Logger 458 """ 459 tlib_logger = get_tlib_logger() 460 adb_logger = get_adb_logger() 461 462 start_adb_server(tlib_logger, adb_logger) 463 tlib_logger.info("Starting ADB") 464 out = TestHelper.run_command(adb_logger, "adb shell getprop ro.build.version.release", fail_on_error=False) 465 log_adb_output(adb_logger, out) 466 return out
467 468 # noinspection PyUnresolvedReferences
469 -def start_appium_server(tlib_logger, adb_logger):
470 """ 471 Start appium server listening on port 4444 (default) 472 473 @param tlib_logger: TLib logger 474 @type tlib_logger: logging.Logger 475 @param adb_logger: Adb logger 476 @type adb_logger: logging.Logger 477 @param app_path: Location of the apk file 478 @type app_path: str 479 """ 480 def appium_libs(): 481 return os.path.join("lib", "server", "main.js")
482 483 def node_js(): 484 return os.path.join("C:\\", "appium", "node.exe") 485 486 def working_dir(): 487 return os.path.abspath(os.path.join("C:\\", "appium", "node_modules", "appium")) 488 489 def appium_command_line(): 490 command_line = "{node_js} {appium} --address 127.0.0.1 --port 4444 --no-reset".format(node_js=node_js(), 491 appium=appium_libs()) 492 return command_line 493 494 495 tlib_logger.info("Starting appium server") 496 497 if not os.path.isfile(node_js()): 498 tlib_logger.error("Appium is not installed on c:\appium") 499 raise RuntimeError("Appium is not installed on c:\appium\n" 500 "See https://wiki.ypg.com/pages/viewpage.action?pageId=163352061#Python+TLib-InstallTLib " 501 "for more information") 502 503 process = TestHelper.run_command(tlib_logger, 504 appium_command_line(), 505 shell=False, 506 cwd=working_dir(), 507 wait_until_complete=False) 508 509 510 trial_left = 20 511 while not is_selendroid_running(tlib_logger, adb_logger) and trial_left > 0: 512 time.sleep(0.5) 513 trial_left -= 1 514 515 if trial_left > 0: 516 517 return process 518 return None 519