Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Automating Your AVD Test Run - Extended Ver.

F1sherKK
December 02, 2017

Automating Your AVD Test Run - Extended Ver.

My talk on how to make script that automatically creates, configures and launches AVD instances and starts UI tests on them.

Link to PDF for better quality: https://drive.google.com/open?id=1IjsvMsN92pTJpafldR1IyJlg1ZA4hR3U

F1sherKK

December 02, 2017
Tweet

More Decks by F1sherKK

Other Decks in Programming

Transcript

  1. Goal of this presentation: - Introduce you to ADB executables

    and commands which might be helpful in automating AVD run. - Introduce you to problems that you will need to face in order to create such automation on you own. - Present how to solve those problems. - Give you brief idea how you interact with command line from programming language.
  2. Using command line command from Python code import subprocess def

    execute_shell(cmd, display_cmd=True, display_output=True): if display_cmd: print("- Executing shell command: {}".format(cmd)) with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: output = "" for line in p.stdout: if display_output: print(line.strip()) output += line if p.returncode != 0: raise Exception(p.returncode, p.args) return output
  3. Using command line command from Python code import subprocess def

    execute_shell(cmd, display_cmd=True, display_output=True): if display_cmd: print("- Executing shell command: {}".format(cmd)) with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: output = "" for line in p.stdout: if display_output: print(line.strip()) output += line if p.returncode != 0: raise Exception(p.returncode, p.args) return output Blocking
  4. Input Parts of script Machine Clean Up AVD Preparation .apk

    Preparation Testing Machine Clean Up Code snippets provided
  5. Restarting ADB Purpose: - Resetting currently undergoing ADB processes in

    order to avoid potential ADB errors: error: protocol fault (couldn't read status): Connection reset by peer Kill ADB Server Start ADB Server Diagram: Any operation that is in grey box, is using execute_shell function
  6. Killing ADB Server Binary file location: Hint: <SDK_location>/platform-tools/adb <SDK_location> can

    be saved in ANDROID_HOME environment variable or ANDROID_SDK_ROOT (ANDROID_HOME got deprecated in Android Studio lately) Command: $ adb kill-server
  7. Killing ADB Server Command line: $ adb kill-server * server

    not running * - ADB server not running case $ adb kill-server - ADB server running case
  8. Starting ADB Server Command line: $ adb start-server * daemon

    not running. starting it now at tcp:5037 * * daemon started successfully * - ADB server not running case $ adb start-server - ADB server running case
  9. Scripting - RestartAdbAction.py import os from utils.CommandLineUtils import execute_shell #

    Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] ADB_BIN_DIR = os.path.join(SDK_DIR, "") + "platform-tools/adb" # ADB Commands ADB_KILL_SERVER_CMD = "{} {}".format(ADB_BIN_DIR, "kill-server") ADB_START_SERVER_CMD = "{} {}".format(ADB_BIN_DIR, "start-server") def execute(): print("\n----- RestartAdbAction runs! -----") execute_shell(ADB_KILL_SERVER_CMD, display_cmd=True, display_output=True) execute_shell(ADB_START_SERVER_CMD, display_cmd=True, display_output=True)
  10. Killing all currently launched AVD Purpose: - PROPERLY turn off

    currently running AVD instances. - Free memory allocated by HAXM to allow starting new AVD instances. Get Visible Devices Diagram: List of (device name, status) Loop Next item starts with “emulator” Kill AVD Instance continue No Yes
  11. Getting visible AVD devices Command line: $ adb devices List

    of devices attached emulator-5554 offline - Emulator instance init case $ adb devices List of devices attached emulator-5554 device - Emulator system booting case $ adb devices List of devices emulator-5554 unauthorized - Device unauthorized case
  12. Getting visible AVD devices $ adb devices List of devices

    attached - No devices case Command line:
  13. Killing AVD instance Binary file location: <SDK_location>/platform-tools/adb Command: $ adb

    -s AVD_NAME_IN_ADB emu kill Where: -s - specific device on which command should be executed AVD_NAME_IN_ADB - serial name of AVD which is visible after in ADB e.g after calling `adb devices` command Native AVD are always named in the same pattern: ‘emulator-` + four digit number which is a port to which AVD was assigned (in example emulator-5556)
  14. Scripting - KillVisibleAvdInstancesAction.py import os from utils.CommandLineUtils import execute_shell #

    Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] ADB_BIN_DIR = os.path.join(SDK_DIR, "") + "platform-tools/adb" # ADB Commands ADB_GET_DEVICES_CMD = "{} {}".format(ADB_BIN_DIR, "devices") ADB_KILL_EMULATOR_TEMPLATE = "{} -s {} {}" def _get_visible_emulators(adb_devices_result): """Parses 'adb devices' output and returns list of emulators.""" visible_emulators = list() for line in adb_devices_result.splitlines(): if line.startswith("emulator"): emulator_name, emulator_status = tuple(line.split()) visible_emulators.append({"name": emulator_name, "status": emulator_status}) return visible_emulators (…)
  15. Scripting - KillVisibleAvdInstancesAction.py (…) def execute(): print("\n----- KillVisibleAvdInstances runs! -----")

    # Getting list of emulators get_devices_result = execute_shell( ADB_GET_DEVICES_CMD, display_cmd=True, display_output=True) visible_emulators = _get_visible_emulators(get_devices_result) # Killing visible emulators if visible_emulators: for emulator in visible_emulators: kill_emu_cmd = ADB_KILL_EMULATOR_TEMPLATE.format( ADB_BIN_DIR, emulator["name"], "emu kill") execute_shell(kill_emu_cmd, display_cmd=True, display_output=True) else: print("No visible emulator instances.")
  16. Waiting for AVD to turn off Purpose: - AVD are

    no longer forcefully killed. There is a save state process and we need to synchronise with that before we make any other AVD related operations. Diagram: Loop Get Visible Devices “emulator” in list No Yes List of (device name, status) sleep(5) break Timeout start time current time - start time >= timeout Yes No
  17. Scripting - KillVisibleAvdInstancesAction.py import os import time from utils.CommandLineUtils import

    execute_shell # Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] ADB_BIN_DIR = os.path.join(SDK_DIR, "") + "platform-tools/adb" # ADB Commands ADB_GET_DEVICES_CMD = "{} {}".format(ADB_BIN_DIR, "devices") def _get_visible_emulators(adb_devices_result): … def execute(timeout=120): print("\n----- WaitForAvdToTurnOffAction runs! -----") start_time = time.time() while True: # Checking timeout current_time = time.time() if current_time - start_time >= timeout: break # Getting list of devices get_devices_result = execute_shell(ADB_GET_DEVICES_CMD, display_cmd=True, display_output=True) # Checking emulator state if not _get_visible_emulators(get_devices_result): break # Avoid spamming to ADB time.sleep(5) print("All emulator instances are turned off.")
  18. Scripting - Launcher.py from machine_clean_up_part import ( RestartAdbAction, KillVisibleAvdInstancesAction, WaitForAvdToTurnOffAction

    ) if __name__ == "__main__": # Machine Clean Up part RestartAdbAction.execute() KillVisibleAvdInstancesAction.execute() WaitForAvdToTurnOffAction.execute()
  19. AVD Preparation Part -Creating fresh AVD -Downloading system images -AVD

    configuration -Checking if AVD is already created -Deleting AVD -Re-creating AVD -Get available ports -Launching AVD instances -Waiting for AVD instances to properly launch Actions:
  20. Number of AVD instances - Number of instances is limited

    not only to machine RAM capacity but also to RAM limit set in Intel® Hardware Accelerated Execution Manager (Intel® HAXM).
  21. Number of AVD instances - Number of instances is limited

    not only to machine RAM capacity but also to RAM limit set in Intel® Hardware Accelerated Execution Manager (Intel® HAXM). - In the past you had to download HAXM from Intel official website. Now you can do it from Android Studio. RAM limit is set during installation.
  22. Number of AVD instances - Number of instances is limited

    not only to machine RAM capacity but also to RAM limit set in Intel® Hardware Accelerated Execution Manager (Intel® HAXM). - In the past you had to download HAXM from Intel official website. Now you can do it from Android Studio. RAM limit is set during installation. - First statement is not really true. If you start all AVD in the same moment RAM limit won’t kick in. e.g. you can start even 10 AVD at the same time with 2GB RAM limit. Don’t do it, your machine will simply restart from OOM.
  23. Number of AVD instances - Number of instances is limited

    not only to machine RAM capacity but also to RAM limit set in Intel® Hardware Accelerated Execution Manager (Intel® HAXM). - In the past you had to download HAXM from Intel official website. Now you can do it from Android Studio. RAM limit is set during installation. - First statement is not really true. If you start all AVD in the same moment RAM limit won’t kick in. e.g. you can start even 10 AVD at the same time with 2GB RAM limit. Don’t do it, your machine will simply restart from OOM. - Pick number of AVD according to your machine power. If you have enough RAM but not enough computing power AVD will lag and that will affect your tests.
  24. Creating fresh AVD Purpose: - Automatically configure each machine on

    which you would like to run tests. Diagram: List with AVD data Create AVD Next item Loop
  25. Creating fresh AVD Binary file location: <SDK_location>/tools/bin/avdmanager Command: $ avdmanager

    create avd —-name AVD_NAME —-abi AVD_ABI —-package AVD_PACKAGE Required parameters: AVD_NAME - unique alias under which all settings and properties of AVD will be connected to. Can take any value you like. AVD_ABI - application binary interface e.g. x86, x86_64, google_apis/x86_64 (https://developer.android.com/ndk/guides/abis.html) AVD_PACKAGE - system image to be launched. You can pick Android SDK version to be used and if it should support Google Apis or Google Play Store. Example: • system-images;android-23;google_apis;x86_64
  26. Creating fresh AVD Binary file location: <SDK_location>/tools/bin/avdmanager Command: $ avdmanager

    create avd —-name AVD_NAME —-abi AVD_ABI —-package AVD_PACKAGE —-sdcard AVD_SD_CARD Useful optional parameters (for now): AVD_SD_CARD - SD card size in Mb e.g. 512M
  27. How to list packages Binary file location: <SDK_location>/tools/bin/sdkmanager Command: $

    sdkmanager —-list —-verbose Where: --list - lists all available packages --verbose - took me a while before I have found this parameter, it unwraps all package names
  28. How to list packages system-images;a...ult;armeabi-v7a | 4 | ARM EABI

    v7a System Image system-images;a...-10;default;x86 | 4 | Intel x86 Atom System Image system-images;a...pis;armeabi-v7a | 5 | Google APIs ARM EABI v7a Syste... system-images;a...google_apis;x86 | 5 | Google APIs Intel x86 Atom Sys… $ sdkmanager —-list VS system-images;android-10;default;armeabi-v7a Description: ARM EABI v7a System Image Version: 4 system-images;android-10;default;x86 Description: Intel x86 Atom System Image Version: 4 system-images;android-10;google_apis;armeabi-v7a Description: Google APIs ARM EABI v7a System Image Version: 5 system-images;android-10;google_apis;x86 Description: Google APIs Intel x86 Atom System Image Version: 5 $ sdkmanager —-list —-verbose
  29. Downloading packages Purpose: We want to be able to provide

    any config we like and make sure that script will fetch all dependencies and files that we are lacking. Diagram: List system- image packages Download Package Next item Loop
  30. Downloading packages Binary file location: <SDK_location>/tools/bin/sdkmanager Command: $ sdkmanager PACKAGE

    Where: PACKAGE - correct package string to be fetched from Google repo
  31. Uninstalling packages Binary file location: <SDK_location>/tools/bin/sdkmanager Command: $ sdkmanager -uninstall

    PACKAGE Where: PACKAGE - correct package string to be fetched from Google repo
  32. Downloading packages Pick package e.g: system-images;android-10;google_apis;x86 And use it: $

    sdkmanager "system-images;android-26;google_apis;x86" [=======================================] 100% Unzipping... x86/data/benchmarkte $ sdkmanager --uninstall "system-images;android-26;google_apis;x86" [=======================================] 100% Fetch remote repository...
  33. Scripting - UpdatePackagesAction.py import os from utils.CommandLineUtils import execute_shell #

    Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] SDKMANAGER_BIN_DIR = os.path.join(SDK_DIR, "") + "tools/bin/sdkmanager" # Command assembly UPDATE_PACKAGE_CMD_TEMPLATE = '{} "{}"' def execute(config): print("\n----- UpdatePackagesAction runs! -----") for package in [avd["package"] for avd in config["avd_list"]]: update_package_cmd = UPDATE_PACKAGE_CMD_TEMPLATE.format( SDKMANAGER_BIN_DIR, package) execute_shell(update_package_cmd, display_cmd=True, display_output=False)
  34. Let’s try creating AVD: $ avdmanager create avd --name "testAVD"

    --package "system-images;android-26;google_apis;x86" --abi "google_apis/x86" Do you wish to create a custom hardware profile? [no]
  35. How to handle AVD config? Let’s try creating AVD: If

    you answer `yes` then you will be asked 52 low level questions about where to store cache, where to allocate memory during AVD initial boot, networking, CPU properties and so on… $ avdmanager create avd --name "testAVD" --package "system-images;android-26;google_apis;x86" --abi "google_apis/x86" Do you wish to create a custom hardware profile? [no]
  36. You can automate that… $ echo "answer1 answer2 … answer52"

    | avdmanager create avd --name "testAVD" --package "system-images;android-26;google_apis;x86" --abi "google_apis/x86" But if you are scripting, then you can make it simpler in my opinion. First of all you don’t need to bother with those answers. How to handle AVD config? Let’s try creating AVD: If you answer `yes` then you will be asked 52 low level questions about where to store cache, where to allocate memory during AVD initial boot, networking, CPU properties and so on… $ avdmanager create avd --name "testAVD" --package "system-images;android-26;google_apis;x86" --abi "google_apis/x86" Do you wish to create a custom hardware profile? [no]
  37. How to handle AVD config - method for scripting Create

    AVD and refuse to provide custom hardware config profile: $ echo "no" | avdmanager create avd --name "AVDfromTerminal" --package "system-images;android-23;google_apis;x86" --abi "google_apis/x86" Do you wish to create a custom hardware profile? [no] no
  38. Scripting - CreateAvdAction.py import os from utils.CommandLineUtils import execute_shell #

    Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] AVDMANAGER_BIN_DIR = os.path.join(SDK_DIR, "") + "tools/bin/avdmanager" # Command assembly CREATE_AVD_CMD_TEMPLATE = 'echo "no" | {} create avd --name "{}" --abi "{}" --package “{}"’ SD_CARD_CMD_TEMPLATE = "--sdcard {}M" def execute(config): print("\n----- CreateAVDAction runs! -----") for avd in config["avd_list"]: create_avd_cmd = CREATE_AVD_CMD_TEMPLATE.format( AVDMANAGER_BIN_DIR, avd["name"], avd["abi"], avd["package"]) # Adding optional sdcard part based config if "sdcard" in avd.keys() and avd["sdcard"] != “": create_avd_cmd = " ".join([create_avd_cmd, SD_CARD_CMD_TEMPLATE.format(avd["sdcard"])]) execute_shell(create_avd_cmd, display_cmd=True, display_output=True)
  39. Where AVD is located after creation? Default: ~/.android/avd/ All device

    settings are stored there Current memory state of AVD (Try compressing it and check what happens with size)
  40. Where AVD is located after creation? How to change it?

    1. Set new environment variable named ANDROID_SDK_HOME and put path to which `~/.android/avd/` file structure will be written. 2. Set new environment variable name ANDROID_AVD_HOME to specify directory only for AVD files. 3. Add parameter --path AVD_PATH to your `avdmanager create avd` command. Default: ~/.android/avd/
  41. Where AVD is located after creation? How to change it?

    1. Set new environment variable named ANDROID_SDK_HOME and put path to which `~/.android/avd/` file structure will be written. 2. Set new environment variable name ANDROID_AVD_HOME to specify directory only for AVD files. 3. Add parameter --path AVD_PATH to your `avdmanager create avd` command. Default: ~/.android/avd/ For me it’s bugged (tested 21.11.2017) --path ”~/dir1/dir2/dir3/” AVD files appear in dir2 Instead of dir3
  42. How to handle AVD config - method for scripting Create

    AVD with AVD Manager via Android Studio without modifying anything - just click through it.
  43. avd.ini.encoding=UTF-8 AvdId=Nexus_5X_API_26 PlayStore.enabled=false abi.type=x86 avd.ini.displayname=Nexus 5X API 26 disk.dataPartition.size=800M hw.accelerometer=yes

    hw.audioInput=yes hw.battery=yes hw.camera.back=emulated hw.camera.front=emulated hw.cpu.arch=x86 hw.cpu.ncore=4 hw.dPad=no hw.device.hash2=MD5:1be89bc42ec9644d4b77968b23474980 hw.device.manufacturer=Google hw.device.name=Nexus 5X hw.gps=yes hw.gpu.enabled=yes hw.gpu.mode=auto hw.initialOrientation=Portrait hw.keyboard=yes hw.lcd.density=420 hw.mainKeys=no hw.ramSize=1536 hw.sdCard=yes hw.sensors.orientation=yes hw.sensors.proximity=yes hw.trackBall=no image.sysdir.1=system-images/android-26/google_apis/x86/ runtime.network.latency=none runtime.network.speed=full sdcard.size=100M showDeviceFrame=yes skin.dynamic=yes skin.name=nexus_5x skin.path=/Users/F1sherKK/Library/Android/sdk/skins/nexus_5x tag.display=Google APIs tag.id=google_apis vm.heapSize=256 Copy content of config.ini file of cerated emulator. Important: Do not use ‘Copy to Clipboard and Close’ function as copied content is different than config.ini file content. (It’s parsed for better readability)
  44. """ avd.ini.encoding=UTF-8 AvdId={} PlayStore.enabled=false abi.type={} avd.ini.displayname={} disk.dataPartition.size={} hw.accelerometer=yes hw.audioInput=yes hw.battery=yes

    hw.camera.back=emulated hw.camera.front=emulated hw.cpu.arch={} hw.cpu.ncore=4 hw.dPad=no hw.device.hash2={} hw.device.manufacturer=Google hw.device.name={} hw.gps=yes hw.gpu.enabled=yes hw.gpu.mode=auto hw.initialOrientation=Portrait hw.keyboard=yes hw.lcd.density={} hw.mainKeys=no hw.ramSize=1536 hw.sdCard=yes hw.sensors.orientation=yes hw.sensors.proximity=yes hw.trackBall=no image.sysdir.1={} runtime.network.latency=none runtime.network.speed=full sdcard.size={} showDeviceFrame=yes skin.dynamic=yes skin.name={} skin.path={} tag.display={} tag.id={} vm.heapSize=256 """ 1. Make string template from it. 2. Every time you create new AVD simply overwrite it’s config.ini. based on AVD_NAME based on AVD_NAME based on AVD_NAME your choice your choice your choice based on AVD_PACKAGE delete it based on AVD_ABI your choice based on AVD_ABI based on AVD_ABI based on AVD_SD_CARD based on AVD_ABI
  45. Scripting - ProvideAvdConfigAction.py import os from utils import ConfigFactory def

    execute(config): print("\n----- CreateAVDAction runs! -----") for avd in config["avd_list"]: # Creating config for AVD config_string = ConfigFactory.create( avd["name"], avd["abi"], avd["package"], avd["skin_name"], avd["device"], avd[“screen_density"], avd["hard_drive_size"], avd["sdcard"] if "sdcard" in avd.keys() else "" ) # Locating config.ini file if os.getenv("ANDROID_SDK_HOME") is not None: path = os.path.join(os.getenv("ANDROID_SDK_HOME"), ".android", "avd", avd["name"] + ".avd") elif os.getenv("ANDROID_AVD_HOME") is not None: path = os.path.join(os.getenv("ANDROID_AVD_HOME"), avd["name"] + ".avd") else: path = os.path.join(os.getenv("HOME"), ".android", "avd", avd["name"] + ".avd") config_file = os.path.join(path, "config.ini") if os.path.isfile(config_file): print("- Found config.ini file of AVD '{}' - modifying.".format(avd["name"])) else: raise Exception("Unable to find config.ini file of AVD '{}'.".format(avd["name"])) # Appending config.ini content with open(config_file, "w") as file: file.write(config_string)
  46. Checking if AVD exists Purpose: - Avoiding removal on non-existent

    files. - Avoiding overwriting existing AVD. Diagram: Get Existing AVD Next item Loop List of AVD names to create List with results is in AVD list store (AVD, false) store (AVD, true) List of AVD No Yes
  47. Command line: Checking if AVD exists $ avdmanager list avd

    Parsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-19/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-24/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/25.0.0/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/25.0.2/package.xmlParsing /Users/F1sherKK/ Library/Android/sdk/build-tools/25.0.3/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/26.0.0-rc2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build- tools/26.0.1/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/26.0.2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/27.0.1/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/docs/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/emulator/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/android/ m2repository/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/auto/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/google_play_services/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/m2repository/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/simulators/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/extras/intel/Hardware_Accelerated_Execution_Manager/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/m2repository/com/android/support/ constraint/constraint-layout-solver/1.0.2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/m2repository/com/android/support/constraint/constraint-layout/1.0.2/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/lldb/3.0/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/patcher/v1/package.xmlParsing /Users/F1sherKK/Library/Android/ sdk/patcher/v4/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platform-tools/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-13/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/platforms/android-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-21/package.xmlParsing /Users/F1sherKK/Library/Android/ sdk/platforms/android-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-24/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-25/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-21/package.xmlParsing /Users/F1sherKK/ Library/Android/sdk/sources/android-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-24/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/ android-25/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-17/default/mips/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/ android-23/google_apis/armeabi-v7a/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-23/google_apis/x86_64/package.xmlParsing /Users/F1sherKK/Library/ Android/sdk/system-images/android-24/default/arm64-v8a/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-24/default/armeabi-v7a/package.xmlParsing /Users/ F1sherKK/Library/Android/sdk/system-images/android-26/google_apis/x86/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/tools/package.xmlAvailable Android Virtual Devices: Name: GoogleApis_API26-0 Device: Nexus 5X (Google) Path: /Users/F1sherKK/.android/avd/GoogleApis_API26-0.avd Target: Google APIs (Google Inc.) Based on: Android 8.0 (Oreo) Tag/ABI: google_apis/x86 Skin: nexus_5x Sdcard: M --------- Name: GoogleApis_API26-1 Device: Nexus 5X (Google) Path: /Users/F1sherKK/.android/avd/GoogleApis_API26-1.avd Target: Google APIs (Google Inc.) Based on: Android 8.0 (Oreo) Tag/ABI: google_apis/x86 Skin: nexus_5x Sdcard: 512M --------- (...)
  48. Command line: Checking if AVD exists $ avdmanager list avd

    Parsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-19/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-24/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/25.0.0/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/25.0.2/package.xmlParsing /Users/F1sherKK/ Library/Android/sdk/build-tools/25.0.3/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/26.0.0-rc2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build- tools/26.0.1/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/26.0.2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/27.0.1/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/docs/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/emulator/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/android/ m2repository/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/auto/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/google_play_services/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/m2repository/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/simulators/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/extras/intel/Hardware_Accelerated_Execution_Manager/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/m2repository/com/android/support/ constraint/constraint-layout-solver/1.0.2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/m2repository/com/android/support/constraint/constraint-layout/1.0.2/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/lldb/3.0/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/patcher/v1/package.xmlParsing /Users/F1sherKK/Library/Android/ sdk/patcher/v4/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platform-tools/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-13/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/platforms/android-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-21/package.xmlParsing /Users/F1sherKK/Library/Android/ sdk/platforms/android-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-24/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-25/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-21/package.xmlParsing /Users/F1sherKK/ Library/Android/sdk/sources/android-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-24/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/ android-25/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-17/default/mips/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/ android-23/google_apis/armeabi-v7a/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-23/google_apis/x86_64/package.xmlParsing /Users/F1sherKK/Library/ Android/sdk/system-images/android-24/default/arm64-v8a/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-24/default/armeabi-v7a/package.xmlParsing /Users/ F1sherKK/Library/Android/sdk/system-images/android-26/google_apis/x86/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/tools/package.xmlAvailable Android Virtual Devices: Name: GoogleApis_API26-0 Device: Nexus 5X (Google) Path: /Users/F1sherKK/.android/avd/GoogleApis_API26-0.avd Target: Google APIs (Google Inc.) Based on: Android 8.0 (Oreo) Tag/ABI: google_apis/x86 Skin: nexus_5x Sdcard: M --------- Name: GoogleApis_API26-1 Device: Nexus 5X (Google) Path: /Users/F1sherKK/.android/avd/GoogleApis_API26-1.avd Target: Google APIs (Google Inc.) Based on: Android 8.0 (Oreo) Tag/ABI: google_apis/x86 Skin: nexus_5x Sdcard: 512M --------- (...) Detailed output!
  49. Scripting - CheckIfAvdExistsAction.py from utils.CommandLineUtils import execute_shell # Path to

    executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] EMULATOR_BIN_DIR = os.path.join(SDK_DIR, "") + "emulator/emulator" # Command assembly GET_AVD_LIST_CMD = "{} {}".format(EMULATOR_BIN_DIR, "-list-avds") def _get_avd_names(list_avds_result): """Parses 'emulator -list-avds' output and returns list of AVD names.""" return [avd for avd in list_avds_result.splitlines()] def execute(config): print("\n----- CheckIfAVDExistsAction runs! -----") # Get existing AVD list list_avds_result = execute_shell(GET_AVD_LIST_CMD, display_cmd=True, display_output=True) existing_avd_list = _get_avd_names(list_avds_result) # Returning list with statuses for each AVD return [(avd["name"], avd["name"] in existing_avd_list) for avd in config["avd_list"]]
  50. Deleting AVD Purpose: - We are capable of creating separated

    environment, where script is creating it’s own AVD and cleans them after the the work. - Delete existing AVD to make space for new ones with fresh memory state. Diagram: Next item Loop List of (AVD name, exists) exists continue Delete AVD No Yes
  51. Binary file location: Command: $ avdmanager delete avd —-name AVD_NAME

    Where: AVD_NAME - unique alias under which all settings and properties of AVD will be connected to. Can take any value you like. <SDK_location>/tools/bin/avdmanager Deleting AVD
  52. Deleting AVD Command line: $ avdmanager delete avd —-name "GoogleApis_API26-2"

    Parsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add- ons/addon-google_apis-google-19/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-23/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/add-ons/addon-google_apis-google-24/package.xmlParsing /Users/F1sherKK/Library/ Android/sdk/build-tools/25.0.0/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/25.0.2/package.xmlParsing /Users/F1sherKK/ Library/Android/sdk/build-tools/25.0.3/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/26.0.0-rc2/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/build-tools/26.0.1/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/26.0.2/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/build-tools/27.0.1/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/docs/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/emulator/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/android/ m2repository/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/auto/package.xmlParsing /Users/F1sherKK/Library/Android/ sdk/extras/google/google_play_services/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/m2repository/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/google/simulators/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/ extras/intel/Hardware_Accelerated_Execution_Manager/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/m2repository/com/android/ support/constraint/constraint-layout-solver/1.0.2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/extras/m2repository/com/android/ support/constraint/constraint-layout/1.0.2/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/lldb/3.0/package.xmlParsing /Users/ F1sherKK/Library/Android/sdk/patcher/v1/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/patcher/v4/package.xmlParsing /Users/ F1sherKK/Library/Android/sdk/platform-tools/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-13/package.xmlParsing / Users/F1sherKK/Library/Android/sdk/platforms/android-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-21/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/ platforms/android-24/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/platforms/android-25/package.xmlParsing /Users/F1sherKK/Library/ Android/sdk/sources/android-16/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-21/package.xmlParsing /Users/F1sherKK/ Library/Android/sdk/sources/android-23/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/sources/android-24/package.xmlParsing /Users/ F1sherKK/Library/Android/sdk/sources/android-25/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-17/default/ mips/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-23/google_apis/armeabi-v7a/package.xmlParsing /Users/ F1sherKK/Library/Android/sdk/system-images/android-23/google_apis/x86_64/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system- images/android-24/default/arm64-v8a/package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-24/default/armeabi-v7a/ package.xmlParsing /Users/F1sherKK/Library/Android/sdk/system-images/android-26/google_apis/x86/package.xmlParsing /Users/F1sherKK/Library/ Android/sdk/tools/package.xmlDeleting file /Users/F1sherKK/.android/avd/GoogleApis_API26-2.ini Deleting folder /Users/F1sherKK/.android/avd/GoogleApis_API26-2.avd AVD 'GoogleApis_API26-2' deleted. - AVD exists case
  53. Deleting AVD Command line: $ avdmanager delete avd -n GoogleApis_API26-2

    Error: There is no Android Virtual Device named 'GoogleApis_API26-2'. null - AVD does not exist
  54. Scripting - DeleteExistingAvdAction.py import os from utils.CommandLineUtils import execute_shell #

    Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] AVDMANAGER_BIN_DIR = os.path.join(SDK_DIR, "") + "tools/bin/avdmanager" # Command assembly CREATE_AVD_CMD_TEMPLATE = '{} delete avd --name "{}"' def execute(avd_name_status_list): print("\n----- DeleteExistingAVDAction runs! -----") for avd_name in [avd_name for avd_name, exists in avd_name_status_list if exists]: delete_avd_command = CREATE_AVD_CMD_TEMPLATE.format(AVDMANAGER_BIN_DIR, avd_name) execute_shell(delete_avd_command, display_cmd=True, display_output=False) print("AVD contained in config are not present on machine anymore.")
  55. Creating fresh AVD Binary file location: <SDK_location>/tools/bin/avdmanager Command: $ avdmanager

    create avd —-name AVD_NAME —-abi AVD_ABI —-package AVD_PACKAGE —-sdcard AVD_SD_CARD —-force Useful optional parameters (for now): AVD_SD_CARD - SD card size in Mb e.g. 512M --force - if AVD exists it will be forcefully overwritten Best option. No need to do checks and deletes.
  56. Get available ports Purpose: - AVD are listed in ADB

    with ‘emulator-port’ name format. - When you create new AVD you want to be able to interact with it and monitor it’s behaviour. If you know port of AVD you have started then you know how to interact with it. Port is a reference to your AVD in ADB. - Google recommends to start AVD on port range 5554 - 5586 so you want to see if those ports are free.
  57. Get available ports Diagram: List of ports to check Loop

    is available store Check Port Availability No Yes Next item Number of needed ports continue List of available ports len of ‘List of available ports’ > ‘Number of needed ports’ exit() No Yes
  58. Check port availability Binary file location: Command: $ lsof -i:PORT

    Where: PORT - is four digit number (e.g. 5554) Built in system executable: /usr/sbin/lsof
  59. Check port availability Command line: $ lsof -i:5554 - AVD

    not running case on port 5554 $ lsof -i:5554 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME qemu-syst 46043 F1sherKK 45u IPv4 0x78c5e65ae41cd8ff 0t0 TCP localhost:sgi-esphttp (LISTEN) - AVD launched on port 5554 case
  60. Check port availability Command line: $ lsof -i:5554 - AVD

    not running case on port 5554 $ lsof -i:5554 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME qemu-syst 46043 F1sherKK 45u IPv4 0x78c5e65ae41cd8ff 0t0 TCP localhost:sgi-esphttp (LISTEN) - AVD launched on port 5554 case Seems like no output - but in reality it returns code 1 which means error! Error because it couldn’t find any resource on port 5554.
  61. Check port availability Command line: $ lsof -i:5555 COMMAND PID

    USER FD TYPE DEVICE SIZE/OFF NODE NAME qemu-syst 46043 F1sherKK 43u IPv4 0x78c5e65ae9a40e17 0t0 TCP localhost:personal-agent (LISTEN) qemu-syst 46043 F1sherKK 44u IPv6 0x78c5e65ae4824a5f 0t0 TCP localhost:personal-agent (LISTEN) qemu-syst 46043 F1sherKK 62u IPv4 0x78c5e65ae5543aef 0t0 TCP localhost:personal-agent-> localhost:57472 (ESTABLISHED) adb 46046 F1sherKK 9u IPv4 0x78c5e65af299f1f7 0t0 TCP localhost:57472-> localhost:personal-agent (ESTABLISHED) - Hint Every time you reserve port for AVD - additional one is taken by ADB. AVD has to be started only on ports with even number! Uneven ports are reserved for ADB interaction with AVD.
  62. Scripting - GetAvailablePortsAction.py from utils.CommandLineUtils import execute_shell GET_PORT_INFO_TEMPLATE = "lsof

    -i:{}" def execute(avd_number, ports_range=range(5554, 5588, 2)): print("\n----- GetAvailablePortsAction runs! -----") available_ports = list() for port in ports_range: get_port_info_cmd = GET_PORT_INFO_TEMPLATE.format(port) # Check port and save it if it's free try: execute_shell(get_port_info_cmd, display_cmd=True, display_output=True) except Exception: # No output means there was nothing found under port address available_ports.append(port) # Finish if port was found for every AVD if len(available_ports) == avd_number: break if len(available_ports) == avd_number: print("- Available ports found: {}".format(available_ports)) return available_ports else: raise Exception("Not enough ports to start {} AVD.".format(avd_number))
  63. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME Required parameters: AVD_NAME - unique alias under which all settings and properties of AVD will be connected to.
  64. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME -port AVD_PORT Optional but very helpful parameters: AVD_PORT - port on which AVD will run. Only way to have reference to created AVD from code. Takes 4 digit int value e.g. 5554.
  65. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME -port AVD_PORT -no-boot-anim Optional but very helpful parameters: AVD_PORT - port on which AVD will run. Only way to have reference to created AVD from code. Takes 4 digit int value e.g. 5554 -no-boot-anim - disables boot animation during emulator startup for faster booting
  66. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME -port AVD_PORT -no-boot-anim -gpu on Optional but very helpful parameters: AVD_PORT - port on which AVD will run. Only way to have reference to created AVD from code. Takes 4 digit int value e.g. 5554 -no-boot-anim - disables boot animation during emulator startup for faster booting -gpu on - makes use of machine GPU to render graphics - speed ups AVD performance
  67. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME -port AVD_PORT -no-boot-anim -gpu on -memory AVD_MEMORY Optional but very helpful parameters: AVD_PORT - port on which AVD will run. Only way to have reference to created AVD from code. Takes 4 digit int value e.g. 5554 -no-boot-anim - disables boot animation during emulator startup for faster booting -gpu on - makes use of machine GPU to render graphics - speed ups AVD performance AVD_MEMORY - possibility to extend or decrease memory usage by AVD e.g. -memory 2048
  68. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME -port AVD_PORT -no-boot-anim -gpu on -memory AVD_MEMORY -wipe-data Optional but very helpful parameters: -wipe-data - resets emulator memory -> it resets userdata-qemu.img (current memory state) file to userdata.img (initial memory state) state
  69. Starting AVD Binary file location: <SDK_location>/emulator/emulator Command: $ emulator -avd

    AVD_NAME -port AVD_PORT -no-boot-anim -gpu on -memory AVD_MEMORY -wipe-data -initdata AVD_MEMORY_IMAGE Optional but very helpful parameters: -wipe-data - resets emulator memory -> it resets userdata-qemu.img (current memory state) file to userdata.img (initial memory state) state -initdata - Allows to specify new path to memory image. File under path becomes new userdata.img. But userdata.img is used only on first AVD boot or after using -wipe-data. Therefore -initdata should be always used with -wipe-data. AVD_MEMORY_IMAGE - absolute path to AVD memory image
  70. Edit AVD state -> save it -> load it 1.

    You can start AVD, change any option you like e.g language, set VPN, disable animations, setup pin or fingerprint (if you need to test that) etc. 2. Turn off AVD and copy userdata-qemu to create reusable AVD memory state.
  71. Starting AVD Command line: $ emulator -avd Nexus_5X_API_26 Problems: 1.

    Process which starts take over command line until AVD turns off. 2. No output.
  72. Starting AVD Command line: $ emulator -avd Nexus_5X_API_26 & [1]

    22914 Problems: 1. Process which starts take over command line until AVD turns off. 2. No output. Add “&” to delegate process to different thread. We want delegate process to different thread because it will end when device is turned off. We need to avoid blocking our script main thread after starting AVD.
  73. Starting AVD $ emulator -avd Nexus_5X_API_26 -verbose & [1] 22914

    emulator:Android emulator version 26.0.3.0 (build_id 3965150) emulator:Found AVD name 'Nexus_5X_API_26' emulator:Found AVD target architecture: x86 emulator:argv[0]: 'emulator'; program directory: '/Users/F1sherKK/Library/Android/sdk/ tools' emulator: Found directory: /Users/F1sherKK/Library/Android/sdk/system-images/ android-26/google_apis/x86/ emulator:Probing for /Users/F1sherKK/Library/Android/sdk/system-images/android-26/ google_apis/x86//kernel-ranchu: file exists emulator:Auto-config: -engine qemu2 (based on configuration) emulator: Found directory: /Users/F1sherKK/Library/Android/sdk/system-images/ android-26/google_apis/x86/ emulator:try dir /Users/F1sherKK/Library/Android/sdk/tools emulator:try dir . (…) Problems: 1. Process which starts take over command line until AVD turns off. 2. No output. 3. Too much output. Command line: Add “-verbose” to receive output from AVD.
  74. Starting AVD $ emulator -avd Nexus_5X_API_26 -verbose &> /Users/F1sherKK/log.txt &

    [1] 24475 Problems: 1. Process which starts take over command line until AVD turns off. 2. No output. 3. Too much output. Command line: Add “&> AVD_LOG_PATH” to dump AVD output to file.
  75. Important: - We want to make sure that Start AVD

    is called after AVD has finished launching to prevent machine going OOM. Diagram: Loop Yes sleep(5) exit() Timeout per AVD current time - start time >= timeout No List with AVD data Start AVD Get AVD boot status launched Next item No continue Yes Starting AVD sequentially start time
  76. Getting AVD boot status Binary file location: Command: $ adb

    devices 1. Print currently visible devices and their statuses in ADB. <SDK_location>/platform-tools/adb 2. Get specific property from specific device $ adb -s ADB_DEVICE shell getprop DEVICE_PROPERTY
  77. AVD boot stages $ adb devices List of devices attached

    emulator-5554 device Status ‘device’ but animation is still running…
  78. AVD boot stages $ adb -s emulator-5554 shell getprop |

    grep -e "dev.bootcomplete" -e "sys.boot_completed" -e "init.svc.bootanim" [init.svc.bootanim]: [running] Parameters we can wait for: - dev.bootcomplete - sys.boot_completed - init.svc.bootanim
  79. AVD boot stages $ adb -s emulator-5554 shell getprop |

    grep -e "dev.bootcomplete" -e "sys.boot_completed" -e "init.svc.bootanim" [dev.bootcomplete]: [1] [init.svc.bootanim]: [stopped] [sys.boot_completed]: [1]
  80. Scripting - StartAvdAction.py import os import time from utils.CommandLineUtils import

    execute_shell # Path to executable SDK_DIR = os.environ["ANDROID_SDK_ROOT"] EMULATOR_BIN_DIR = os.path.join(SDK_DIR, "") + "emulator/emulator" ADB_BIN_DIR = os.path.join(SDK_DIR, "") + "platform-tools/adb" # Start AVD command assembly CREATE_AVD_CMD_BASE_TEMPLATE = '{} -avd "{}" -port "{}" -no-boot-anim -gpu on' MEMORY_IMAGE_CMD_TEMPLATE = '-wipe-data -initdata "{}"' DELEGATE_TO_OTHER_PROCESS_TEMPLATE = '&> "{}"' DELEGATE_TO_OTHER_THREAD = "&" # Get devices command assembly ADB_GET_DEVICES_CMD = "{} {}".format(ADB_BIN_DIR, "devices") # Get property command assembly ADB_SHELL_GET_PROPERTY = "{} -s {} shell getprop | grep {}" PROPERTY_PART = '-e "{}"' PROPERTIES = { "dev.bootcomplete": "1", "init.svc.bootanim": "stopped", "sys.boot_completed": "1" } (…)
  81. Scripting - StartAvdAction.py (…) def _get_visible_emulators(adb_devices_result): """Parses 'adb devices' output

    and returns list of emulators.""" visible_emulators = list() for line in adb_devices_result.splitlines(): if line.startswith("emulator"): emulator_name, emulator_status = tuple(line.split()) visible_emulators.append({"name": emulator_name, “status": emulator_status}) return visible_emulators (…)
  82. Scripting - StartAvdAction.py (…) def _wait_for_device_on_port(port, timeout=300): device_name = "emulator-{}".format(port)

    # Wait end conditions status adb_boot_complete = False param_boot_complete = False # Wait start start_time = time.time() while not all([adb_boot_complete, param_boot_complete]): # Check for timeout current_time = time.time() if current_time - start_time >= timeout: break # Adb Boot Complete - check if not adb_boot_complete: adb_devices_result = execute_shell(ADB_GET_DEVICES_CMD, display_cmd=True, display_output=True) visible_avds = _get_visible_emulators(adb_devices_result) if (device_name, "device") in [(avd["name"], avd["status"]) for avd in visible_avds]: adb_boot_complete = True if adb_boot_complete: # Assemble getprop command with all properties connected by grep get_property_params_part = " ".join([PROPERTY_PART.format(prop) for prop in PROPERTIES.keys()]) get_property_cmd = ADB_SHELL_GET_PROPERTY.format(ADB_BIN_DIR, device_name, get_property_params_part) # Property Boot Complete - check get_property_result = execute_shell(get_property_cmd, display_cmd=True, display_output=True) properties_with_status = get_property_result.splitlines() if all(("[{}]: [{}]".format(prop, status) in properties_with_status) for prop, status in PROPERTIES.items()): param_boot_complete = True # If device is not booted after checks then avoid ADB spamming if not all([adb_boot_complete, param_boot_complete]): time.sleep(5) (…)
  83. Scripting - StartAvdAction.py (…) def execute(config, available_ports): print("\n----- StartAVDActions runs!

    -----") for avd, port in zip(config["avd_list"], available_ports): start_avd_cmd_base = CREATE_AVD_CMD_BASE_TEMPLATE.format( EMULATOR_BIN_DIR, avd["name"], port) # Adding optional memory image avd_memory_image_cmd_part = "" if os.path.isfile(avd["avd_memory_image_dir"]): avd_memory_image_cmd_part = MEMORY_IMAGE_CMD_TEMPLATE.format( avd["avd_memory_image_dir"]) # Create path for AVD output avd_output = avd["avd_log_dir"] + avd["name"].replace(" ", "_") + ".txt" avd_output_cmd_part = DELEGATE_TO_OTHER_PROCESS_TEMPLATE.format(avd_output) # Assemble start_avd_cmd_assemble = " ".join([start_avd_cmd_base, avd_memory_image_cmd_part, avd_output_cmd_part, DELEGATE_TO_OTHER_THREAD]) # Start and wait for AVD execute_shell(start_avd_cmd_assemble, display_cmd=True, display_output=True) _wait_for_device_on_port(port)
  84. Scripting - Launcher.py from machine_clean_up_part import ( RestartAdbAction, KillVisibleAvdInstancesAction, WaitForAvdToTurnOffAction

    ) from avd_preparation_part import ( UpdatePackagesAction, CheckIfAvdExistsAction, DeleteExistingAvdAction, CreateAvdAction, ProvideAvdConfigAction, GetAvailablePortsAction, StartAvdAction ) LAUNCH_CONFIG = {"avd_list": []} if __name__ == "__main__": # Machine Clean Up part RestartAdbAction.execute() KillVisibleAvdInstancesAction.execute() WaitForAvdToTurnOffAction.execute() # AVD Preparation part UpdatePackagesAction.execute(LAUNCH_CONFIG) avd_name_exists_list = CheckIfAvdExistsAction.execute(LAUNCH_CONFIG) DeleteExistingAvdAction.execute(avd_name_exists_list) CreateAvdAction.execute(LAUNCH_CONFIG) ProvideAvdConfigAction.execute(LAUNCH_CONFIG) avd_num = len(LAUNCH_CONFIG["avd_list"]) available_ports = GetAvailablePortsAction.execute(avd_num) StartAvdAction.execute(LAUNCH_CONFIG, available_ports)
  85. Scripting - Example of launch config LAUNCH_CONFIG = { "avd_list":

    [ {"name": "GoogleApis_API26-0", "abi": "google_apis/x86", "package": "system-images;android-26;google_apis;x86", "skin_name": "nexus_5x", "device": "Nexus 5X", "screen_density": 420, "hard_drive_size": 1024, "sdcard": 512, "avd_log_dir": "./", "avd_memory_image_dir": ""}, {"name": "GoogleApis_API26-1", "abi": "google_apis/x86", "package": "system-images;android-26;google_apis;x86", "skin_name": "nexus_5x", "device": "Nexus 5X", "screen_density": 420, "hard_drive_size": 1024, "sdcard": 512, "avd_log_dir": "./", "avd_memory_image_dir": ""}, {"name": "GoogleApis_API26-2", "abi": "google_apis/x86", "package": "system-images;android-26;google_apis;x86", "skin_name": "nexus_5x", "device": "Nexus 5X", "screen_density": 420, "hard_drive_size": 1024, "sdcard": 512, "avd_log_dir": "./", "avd_memory_image_dir": ""}, ] }
  86. .apk preparation Part -Building app and test .apk files -Picking

    .apk files for test -Inspecting your .apk data -Retrieving instrumentation runner and app package from .apk -Checking if app is installed on device -Installing and uninstalling .apk on/from device Actions:
  87. Build app .apk and test .apk files Purpose: - Making

    sure you always have complete of both, up to date .apk files and with right versions. Diagram: Gradle task names Build .apk Next item Loop Clean
  88. Binary file location: <android_project_dir>/gradlew Command: $ gradlew clean GRADLE_BUILD_TASK GRADLE_BUILD_TASK

    Gradle tasks: clean - removes whole buildDir GRADLE_BUILD_TASK - task name depends on project, it builds specific flavour of android application Build app .apk and test .apk files
  89. Binary file location: <android_project_dir>/gradlew Command: $ gradlew clean GRADLE_BUILD_TASK GRADLE_BUILD_TASK

    Gradle tasks: clean - removes whole buildDir GRADLE_BUILD_TASK - task name depends on project, it builds specific flavour of android application Build app .apk and test .apk files If you have specific flavour name, build tasks will be camel cased result of: assemble + BUILD_TYPE_NAME + AndroidTest assembleStaging assembleStagingAndroidTest Example:
  90. Picking .apk files for tests Purpose: - If script won’t

    be able to pick .apk file by itself then it won’t be able to run without programmers help. Diagram: .apk name part Pick .apk dir where .apk is stored List with paths to app and test .apk
  91. Picking .apk files for tests …/app-debug-401.apk …/app-debug-402.apk …/app-debug-403.apk …/app-staging-402-androidTest.apk …/app-staging-403-androidTest.apk

    …/app-staging-401.apk …/app-staging-402.apk …/app-staging-403.apk staging Use name of your build_type to filter .apk files:
  92. Picking .apk files for tests …/app-debug-401.apk …/app-debug-402.apk …/app-debug-403.apk …/app-staging-402-androidTest.apk …/app-staging-403-androidTest.apk

    …/app-staging-401.apk …/app-staging-402.apk …/app-staging-403.apk And then inspect .apk files and find pair of two newest ones based on version code. 400002 400003 400001 400002 400003 Names might not contain versioning.
  93. Inspecting .apk file Purpose: - Get version code for .apk

    comparison. - Get instrumentation runner package for further test running. - Get app package and test package for checking if it’s currently on device. Diagram: .apk file path Dump Badging .apk package .apk file path Instrumentation runner package List contents of .zip archive Parse output version code Parse output
  94. Binary file location: <SDK_location>/<build-tools_folder>/aapt Inspecting .apk file aapt - Android

    Asset Packaging Tool Explanation: First obstacle: Script will need to handle build-tools version sorting
  95. Binary file location: <SDK_location>/<build-tools_folder>/aapt Inspecting .apk file aapt - Android

    Asset Packaging Tool Explanation: Command: $ aapt dump badging PATH_TO_APK Where: dump badging - prints the label and icon for the app declared in .apk PATH_TO_APK - path to .apk file, absolute or relative to your current directory
  96. Command line: Kamils-MacBook-Pro-2 F1sherKK$ aapt dump badging /Users/F1sherKK/MyProject/ app/build/outputs/apk/app-staging-403.apk package:

    name=‘com.myapp.debug1’ versionCode='400003' versionName='4.0.3' platformBuildVersionName='7.1.1' sdkVersion:'18' targetSdkVersion:'25' uses-permission: name='android.permission.SET_ANIMATION_SCALE' uses-permission: name='android.permission.DISABLE_KEYGUARD' uses-permission: name='android.permission.WRITE_CONTACTS' uses-permission: name='android.permission.READ_EXTERNAL_STORAGE' uses-permission: name='android.permission.VIBRATE' uses-permission: name='android.permission.INTERNET' uses-permission: name='android.permission.ACCESS_NETWORK_STATE' uses-permission: name='android.permission.WRITE_SYNC_SETTINGS' uses-permission: name='android.permission.READ_SYNC_SETTINGS' uses-permission: name='android.permission.READ_SYNC_STATS' uses-permission: name='android.permission.WAKE_LOCK' uses-permission: name='com.google.android.c2dm.permission.RECEIVE' uses-permission: name='android.permission.MANAGE_ACCOUNTS' uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' uses-permission: name='android.permission.READ_CONTACTS' uses-permission: name='android.permission.CAMERA' uses-permission: name='android.permission.GET_ACCOUNTS' (…) Inspecting .apk file
  97. Command line: Kamils-MacBook-Pro-2 F1sherKK$ aapt dump badging /Users/F1sherKK/MyProject/ app/build/outputs/apk/app-staging-403.apk package:

    name=‘com.myapp.debug1’ versionCode='400003' versionName='4.0.3' platformBuildVersionName='7.1.1' sdkVersion:'18' targetSdkVersion:'25' uses-permission: name='android.permission.SET_ANIMATION_SCALE' uses-permission: name='android.permission.DISABLE_KEYGUARD' uses-permission: name='android.permission.WRITE_CONTACTS' uses-permission: name='android.permission.READ_EXTERNAL_STORAGE' uses-permission: name='android.permission.VIBRATE' uses-permission: name='android.permission.INTERNET' uses-permission: name='android.permission.ACCESS_NETWORK_STATE' uses-permission: name='android.permission.WRITE_SYNC_SETTINGS' uses-permission: name='android.permission.READ_SYNC_SETTINGS' uses-permission: name='android.permission.READ_SYNC_STATS' uses-permission: name='android.permission.WAKE_LOCK' uses-permission: name='com.google.android.c2dm.permission.RECEIVE' uses-permission: name='android.permission.MANAGE_ACCOUNTS' uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' uses-permission: name='android.permission.READ_CONTACTS' uses-permission: name='android.permission.CAMERA' uses-permission: name='android.permission.GET_ACCOUNTS' (…) Inspecting .apk file Package and version code to be found in first line.
  98. Binary file location: <SDK_location>/<build-tools_folder>/aapt Inspecting .apk file aapt - Android

    Asset Packaging Tool Explanation: Command: $ aapt l -a PATH_TO_APK Where: l -a - list contents of Zip-compatible archive PATH_TO_APK - path to .apk file, absolute or relative to your current directory
  99. Command line: $ aapt l -a /Users/F1sherKK/MyProject/app/build/outputs/apk/androidTest/staging/app- staging-403-androidTest.apk (…) Android

    manifest: N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) A: package=“com.myapp.debug1.test" (Raw: "com.myapp.debug1.test") A: platformBuildVersionCode=(type 0x10)0x19 (Raw: "25") A: platformBuildVersionName="7.1.1" (Raw: "7.1.1") E: uses-sdk (line=5) A: android:minSdkVersion(0x0101020c)=(type 0x10)0x15 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x19 E: instrumentation (line=9) A: android:label(0x01010001)="Tests for com.myapp.debug1" (Raw: "Tests for com.myapp.debug1”) A: android:name(0x01010003)="com.myapp.instrumentation.config.MyAppTestRunner" (Raw: "com.myapp.instrumentation.config.MyAppTestRunner") A: android:targetPackage(0x01010021)="com.myapp.debug1" (Raw: "com.myapp.debug1") A: android:handleProfiling(0x01010022)=(type 0x12)0x0 A: android:functionalTest(0x01010023)=(type 0x12)0x0 E: application (line=16) A: android:label(0x01010001)="Intents" (Raw: "Intents") A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff E: uses-library (line=19) A: android:name(0x01010003)="android.test.runner" (Raw: "android.test.runner") Inspecting .apk file
  100. Command line: $ aapt l -a /Users/F1sherKK/MyProject/app/build/outputs/apk/androidTest/staging/app- staging-403-androidTest.apk (…) Android

    manifest: N: android=http://schemas.android.com/apk/res/android E: manifest (line=2) A: package=“com.myapp.debug1.test" (Raw: "com.myapp.debug1.test") A: platformBuildVersionCode=(type 0x10)0x19 (Raw: "25") A: platformBuildVersionName="7.1.1" (Raw: "7.1.1") E: uses-sdk (line=5) A: android:minSdkVersion(0x0101020c)=(type 0x10)0x15 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x19 E: instrumentation (line=9) A: android:label(0x01010001)="Tests for com.myapp.debug1" (Raw: "Tests for com.myapp.debug1”) A: android:name(0x01010003)="com.myapp.instrumentation.config.MyAppTestRunner" (Raw: "com.myapp.instrumentation.config.MyAppTestRunner") A: android:targetPackage(0x01010021)="com.myapp.debug1" (Raw: "com.myapp.debug1") A: android:handleProfiling(0x01010022)=(type 0x12)0x0 A: android:functionalTest(0x01010023)=(type 0x12)0x0 E: application (line=16) A: android:label(0x01010001)="Intents" (Raw: "Intents") A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff E: uses-library (line=19) A: android:name(0x01010003)="android.test.runner" (Raw: "android.test.runner") Inspecting .apk file in project that I have checked, there was 15000+ lines We can extract Instrumentation Runner package.
  101. Checking if app is installed on device Purpose: - If

    app package is currently on device then we can remove it before next install attempt to avoid: Diagram: Installation error: INSTALL_FAILED_VERSION_DOWNGRADE Get Packages Installed On Device Next item Loop app and test packages is in list of device packages continue Device Packages List No Yes Uninstall Package
  102. Checking if app is installed on device Binary file location:

    <SDK_location>/platform-tools/adb Command: $ adb -s AVD_NAME_IN_ADB shell pm list packages Where: AVD_NAME_IN_ADB - serial name of AVD which is visible after in ADB e.g after calling `adb devices` command shell - interaction with shell module of adb pm - interaction with adb shell module named package manager list packages - listing all installed packages on device
  103. Command line: $ adb -s emulator-5554 shell pm list packages

    (…) package:com.android.smoketest package:com.android.cts.priv.ctsshim package:com.google.android.youtube package:com.google.android.ext.services package:com.example.android.livecubes package:com.myapp.debug1 package:com.android.providers.telephony package:com.google.android.googlequicksearchbox package:com.android.providers.calendar package:com.android.providers.media package:com.android.protips package:com.android.documentsui package:com.android.externalstorage package:com.android.htmlviewer package:com.android.companiondevicemanager package:com.android.mms.service package:com.android.providers.downloads package:com.google.android.apps.messaging package:com.google.android.configupdater package:com.android.defcontainer package:com.myapp.debug1.test package:com.android.providers.downloads.ui package:com.android.vending (…) Inspecting .apk file
  104. Command line: $ adb -s emulator-5554 shell pm list packages

    (…) package:com.android.smoketest package:com.android.cts.priv.ctsshim package:com.google.android.youtube package:com.google.android.ext.services package:com.example.android.livecubes package:com.myapp.debug1 package:com.android.providers.telephony package:com.google.android.googlequicksearchbox package:com.android.providers.calendar package:com.android.providers.media package:com.android.protips package:com.android.documentsui package:com.android.externalstorage package:com.android.htmlviewer package:com.android.companiondevicemanager package:com.android.mms.service package:com.android.providers.downloads package:com.google.android.apps.messaging package:com.google.android.configupdater package:com.android.defcontainer package:com.myapp.debug1.test package:com.android.providers.downloads.ui package:com.android.vending (…) Inspecting .apk file
  105. Uninstalling package Binary file location: <SDK_location>/platform-tools/adb Command: $ adb -s

    AVD_NAME_IN_ADB shell pm uninstall APP_PACKAGE Where: AVD_NAME_IN_ADB - serial name of AVD which is visible after in ADB e.g after calling `adb devices` command shell - interaction with shell module of adb pm - interaction with adb shell module named package manager APP_PACKAGE - package of Android application or Android application under test
  106. Uninstalling package Command line: $ adb -s emulator-5554 shell pm

    uninstall com.myapp.debug1 Success Uninstalling package happens almost instantly.
  107. Installing .apk Binary file location: <SDK_location>/platform-tools/adb Command: $ adb -s

    AVD_NAME_IN_ADB install APK_FILE_PATH Where: AVD_NAME_IN_ADB - serial name of AVD which is visible after in ADB e.g after calling `adb devices` command APK_FILE_PATH - path to .apk file that should be installed
  108. Installing .apk Command line: $ adb -s emulator-5554 install /Users/F1sherKK/MyApp/build/outputs/

    apk/staging/app-staging-403.apk Success Takes more time - depending on .apk size even up to 20 seconds. You will need to install two .apk files - app and test. Command line: $ adb -s emulator-5554 install /Users/F1sherKK/MyApp/build/outputs/ apk/staging/app-staging-403.apk | adb -s emulator-5554 install /Users/ F1sherKK/MyApp/build/outputs/apk/androidTest/staging/app-staging-403- androidTest.apk Success Two .apk installs single process.
  109. Installing .apk Binary file location: <SDK_location>/platform-tools/adb Command: $ adb -s

    AVD_NAME_IN_ADB install -r APK_FILE_PATH Where: AVD_NAME_IN_ADB - serial name of AVD which is visible after in ADB e.g after calling `adb devices` command APK_FILE_PATH - path to .apk file that should be installed -r - replace application if exists Best option. No need to do checks and deletes.
  110. Diagram: test/app .apk filepaths Installing .apk 1st Device Thread 2nd

    Device Thread Install .apk n-th Device Thread join Install .apk Install .apk avd ports
  111. Multithreading vs ADB 1st Device Thread 2nd Device Thread ADB

    call n-th Device Thread ADB call ADB call ADB Server
  112. Multithreading vs ADB 1st Device Thread 2nd Device Thread ADB

    call n-th Device Thread ADB call ADB call ADB Server error: protocol fault (couldn't read status): Connection reset by peer
  113. Multithreading vs ADB 1st Device Thread 2nd Device Thread ADB

    call n-th Device Thread ADB call ADB call ADB Server ADB call ADB call ADB call Call queue 2.5 - 5 sec interval
  114. Running Tests Binary file location: <SDK_location>/platform-tools/adb Command: $ adb -s

    AVD_NAME_IN_ADB shell am instrument -e package JAVA_PACKAGE TEST_APPLICATION_PACKAGE/INSTRUMENTATION_RUNNER_PACKAGE Where: AVD_NAME_IN_ADB - serial name of AVD which is visible after in ADB e.g after calling `adb devices` command shell - interaction with shell module of adb am - interaction with adb shell module named activity manager instrument - interaction with adb shell am module responsible for instrumentation process in which tests run Running tests by java package will allow you to test parts of your app rather than whole flavour. If you have long tests it will allow you to avoid memory problems.
  115. Running Tests JAVA_PACKAGE - package in which .java files containing

    code annotated with @Test are stored package com.example.labs.azimo.note.tests.function.login; import … public class Login_Function_Tests { @Rule public ActivityTestRule<LoginActivity> activityRule = new ActivityTestRule<>(LoginActivity.class, true, false); @Before public void before() {…} @After public void after() {…} @Test public void testLogin_insertWrongEmail_shouldDisplayError() {…} @Test public void testLogin_whenEmailFieldIsEmpty_shouldDisplayError() {…} @Test public void testLogin_whenPasswordFieldIsEmpty_shouldDisplayError() {…} @Test public void testLogin_whenSwitchingFromLoginToRegister_shouldKeepData() {…} all login tests tests below belongs to this package
  116. Running Tests Binary file location: <SDK_location>/platform-tools/adb Command: $ adb -s

    AVD_NAME_IN_ADB shell am instrument -e package JAVA_PACKAGE TEST_APPLICATION_PACKAGE/INSTRUMENTATION_RUNNER_PACKAGE Where: TEST_APPLICATION_PACKAGE - package of your application with ‘.test’ added at the end INSTRUMENTATION_RUNNER_PACKAGE - java package to Instrumentation Runner class which should be used during test session. Running tests by java package will allow you to test parts of your app rather than whole flavour. If you have long tests it will allow you to avoid memory problems. Remember that both values can be automatically scrapped with usage of aapt binary. It will decrease complexity of your input config to script.
  117. Command line: $ adb -s emulator-5554 shell am instrument -e

    package com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.test/ com.example.labs.azimo.note.config.AzimoTestRunner Running Tests Problems: 1. Script cannot tell when tests has finished. 2. No info about which tests have passed and which have failed.
  118. Running Tests Binary file location: <SDK_location>/platform-tools/adb Command: $ adb -s

    AVD_NAME_IN_ADB shell am instrument -w -r -e package JAVA_PACKAGE TEST_APPLICATION_PACKAGE/INSTRUMENTATION_RUNNER_PACKAGE Where: -w - instead of starting process in different thread, current thread will be blocked and you will receive basic output -r - turns basic output into raw output so there is more information about tests and their status Running tests by java package will allow you to test parts of your app rather than whole flavour. If you have long tests it will allow you to avoid memory problems.
  119. Command line: $ adb —s emulator-5554 shell am instrument -w

    -r -e package com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.test/ com.example.labs.azimo.note.config.AzimoTestRunner INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream= com.example.labs.azimo.note.tests.function.login.Login_Function_Tests: INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenEmailFieldIsEmpty_shouldDisplayError (…) Running Tests Problems: 1. Script cannot tell when tests has finished. 2. No info about which tests have passed and which have failed. Using command line tests start instantly!
  120. Command line: $ adb —s emulator-5554 shell am instrument -w

    -r -e package com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.test/ com.example.labs.azimo.note.config.AzimoTestRunner Running Tests Test Case Output Test Case Output Test Case Output … Session Result Code + Time
  121. What we can get from passed test case output Test

    case output INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 Displays overall number of tests in package: INSTRUMENTATION_STATUS: numtests=4
  122. What we can get from passed test case output Test

    case output INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 Displays test case name: INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError
  123. What we can get from passed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 Displays whole test package: INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests Test case output
  124. What we can get from passed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 Index of current test (can be used to determine how much tests are left): INSTRUMENTATION_STATUS: current=1 Test case output
  125. What we can get from passed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 STATUS CODE with value 1 means that test has started: INSTRUMENTATION_STATUS_CODE: 1 Test case output
  126. What we can get from passed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 Repeats itself once: INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 Test case output
  127. What we can get from passed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 STATUS CODE with value 0 means that test has PASSED: INSTRUMENTATION_STATUS_CODE: 0 Test case output
  128. What we can get from failed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= com.example.labs.azimo.note.tests.function.login.Login_Function_Tests: INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream= Error in testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(com.example.labs.azimo.note.tests.function.login.Login_Function_Tests): org.mockito.exceptions.misusing.InvalidUseOfMatchersException: No matchers found for additional matcher Not(?) -> at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at java.lang.reflect.Method.invoke(Native Method) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80) at android.support.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61) at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:433) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at org.junit.runner.JUnitCore.run(JUnitCore.java:115) at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58) at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:369) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2074) INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: stack= org.mockito.exceptions.misusing.InvalidUseOfMatchersException: No matchers found for additional matcher Not(?) -> at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at java.lang.reflect.Method.invoke(Native Method) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80) at android.support.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61) at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:433) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at org.junit.runner.JUnitCore.run(JUnitCore.java:115) at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58) at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:369) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2074) INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: -2 Full error stack trace Full error stack trace Test case output
  129. What we can get from failed test case output INSTRUMENTATION_STATUS:

    numtests=4 INSTRUMENTATION_STATUS: stream= com.example.labs.azimo.note.tests.function.login.Login_Function_Tests: INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=4 INSTRUMENTATION_STATUS: stream= Error in testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(com.example.labs.azimo.note.tests.function.login.Login_Function_Tests): org.mockito.exceptions.misusing.InvalidUseOfMatchersException: No matchers found for additional matcher Not(?) -> at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at java.lang.reflect.Method.invoke(Native Method) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80) at android.support.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61) at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:433) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at org.junit.runner.JUnitCore.run(JUnitCore.java:115) at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58) at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:369) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2074) INSTRUMENTATION_STATUS: id=AndroidJUnitRunner INSTRUMENTATION_STATUS: test=testLogin_whenPasswordFieldIsEmpty_shouldDisplayError INSTRUMENTATION_STATUS: class=com.example.labs.azimo.note.tests.function.login.Login_Function_Tests INSTRUMENTATION_STATUS: stack= org.mockito.exceptions.misusing.InvalidUseOfMatchersException: No matchers found for additional matcher Not(?) -> at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at com.example.labs.azimo.note.tests.function.login.Login_Function_Tests.testLogin_whenPasswordFieldIsEmpty_shouldDisplayError(Login_Function_Tests.java:96) at java.lang.reflect.Method.invoke(Native Method) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80) at android.support.test.internal.runner.junit4.statement.RunAfters.evaluate(RunAfters.java:61) at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:433) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runners.Suite.runChild(Suite.java:128) at org.junit.runners.Suite.runChild(Suite.java:27) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at org.junit.runner.JUnitCore.run(JUnitCore.java:115) at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:58) at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:369) at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2074) INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: -2 status code -2 means test has failed Test case output
  130. Gather test packages in groups as you wish: Running Tests

    com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.tests.function.register com.example.labs.azimo.note.tests.function.welcome All_Function_Tests com.example.labs.azimo.note.tests.endtoend.dispatcher com.example.labs.azimo.note.tests.endtoend.login com.example.labs.azimo.note.tests.endtoend.logout com.example.labs.azimo.note.tests.endtoend.notes com.example.labs.azimo.note.tests.endtoend.welcome All_EndToEnd_Tests All_Login_Tests com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.tests.endtoend.login
  131. Prepare test run command from each java package: Running Tests

    com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.tests.function.register com.example.labs.azimo.note.tests.function.welcome All_Function_Tests all_function_tests_package = [ "com.example.labs.azimo.note.tests.function.login" "com.example.labs.azimo.note.tests.function.register" "com.example.labs.azimo.note.tests.function.welcome" ] def create_package_launch_commands(packages): launch_commands = list() for java_package in packages: cmd_template = " ".join([ADB_BIN_PATH, "-s emulator-{}", "shell am instrument -w -r -e package”, java_package, APP_TEST_PACKAGE, "/", INSTRUMENTATION_RUNNER_PACKAGE]) launch_commands.append(cmd_template) return launch_commands Example: Place where you can insert AVD port
  132. Package Launch Cmd … num of device working threads =

    num of devices No n-th Device Thread Run Test Package Test Results Loop Next Line Parse Line n-th Logging Thread Write to .json Yes Thread References List Start Test Thread for next package Next item Check Test Threads Statues Loop is there idle thread with unsaved logs No Yes Start Test Logging Thread join Packages are distributed between all devices. Devices List Package Launch Cmd Package Launch Cmd
  133. Test Split Problem - when each device takes different package

    All_Login_Tests com.example.labs.azimo.note.tests.function.login Test Case - 10 seconds Test Case - 10 seconds Test Case - 10 seconds com.example.labs.azimo.note.tests.endtoend.login Test Case - 1 minute Test Case - 2 minute Test Case - 1 minute Test Case - 2 minute Test Case - 1 minute Test Case - 2 minute Device 1 - received package function.login Device 2 - received package endtoend.login Execution time took 30 seconds. Execution time took 9 minutes. Device 1 had to wait for Device 2 - 8 minutes 30 seconds while it could take half of tests.
  134. Google - Test Sharding com.example.labs.azimo.note.tests.endtoend.login Test Case - 1 minute

    Test Case - 2 minute Test Case - 1 minute Test Case - 2 minute Test Case - 1 minute Test Case - 2 minute No shards: Shards Number - 3 What do you expect to happen: Test Case - 1 minute Test Case - 2 minute Device 1: endtoend.login shard 1 of 3 Test Case - 1 minute Test Case - 2 minute Device 2: endtoend.login shard 2 of 3 Test Case - 1 minute Test Case - 2 minute Device 3: endtoend.login shard 3 of 3 Execution time 3 min. Execution time 3 min. Execution time 3 min.
  135. Shards Number - 3 What really happens (or can happen

    in different variation): Device 1: endtoend.login shard 1 of 3 Test Case - 1 minute Test Case - 2 minute Device 2: endtoend.login shard 2 of 3 Test Case - 1 minute Test Case - 1 minute Test Case - 2 minute Device 3: endtoend.login shard 3 of 3 Test Case - 2 minute Execution time 0 min. Execution time 3 min. Execution time 6 min. 6 min wasted. 3 min wasted. Google - Test Sharding com.example.labs.azimo.note.tests.endtoend.login Test Case - 1 minute Test Case - 2 minute Test Case - 1 minute Test Case - 2 minute Test Case - 1 minute Test Case - 2 minute No shards:
  136. Running Tests With Shards Binary file location: <SDK_location>/platform-tools/adb Command: $

    adb -s AVD_NAME_IN_ADB shell am instrument -w -r -e package JAVA_PACKAGE -e numShards NUM_SHARDS -e shardIndex SHARD_INDEX TEST_APPLICATION_PACKAGE/INSTRUMENTATION_RUNNER_PACKAGE Where: NUM_SHARDS - number of parts to which inserted package should be divided SHARD_INDEX - index of package part
  137. Prepare test run command from each java package: Running Tests

    with com.example.labs.azimo.note.tests.function.login com.example.labs.azimo.note.tests.function.register com.example.labs.azimo.note.tests.function.welcome All_Function_Tests all_function_tests_package = [ "com.example.labs.azimo.note.tests.function.login" "com.example.labs.azimo.note.tests.function.register" "com.example.labs.azimo.note.tests.function.welcome" ] NUM_SHARDS = NUM_OF_DEVICES def create_package_launch_commands(packages): launch_commands = list() for shard_index in range(NUM_SHARDS): for java_package in packages: cmd_template = " ".join([ADB_BIN_PATH, "-s emulator-{}", "shell am instrument -w -r -e package”, java_package, APP_TEST_PACKAGE, "/", INSTRUMENTATION_RUNNER_PACKAGE, "-e numShards", NUM_SHARDS, "-e shardIndex", shard_index]) launch_commands.append(cmd_template) return launch_commands Example: There are NUM_SHARDS * PACKAGES_NUM commands to run. The larger granularity the less time is lost.
  138. Instead of using list of packages - prepare test run

    command from each java package: Running Tests with com.example.labs.azimo.note.tests.function.login adb —s emulator-{} shell am instrument -w -r -e package com.example.labs.azimo.note.tests.function.login -e numShards 3 -e shardIndex 0 com.example.labs.azimo.note.test/com.example.labs.azimo.note.config.AzimoTestRunner Emulator port will be added in test thread that will use command adb —s emulator-{} shell am instrument -w -r -e package com.example.labs.azimo.note.tests.function.login -e numShards 3 -e shardIndex 1 com.example.labs.azimo.note.test/com.example.labs.azimo.note.config.AzimoTestRunner adb —s emulator-{} shell am instrument -w -r -e package com.example.labs.azimo.note.tests.function.login -e numShards 3 -e shardIndex 2 com.example.labs.azimo.note.test/com.example.labs.azimo.note.config.AzimoTestRunner
  139. … num of device working threads = num of devices

    No n-th Device Thread Run Test Package Test Results Loop Next Line Parse Line n-th Logging Thread Write to .json Yes Thread References List Start Test Thread for next package Next item Check Test Threads Statues Loop is there idle thread with unsaved logs No Yes Start Test Logging Thread join With this structure the more shards you use the less idle devices you have. Each device receives new launch command instantly after process created with previous one has ended. Devices List Package Launch Cmd … Package Launch Cmd Package Launch Cmd
  140. Introducing AutomationTestSupervisor https://github.com/AzimoLabs/AutomationTestSupervisor Our own Python tool for launching automation

    tests with ability to: • maintaining AVD instances according to provided .json or working with currently connected devices • building and installing .apk files • specifying test sets according to your liking • speeding up test run (when using packages) by making each device independent • generating HTML dashboard with output, device LogCats and more https://github.com/AzimoLabs/AzimoNote Example project: Tool: