1import argparse 2import sys 3import os 4import time 5import datetime 6 7import requests # - for uploading files 8import boto3 9 10parser = argparse.ArgumentParser(description="Utility script to upload and run Android Device tests on AWS Device Farm for CI") 11parser.add_argument('--run_id', required=True, help="A unique number for each workflow run within a repository") 12parser.add_argument('--run_attempt', required=True, help="A unique number for each attempt of a particular workflow run in a repository") 13parser.add_argument('--project_arn', required=True, help="Arn for the Device Farm Project the apk will be tested on") 14parser.add_argument('--device_pool_arn', required=True, help="Arn for device pool of the Device Farm Project the apk will be tested on") 15 16current_working_directory = os.getcwd() 17build_file_location = current_working_directory + '/src/test/android/testapp/build/outputs/apk/debug/testapp-debug.apk' 18test_file_location = current_working_directory + '/src/test/android/testapp/build/outputs/apk/androidTest/debug/testapp-debug-androidTest.apk' 19test_spec_file_location = current_working_directory + '/src/test/android/testapp/instrumentedTestSpec.yml' 20 21def main(): 22 args = parser.parse_args() 23 run_id = args.run_id 24 run_attempt = args.run_attempt 25 project_arn = args.project_arn 26 device_pool_arn = args.device_pool_arn 27 28 region = os.getenv('AWS_DEVICE_FARM_REGION') 29 30 print("Beginning Android Device Farm Setup \n") 31 32 # Create Boto3 client for Device Farm 33 try: 34 client = boto3.client('devicefarm', region_name=region) 35 except Exception: 36 print("Error - could not make Boto3 client. Credentials likely could not be sourced") 37 sys.exit(-1) 38 print("Boto3 client established") 39 40 # Upload the crt library shell app to Device Farm 41 upload_file_name = 'CI-' + run_id + '-' + run_attempt + '.apk' 42 print('Upload file name: ' + upload_file_name) 43 44 # Prepare upload to Device Farm project 45 create_upload_response = client.create_upload( 46 projectArn=project_arn, 47 name=upload_file_name, 48 type='ANDROID_APP' 49 ) 50 device_farm_upload_arn = create_upload_response['upload']['arn'] 51 device_farm_upload_url = create_upload_response['upload']['url'] 52 53 # Upload crt library shell app apk 54 with open(build_file_location, 'rb') as f: 55 data = f.read() 56 r = requests.put(device_farm_upload_url, data=data) 57 print('File upload status code: ' + str(r.status_code) + ' reason: ' + r.reason) 58 device_farm_upload_status = client.get_upload(arn=device_farm_upload_arn) 59 while device_farm_upload_status['upload']['status'] != 'SUCCEEDED': 60 if device_farm_upload_status['upload']['status'] == 'FAILED': 61 print('Upload failed to process') 62 sys.exit(-1) 63 time.sleep(1) 64 device_farm_upload_status = client.get_upload(arn=device_farm_upload_arn) 65 66 # Upload the instrumentation test package to Device Farm 67 upload_test_file_name = 'CI-' + run_id + '-' + run_attempt + 'tests.apk' 68 print('Upload file name: ' + upload_test_file_name) 69 70 # Prepare upload to Device Farm project 71 create_upload_response = client.create_upload( 72 projectArn=project_arn, 73 name=upload_test_file_name, 74 type='INSTRUMENTATION_TEST_PACKAGE' 75 ) 76 device_farm_instrumentation_upload_arn = create_upload_response['upload']['arn'] 77 device_farm_instrumentation_upload_url = create_upload_response['upload']['url'] 78 79 # Upload instrumentation test package 80 with open(test_file_location, 'rb') as f: 81 data_instrumentation = f.read() 82 r_instrumentation = requests.put(device_farm_instrumentation_upload_url, data=data_instrumentation) 83 print('File upload status code: ' + str(r_instrumentation.status_code) + ' reason: ' + r_instrumentation.reason) 84 device_farm_upload_status = client.get_upload(arn=device_farm_instrumentation_upload_arn) 85 while device_farm_upload_status['upload']['status'] != 'SUCCEEDED': 86 if device_farm_upload_status['upload']['status'] == 'FAILED': 87 print('Upload failed to process') 88 sys.exit(-1) 89 time.sleep(1) 90 device_farm_upload_status = client.get_upload(arn=device_farm_instrumentation_upload_arn) 91 92 # Upload the test spec file to Device Farm 93 upload_spec_file_name = 'CI-' + run_id + '-' + run_attempt + 'test-spec.yml' 94 print('Upload file name: ' + upload_spec_file_name) 95 96 # Prepare upload to Device Farm project 97 create_upload_response = client.create_upload( 98 projectArn=project_arn, 99 name=upload_spec_file_name, 100 type='INSTRUMENTATION_TEST_SPEC' 101 ) 102 device_farm_test_spec_upload_arn = create_upload_response['upload']['arn'] 103 device_farm_test_spec_upload_url = create_upload_response['upload']['url'] 104 105 # Default Instrumentation tests run on Device Farm result in detailed individual test breakdowns but comes 106 # at the cost of the test suite running for up to two hours before completing. There is limited control for turning 107 # off unnecessary features which generates an immense amount of traffic resulting in hitting Device Farm rate limits 108 # A bare-bones test spec is used with instrumentation testing which will report a singular fail if any one test fails but 109 # the resulting Test spec output file contains information on each unit test, whether they passed, failed, or were skipped. 110 # Upload test spec yml 111 with open(test_spec_file_location, 'rb') as f: 112 data = f.read() 113 r = requests.put(device_farm_test_spec_upload_url, data=data) 114 print('File upload status code: ' + str(r.status_code) + ' reason: ' + r.reason) 115 device_farm_upload_status = client.get_upload(arn=device_farm_test_spec_upload_arn) 116 while device_farm_upload_status['upload']['status'] != 'SUCCEEDED': 117 if device_farm_upload_status['upload']['status'] == 'FAILED': 118 print('Upload failed to process') 119 sys.exit(-1) 120 time.sleep(1) 121 device_farm_upload_status = client.get_upload(arn=device_farm_test_spec_upload_arn) 122 123 print('scheduling run') 124 schedule_run_response = client.schedule_run( 125 projectArn=project_arn, 126 appArn=device_farm_upload_arn, 127 devicePoolArn=device_pool_arn, 128 name=upload_file_name, 129 test={ 130 'type': 'INSTRUMENTATION', 131 'testPackageArn': device_farm_instrumentation_upload_arn, 132 'testSpecArn': device_farm_test_spec_upload_arn 133 }, 134 executionConfiguration={ 135 'jobTimeoutMinutes': 30 136 } 137 ) 138 139 device_farm_run_arn = schedule_run_response['run']['arn'] 140 141 run_start_time = schedule_run_response['run']['started'] 142 run_start_date_time = run_start_time.strftime("%m/%d/%Y, %H:%M:%S") 143 print('run scheduled at ' + run_start_date_time) 144 145 get_run_response = client.get_run(arn=device_farm_run_arn) 146 while get_run_response['run']['result'] == 'PENDING': 147 time.sleep(10) 148 get_run_response = client.get_run(arn=device_farm_run_arn) 149 150 run_end_time = datetime.datetime.now() 151 run_end_date_time = run_end_time.strftime("%m/%d/%Y, %H:%M:%S") 152 print('Run ended at ' + run_end_date_time + ' with result: ' + get_run_response['run']['result']) 153 154 is_success = True 155 if get_run_response['run']['result'] != 'PASSED': 156 print('run has failed with result ' + get_run_response['run']['result']) 157 is_success = False 158 159 # If Clean up is not executed due to the job being cancelled in CI, the uploaded files will not be deleted 160 # from the Device Farm project and must be deleted manually. 161 162 # Clean up 163 print('Deleting ' + upload_file_name + ' from Device Farm project') 164 client.delete_upload( 165 arn=device_farm_upload_arn 166 ) 167 print('Deleting ' + upload_test_file_name + ' from Device Farm project') 168 client.delete_upload( 169 arn=device_farm_instrumentation_upload_arn 170 ) 171 print('Deleting ' + upload_spec_file_name + ' from Device Farm project') 172 client.delete_upload( 173 arn=device_farm_test_spec_upload_arn 174 ) 175 176 if is_success == False: 177 print('Exiting with fail') 178 sys.exit(-1) 179 180 print('Exiting with success') 181 182if __name__ == "__main__": 183 main()