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