From 143b01101fc52238e87bcdb210d68d3325c40def Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Thu, 17 May 2018 13:45:59 -0700 Subject: [PATCH 01/10] Python 3 support --- testdroid/__init__.py | 106 +++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index b7cd034..36cd828 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -1,6 +1,12 @@ # -*- coding: utf-8 -*- -import os, sys, requests, json, logging, time, httplib, base64 +import os, sys, requests, json, logging, time, base64 + +if sys.version_info[0] > 2: + import http.client +else: + import httplib + from optparse import OptionParser from datetime import datetime @@ -62,7 +68,7 @@ def update(self, pos, total): all_full = self.width - 2 num_hashes = int(round((percent_done / 100.0) * all_full)) self.prog_bar = ' [' + self.fill_char * num_hashes + ' ' * (all_full - num_hashes) + ']' - pct_place = (len(self.prog_bar) / 2) - len(str(percent_done)) + pct_place = (len(self.prog_bar) // 2) - len(str(percent_done)) pct_string = '%d%%' % percent_done self.duration = int(round(time.time()-self.started)) self.eta = int(round( self.duration / (percent_done / 100.0)))-self.duration if percent_done > 5 else 'N/A' @@ -74,9 +80,9 @@ def update(self, pos, total): else: self.prog_bar += ' ' if sys.platform.lower().startswith('win'): - print self, '\r', + print(self +'\r') else: - print self, chr(27) + '[A' + print(str(self) + chr(27) + '[A') def __str__(self): return str(self.prog_bar) @@ -140,7 +146,7 @@ def get_token(self): data = payload, headers = { "Accept": "application/json" } ) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) reply = res.json() @@ -160,8 +166,8 @@ def get_token(self): data = payload, headers = { "Accept": "application/json" } ) - if res.status_code not in range(200, 300): - print "FAILED: Unable to get a new access token using refresh token" + if res.status_code not in list(range(200, 300)): + print("FAILED: Unable to get a new access token using refresh token") self.access_token = None return self.get_token() @@ -177,7 +183,9 @@ def get_token(self): """ def _build_headers(self): if self.api_key: - return {'Authorization' : 'Basic %s' % base64.b64encode(self.api_key+":"), 'Accept' : 'application/json' } + apikey = {'Authorization' : 'Basic %s' % base64.b64encode((self.api_key+":").encode(encoding='utf_8')).decode(), 'Accept' : 'application/json' } + #print(apikey) + return apikey else: return { 'Authorization': 'Bearer %s' % self.get_token(), 'Accept': 'application/json' } @@ -226,7 +234,7 @@ def upload(self, path=None, filename=None): url = "%s/api/v2/%s" % (self.cloud_url, path) files = {'file': open(filename, 'rb')} res = requests.post(url, files=files, headers=self._build_headers()) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) """ GET from API resource @@ -237,9 +245,9 @@ def get(self, path=None, payload={}, headers={}): path = cut_path[1] url = "%s/api/v2/%s" % (self.cloud_url, path) - headers = dict(self._build_headers().items() + headers.items()) + headers = dict(list(self._build_headers().items()) + list(headers.items())) res = requests.get(url, params=payload, headers=headers) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) logger.debug(res.text) if headers['Accept'] == 'application/json': @@ -250,20 +258,20 @@ def get(self, path=None, payload={}, headers={}): """ POST against API resources """ def post(self, path=None, payload=None, headers={}): - headers = dict(self._build_headers().items() + headers.items()) + headers = dict(list(self._build_headers().items()) + list(headers.items())) url = "%s/api/v2/%s" % (self.cloud_url, path) res = requests.post(url, payload, headers=headers) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) return res.json() """ DELETE API resource """ def delete(self, path=None, payload=None, headers={}): - headers = dict(self._build_headers().items() + headers.items()) + headers = dict(list(self._build_headers().items()) + list(headers.items())) url = "%s/api/v2/%s" % (self.cloud_url, path) res = requests.delete(url, headers=headers) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) return res @@ -286,33 +294,33 @@ def get_devices(self, limit=0): """ def print_device_groups(self, limit=0): for device_group in self.get_device_groups(limit)['data']: - print "%s %s %s %s devices" % (str(device_group['id']).ljust(12), device_group['displayName'].ljust(30), device_group['osType'].ljust(10), device_group['deviceCount']) + print("%s %s %s %s devices" % (str(device_group['id']).ljust(12), device_group['displayName'].ljust(30), device_group['osType'].ljust(10), device_group['deviceCount'])) """ Print available free Android devices """ def print_available_free_android_devices(self, limit=0): - print "" - print "Available Free Android Devices" - print "------------------------------" + print("") + print("Available Free Android Devices") + print("------------------------------") for device in self.get_devices(limit)['data']: if device['creditsPrice'] == 0 and device['locked'] == False and device['osType'] == "ANDROID": - print device['displayName'] + print(device['displayName']) - print "" + print("") """ Print available free iOS devices """ def print_available_free_ios_devices(self, limit=0): - print "" - print "Available Free iOS Devices" - print "--------------------------" + print("") + print("Available Free iOS Devices") + print("--------------------------") for device in self.get_devices(limit)['data']: if device['creditsPrice'] == 0 and device['locked'] == False and device['osType'] == "IOS": - print device['displayName'] + print(device['displayName']) - print "" + print("") """ Print available free devices """ @@ -325,7 +333,7 @@ def print_available_free_devices(self, limit=0): """ def create_project(self, project_name, project_type): project = self.post(path="me/projects", payload={"name": project_name, "type": project_type}) - print project + print(project) logger.info("Project %s: %s (%s) created" % (project['id'], project['name'], project['type'] )) return project @@ -351,10 +359,10 @@ def get_project(self, project_id): """ def print_projects(self, limit=0): me = self.get_me() - print "Projects for %s <%s>:" % (me['name'], me['email']) - print + print("Projects for %s <%s>:" % (me['name'], me['email'])) + for project in self.get_projects(limit)['data']: - print "%s %s \"%s\"" % (str(project['id']).ljust(10), project['type'].ljust(15), project['name']) + print("%s %s \"%s\"" % (str(project['id']).ljust(10), project['type'].ljust(15), project['name'])) """ Upload application file to project """ @@ -429,7 +437,7 @@ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None # check project validity project = self.get_project(project_id) if not 'id' in project: - print "Project %s not found" % project_id + print("Project %s not found" % project_id) sys.exit(1) # start populating parameters for the request payload... @@ -440,12 +448,12 @@ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None if device_group_id is not None: payload['usedDeviceGroupId'] = device_group_id - print "Starting test run on project %s \"%s\" using device group %s" % (project['id'], project['name'], device_group_id) + print("Starting test run on project %s \"%s\" using device group %s" % (project['id'], project['name'], device_group_id)) elif device_model_ids is not None: payload['usedDeviceIds[]'] = device_model_ids - print "Starting test run on project %s \"%s\" using device models ids %s" % (project['id'], project['name'], device_model_ids) + print("Starting test run on project %s \"%s\" using device models ids %s" % (project['id'], project['name'], device_model_ids)) else: - print "Either device group or device models must be defined" + print("Either device group or device models must be defined") sys.exit(1) # add optional request params that the user might have specified @@ -455,8 +463,8 @@ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None me = self.get_me() path = "/users/%s/projects/%s/runs" % (me['id'], project_id) test_run = self.post(path=path, payload=payload) - print "Test run id: %s" % test_run['id'] - print "Name: %s" % test_run['displayName'] + print("Test run id: %s" % test_run['id']) + print("Name: %s" % test_run['displayName']) return test_run['id'] @@ -478,7 +486,7 @@ def start_wait_download_test_run(self, project_id, device_group_id=None, device_ """ def wait_test_run(self, project_id, test_run_id): if test_run_id: - print "Awaiting completion of test run with id %s. Will wait forever polling every %smins." % (test_run_id, Testdroid.polling_interval_mins) + print("Awaiting completion of test run with id %s. Will wait forever polling every %smins." % (test_run_id, Testdroid.polling_interval_mins)) while True: time.sleep(Testdroid.polling_interval_mins*60) if not self.api_key: @@ -490,19 +498,19 @@ def wait_test_run(self, project_id, test_run_id): self.get_token() #in case it expired testRunStatus = self.get_test_run(project_id, test_run_id) - if testRunStatus and testRunStatus.has_key('state'): + if testRunStatus and 'state' in testRunStatus: if testRunStatus['state'] == "FINISHED": - print "The test run with id: %s has FINISHED" % test_run_id + print("The test run with id: %s has FINISHED" % test_run_id) break elif testRunStatus['state'] == "WAITING": - print "[%s] The test run with id: %s is awaiting to be scheduled" % (time.strftime("%H:%M:%S"), test_run_id) + print("[%s] The test run with id: %s is awaiting to be scheduled" % (time.strftime("%H:%M:%S"), test_run_id)) continue elif testRunStatus['state'] == "RUNNING": - print "[%s] The test run with id: %s is running" % (time.strftime("%H:%M:%S"), test_run_id) + print("[%s] The test run with id: %s is running" % (time.strftime("%H:%M:%S"), test_run_id)) continue - print "Couldn't establish the state of the test run with id: %s. Aborting" % test_run_id - print testRunStatus + print("Couldn't establish the state of the test run with id: %s. Aborting" % test_run_id) + print(testRunStatus) sys.exit(1) @@ -527,7 +535,7 @@ def get_project_test_runs(self, project_id, limit=0): def print_project_test_runs(self, project_id, limit=0): test_runs = self.get_project_test_runs(project_id, limit)['data'] for test_run in test_runs: - print "%s %s %s %s" % (str(test_run['id']).ljust(10), ts_format(test_run['createTime']), test_run['displayName'].ljust(30), test_run['state']) + print("%s %s %s %s" % (str(test_run['id']).ljust(10), ts_format(test_run['createTime']), test_run['displayName'].ljust(30), test_run['state'])) """ Get a single test run """ @@ -584,7 +592,7 @@ def download_test_run(self, project_id, test_run_id): url = "me/files/%s/file" % (file['id']) prog = DownloadProgressBar() self.download(url, full_path, callback=lambda pos, total: prog.update(int(pos), int(total))) - print + print("") else: logger.info("File %s is not ready" % file['name']) if( len(files['data']) == 0 ): @@ -620,7 +628,7 @@ def download_test_screenshots(self, project_id, test_run_id): url = "me/projects/%s/runs/%s/device-runs/%s/screenshots/%s" % (project_id, test_run['id'], device_run['id'], screenshot['id']) prog = DownloadProgressBar() self.download(url, full_path, callback=lambda pos, total: prog.update(int(pos), int(total))) - print + print("") else: ''' Earlier downloaded images are checked, and if needed re-downloaded. ''' @@ -638,7 +646,7 @@ def download_test_screenshots(self, project_id, test_run_id): url = "me/projects/%s/runs/%s/device-runs/%s/screenshots/%s" % (project_id, test_run['id'], device_run['id'], screenshot['id']) prog = DownloadProgressBar() self.download(url, full_path, callback=lambda pos, total: prog.update(int(pos), int(total))) - print + print("") if no_screenshots: logger.info("Device %s has no screenshots - skipping" % device_run['device']['displayName']) @@ -742,7 +750,7 @@ def cli(self, parser, commands): if options.debug: logger.setLevel(logging.DEBUG) - httplib.HTTPConnection.debuglevel = 1 + http.client.HTTPConnection.debuglevel = 1 logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) @@ -766,7 +774,7 @@ def cli(self, parser, commands): parser.print_help() sys.exit(1) - print command(*args[1:]) or "" + print(command(*args[1:]) or "") #print json.dumps(result, default=lambda o: o.__dict__, sort_keys=True, indent=4) From c5816b48275f496a27b87c01ac899d6511dc296b Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Tue, 22 May 2018 14:24:20 -0700 Subject: [PATCH 02/10] Added functions: List frameworks List project types Start a new test run using test run config List input files --- testdroid/__init__.py | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index b7cd034..79bd2fa 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -277,11 +277,28 @@ def get_me(self): def get_device_groups(self, limit=0): return self.get("me/device-groups", payload = {'limit': limit}) + """ Returns list of frameworks + """ + def get_frameworks(self, limit=0): + return self.get("me/available-frameworks", payload = {'limit': limit}) + + """ Returns list of project types + """ + def get_project_types(self, limit=0): + return self.get("me/available-project-types", payload = {'limit': limit}) + """ Returns list of devices """ def get_devices(self, limit=0): return self.get(path = "devices", payload = {'limit': limit}) + + """ Print input files + """ + def print_input_files(self, limit=0): + for input_file in self.get_input_files(limit)['data']: + print("id:{} name:{} size:{} type:{}".format(input_file['id'],input_file['name'],input_file['size'],input_file['inputType'])) + """ Print device groups """ def print_device_groups(self, limit=0): @@ -300,6 +317,25 @@ def print_available_free_android_devices(self, limit=0): print device['displayName'] print "" + + """ Print available frameworks + """ + def print_available_frameworks(self, os_type=None, limit=0): + print "" + print "Available frameworks" + print "------------------------------" + for framework in self.get_frameworks(limit)['data']: + print("id: {}\tosType:{}\tname:{}".format(framework['id'], framework['osType'], framework['name'])) + + """ Print available project type + """ + def print_available_project_types(self, os_type=None, limit=0): + print "" + print "Available project types" + print "------------------------------" + for project_type in self.get_project_types(limit)['items']: + print("name:{}".format(project_type)) + """ Print available free iOS devices """ @@ -423,6 +459,28 @@ def set_project_framework(self, project_id, frameworkId): } self.post(path, payload={"frameworkId": frameworkId}) + + """ Start a test run using test run config + e.g '{"frameworkId":12252, + "osType": "ANDROID", + "projectId":1234, + "files":[{"id":9876}, {"id":5432}] + "testRunParameters":[{"key":"xyz", "value":"abc"}], + "deviceGroupId":6854 + }' + client.start_test_run_using_config(json.dumps({"frameworkId":123213})) + """ + def start_test_run_using_config(self, test_run_config={}): + if type(test_run_config) == str: + payload = json.loads(test_run_config) + else: + payload = test_run_config + + me = self.get_me() + path = "users/%s/runs" % (me['id']) + test_run = self.post(path=path, payload=test_run_config, headers={'Content-type': 'application/json', 'Accept': 'application/json'}) + return test_run + """ Start a test run on a device group """ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None, name=None, additional_params={}): @@ -557,6 +615,11 @@ def get_device_run_files(self, project_id, test_run_id, device_session_id, tags= else: return self.get("me/projects/%s/runs/%s/device-sessions/%s/output-file-set/files?tag[]=%s" % (project_id, test_run_id, device_session_id, tags)) + """ Get list of input files + """ + def get_input_files(self, limit=0): + return self.get("me/files?limit={}&filter=s_direction_eq_INPUT".format(limit)) + """ Downloads test run files to a directory hierarchy """ def download_test_run(self, project_id, test_run_id): @@ -714,6 +777,8 @@ def get_commands(self): "me": self.get_me, "device-groups": self.print_device_groups, "available-free-devices": self.print_available_free_devices, + "available-frameworks": self.print_available_frameworks, + "available-project-types": self.print_available_project_types, "projects": self.print_projects, "create-project": self.create_project, "delete-project": self.delete_project, @@ -722,12 +787,14 @@ def get_commands(self): "upload-data": self.upload_data_file, "set-project-config": self.set_project_config, "start-test-run": self.start_test_run, + "start-test-run-using-config": self.start_test_run_using_config, "start-wait-download-test-run":self.start_wait_download_test_run, "wait-test-run":self.wait_test_run, "test-run": self.get_test_run, "test-runs": self.print_project_test_runs, "device-runs": self.get_device_runs, "device-run-files": self.get_device_run_files, + "list-input-files": self.print_input_files, "download-test-run": self.download_test_run, "download-test-screenshots": self.download_test_screenshots } From 8795bbe09e33cfb228f3971b26b24365c2b3aed3 Mon Sep 17 00:00:00 2001 From: Emil Kreutzman Date: Thu, 3 May 2018 15:47:27 -0700 Subject: [PATCH 03/10] Don't separately update project when calling start_test_run() --- testdroid/__init__.py | 51 +++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index 10cfd8a..04194a4 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -426,45 +426,38 @@ def set_project_framework(self, project_id, frameworkId): """ Start a test run on a device group """ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None, name=None, additional_params={}): - me = self.get_me() - payload={} if name is None else {'name':name} + # check project validity project = self.get_project(project_id) if not 'id' in project: print "Project %s not found" % project_id sys.exit(1) - if device_group_id is None and device_model_ids is None: - print "Device group or device models must be defined" - sys.exit(1) - - if device_group_id is not None: - device_group = self.get("users/%s/device-groups/%s" % (me['id'], device_group_id)) - if not 'id' in device_group: - print "Device group %s not found" % device_group_id - sys.exit(1) - - if int(device_group['deviceCount']) == 0: - print "ERROR: No devices at device group %s" % device_group['id'] - sys.exit(1) + # start populating parameters for the request payload... + payload={} - # Update device group - reply = self.set_project_config(project_id=project_id, payload={'usedDeviceGroupId': device_group_id}) - if int(reply['usedDeviceGroupId']) != int(device_group_id): - print "Unable to set used device group to %s for project %s" % (device_group_id, project_id) - sys.exit(1) - print "Starting test run on project %s \"%s\" using device group %s \"%s\"" % (project['id'], project['name'], device_group['id'], device_group['displayName']) + if name is not None: + payload['name'] = name + if device_group_id is not None: + payload['usedDeviceGroupId'] = device_group_id + print "Starting test run on project %s \"%s\" using device group %s" % (project['id'], project['name'], device_group_id) + elif device_model_ids is not None: + payload['usedDeviceIds[]'] = device_model_ids + print "Starting test run on project %s \"%s\" using device models ids %s" % (project['id'], project['name'], device_model_ids) else: - payload={'usedDeviceIds[]': device_model_ids} - print "Starting test run on project %s \"%s\" using device models ids %s " % (project['id'], project['name'], device_model_ids) + print "Either device group or device models must be defined" + sys.exit(1) - # Start run - path = "/users/%s/projects/%s/runs" % ( me['id'], project_id ) + # add optional request params that the user might have specified payload.update(additional_params) - reply = self.post(path=path, payload=payload) - print "Test run id: %s" % reply['id'] - print "Name: %s" % reply['displayName'] - return reply['id'] + + # actually start the test run + me = self.get_me() + path = "/users/%s/projects/%s/runs" % (me['id'], project_id) + test_run = self.post(path=path, payload=payload) + print "Test run id: %s" % test_run['id'] + print "Name: %s" % test_run['displayName'] + return test_run['id'] """ Start a test run on a device group and wait for completion From 39bd0ae33a3a25d0c8f025bbeffd1af60e75a710 Mon Sep 17 00:00:00 2001 From: Emil Kreutzman Date: Thu, 3 May 2018 15:49:01 -0700 Subject: [PATCH 04/10] Bumped version to 2.41.4 (fix for start_test_run) --- CHANGELOG | 2 ++ setup.py | 2 +- testdroid/__init__.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fc13b3b..5d5ef2f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +2.41.4 + * Don't separately update project when calling start_test_run() 2.41.3 * Isolate Pillow import, with fallback if import fails 2.41.2 diff --git a/setup.py b/setup.py index adfd8f2..1c43050 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import sys, os -version = '2.41.3' +version = '2.41.4' setup(name='testdroid', version=version, diff --git a/testdroid/__init__.py b/testdroid/__init__.py index 04194a4..b7cd034 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -4,7 +4,7 @@ from optparse import OptionParser from datetime import datetime -__version__ = '2.41.3' +__version__ = '2.41.4' FORMAT = "%(message)s" logging.basicConfig(format=FORMAT) From 469cb772aba439369b0c2b10c33351ac8fdbd6d6 Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Tue, 22 May 2018 14:24:20 -0700 Subject: [PATCH 05/10] Added functions: List frameworks List project types Start a new test run using test run config List input files --- testdroid/__init__.py | 67 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index b7cd034..79bd2fa 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -277,11 +277,28 @@ def get_me(self): def get_device_groups(self, limit=0): return self.get("me/device-groups", payload = {'limit': limit}) + """ Returns list of frameworks + """ + def get_frameworks(self, limit=0): + return self.get("me/available-frameworks", payload = {'limit': limit}) + + """ Returns list of project types + """ + def get_project_types(self, limit=0): + return self.get("me/available-project-types", payload = {'limit': limit}) + """ Returns list of devices """ def get_devices(self, limit=0): return self.get(path = "devices", payload = {'limit': limit}) + + """ Print input files + """ + def print_input_files(self, limit=0): + for input_file in self.get_input_files(limit)['data']: + print("id:{} name:{} size:{} type:{}".format(input_file['id'],input_file['name'],input_file['size'],input_file['inputType'])) + """ Print device groups """ def print_device_groups(self, limit=0): @@ -300,6 +317,25 @@ def print_available_free_android_devices(self, limit=0): print device['displayName'] print "" + + """ Print available frameworks + """ + def print_available_frameworks(self, os_type=None, limit=0): + print "" + print "Available frameworks" + print "------------------------------" + for framework in self.get_frameworks(limit)['data']: + print("id: {}\tosType:{}\tname:{}".format(framework['id'], framework['osType'], framework['name'])) + + """ Print available project type + """ + def print_available_project_types(self, os_type=None, limit=0): + print "" + print "Available project types" + print "------------------------------" + for project_type in self.get_project_types(limit)['items']: + print("name:{}".format(project_type)) + """ Print available free iOS devices """ @@ -423,6 +459,28 @@ def set_project_framework(self, project_id, frameworkId): } self.post(path, payload={"frameworkId": frameworkId}) + + """ Start a test run using test run config + e.g '{"frameworkId":12252, + "osType": "ANDROID", + "projectId":1234, + "files":[{"id":9876}, {"id":5432}] + "testRunParameters":[{"key":"xyz", "value":"abc"}], + "deviceGroupId":6854 + }' + client.start_test_run_using_config(json.dumps({"frameworkId":123213})) + """ + def start_test_run_using_config(self, test_run_config={}): + if type(test_run_config) == str: + payload = json.loads(test_run_config) + else: + payload = test_run_config + + me = self.get_me() + path = "users/%s/runs" % (me['id']) + test_run = self.post(path=path, payload=test_run_config, headers={'Content-type': 'application/json', 'Accept': 'application/json'}) + return test_run + """ Start a test run on a device group """ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None, name=None, additional_params={}): @@ -557,6 +615,11 @@ def get_device_run_files(self, project_id, test_run_id, device_session_id, tags= else: return self.get("me/projects/%s/runs/%s/device-sessions/%s/output-file-set/files?tag[]=%s" % (project_id, test_run_id, device_session_id, tags)) + """ Get list of input files + """ + def get_input_files(self, limit=0): + return self.get("me/files?limit={}&filter=s_direction_eq_INPUT".format(limit)) + """ Downloads test run files to a directory hierarchy """ def download_test_run(self, project_id, test_run_id): @@ -714,6 +777,8 @@ def get_commands(self): "me": self.get_me, "device-groups": self.print_device_groups, "available-free-devices": self.print_available_free_devices, + "available-frameworks": self.print_available_frameworks, + "available-project-types": self.print_available_project_types, "projects": self.print_projects, "create-project": self.create_project, "delete-project": self.delete_project, @@ -722,12 +787,14 @@ def get_commands(self): "upload-data": self.upload_data_file, "set-project-config": self.set_project_config, "start-test-run": self.start_test_run, + "start-test-run-using-config": self.start_test_run_using_config, "start-wait-download-test-run":self.start_wait_download_test_run, "wait-test-run":self.wait_test_run, "test-run": self.get_test_run, "test-runs": self.print_project_test_runs, "device-runs": self.get_device_runs, "device-run-files": self.get_device_run_files, + "list-input-files": self.print_input_files, "download-test-run": self.download_test_run, "download-test-screenshots": self.download_test_screenshots } From 7cc096f09333438018b3faaf3b7927bbec262af2 Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Wed, 23 May 2018 09:59:05 -0700 Subject: [PATCH 06/10] Removed project type methods --- testdroid/__init__.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index 79bd2fa..82d04c5 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -282,11 +282,6 @@ def get_device_groups(self, limit=0): def get_frameworks(self, limit=0): return self.get("me/available-frameworks", payload = {'limit': limit}) - """ Returns list of project types - """ - def get_project_types(self, limit=0): - return self.get("me/available-project-types", payload = {'limit': limit}) - """ Returns list of devices """ def get_devices(self, limit=0): @@ -327,15 +322,6 @@ def print_available_frameworks(self, os_type=None, limit=0): for framework in self.get_frameworks(limit)['data']: print("id: {}\tosType:{}\tname:{}".format(framework['id'], framework['osType'], framework['name'])) - """ Print available project type - """ - def print_available_project_types(self, os_type=None, limit=0): - print "" - print "Available project types" - print "------------------------------" - for project_type in self.get_project_types(limit)['items']: - print("name:{}".format(project_type)) - """ Print available free iOS devices """ @@ -778,7 +764,6 @@ def get_commands(self): "device-groups": self.print_device_groups, "available-free-devices": self.print_available_free_devices, "available-frameworks": self.print_available_frameworks, - "available-project-types": self.print_available_project_types, "projects": self.print_projects, "create-project": self.create_project, "delete-project": self.delete_project, From bf2df62e51a0f8e52c0605f1014a6c986619f434 Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Thu, 24 May 2018 16:30:44 -0700 Subject: [PATCH 07/10] Updated to support python3 --- testdroid/__init__.py | 123 ++++++++++++++++++++++-------------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index 82d04c5..d66c0f5 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -1,6 +1,12 @@ # -*- coding: utf-8 -*- -import os, sys, requests, json, logging, time, httplib, base64 +import os, sys, requests, json, logging, time, base64 + +if sys.version_info[0] > 2: + import http.client +else: + import httplib + from optparse import OptionParser from datetime import datetime @@ -62,7 +68,7 @@ def update(self, pos, total): all_full = self.width - 2 num_hashes = int(round((percent_done / 100.0) * all_full)) self.prog_bar = ' [' + self.fill_char * num_hashes + ' ' * (all_full - num_hashes) + ']' - pct_place = (len(self.prog_bar) / 2) - len(str(percent_done)) + pct_place = (len(self.prog_bar) // 2) - len(str(percent_done)) pct_string = '%d%%' % percent_done self.duration = int(round(time.time()-self.started)) self.eta = int(round( self.duration / (percent_done / 100.0)))-self.duration if percent_done > 5 else 'N/A' @@ -74,9 +80,9 @@ def update(self, pos, total): else: self.prog_bar += ' ' if sys.platform.lower().startswith('win'): - print self, '\r', + print(self +'\r') else: - print self, chr(27) + '[A' + print(str(self) + chr(27) + '[A') def __str__(self): return str(self.prog_bar) @@ -99,13 +105,13 @@ class Testdroid: # polling interval when awaiting for test run completion polling_interval_mins = 10 - """ Constructor, defaults against cloud.testdroid.com + """ Constructor, defaults against cloud.bitbar.com """ def __init__(self, **kwargs): self.api_key = kwargs.get('apikey') self.username = kwargs.get('username') self.password = kwargs.get('password') - self.cloud_url = kwargs.get('url') or "https://cloud.testdroid.com" + self.cloud_url = kwargs.get('url') or "https://cloud.bitbar.com" self.download_buffer_size = kwargs.get('download_buffer_size') or 65536 def set_apikey(self, apikey): @@ -140,7 +146,7 @@ def get_token(self): data = payload, headers = { "Accept": "application/json" } ) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) reply = res.json() @@ -160,8 +166,8 @@ def get_token(self): data = payload, headers = { "Accept": "application/json" } ) - if res.status_code not in range(200, 300): - print "FAILED: Unable to get a new access token using refresh token" + if res.status_code not in list(range(200, 300)): + print("FAILED: Unable to get a new access token using refresh token") self.access_token = None return self.get_token() @@ -177,7 +183,9 @@ def get_token(self): """ def _build_headers(self): if self.api_key: - return {'Authorization' : 'Basic %s' % base64.b64encode(self.api_key+":"), 'Accept' : 'application/json' } + apikey = {'Authorization' : 'Basic %s' % base64.b64encode((self.api_key+":").encode(encoding='utf_8')).decode(), 'Accept' : 'application/json' } + #print(apikey) + return apikey else: return { 'Authorization': 'Bearer %s' % self.get_token(), 'Accept': 'application/json' } @@ -226,7 +234,7 @@ def upload(self, path=None, filename=None): url = "%s/api/v2/%s" % (self.cloud_url, path) files = {'file': open(filename, 'rb')} res = requests.post(url, files=files, headers=self._build_headers()) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) """ GET from API resource @@ -237,9 +245,9 @@ def get(self, path=None, payload={}, headers={}): path = cut_path[1] url = "%s/api/v2/%s" % (self.cloud_url, path) - headers = dict(self._build_headers().items() + headers.items()) + headers = dict(list(self._build_headers().items()) + list(headers.items())) res = requests.get(url, params=payload, headers=headers) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) logger.debug(res.text) if headers['Accept'] == 'application/json': @@ -250,20 +258,20 @@ def get(self, path=None, payload={}, headers={}): """ POST against API resources """ def post(self, path=None, payload=None, headers={}): - headers = dict(self._build_headers().items() + headers.items()) + headers = dict(list(self._build_headers().items()) + list(headers.items())) url = "%s/api/v2/%s" % (self.cloud_url, path) res = requests.post(url, payload, headers=headers) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) return res.json() """ DELETE API resource """ def delete(self, path=None, payload=None, headers={}): - headers = dict(self._build_headers().items() + headers.items()) + headers = dict(list(self._build_headers().items()) + list(headers.items())) url = "%s/api/v2/%s" % (self.cloud_url, path) res = requests.delete(url, headers=headers) - if res.status_code not in range(200, 300): + if res.status_code not in list(range(200, 300)): raise RequestResponseError(res.text, res.status_code) return res @@ -298,43 +306,42 @@ def print_input_files(self, limit=0): """ def print_device_groups(self, limit=0): for device_group in self.get_device_groups(limit)['data']: - print "%s %s %s %s devices" % (str(device_group['id']).ljust(12), device_group['displayName'].ljust(30), device_group['osType'].ljust(10), device_group['deviceCount']) + print("%s %s %s %s devices" % (str(device_group['id']).ljust(12), device_group['displayName'].ljust(30), device_group['osType'].ljust(10), device_group['deviceCount'])) """ Print available free Android devices """ def print_available_free_android_devices(self, limit=0): - print "" - print "Available Free Android Devices" - print "------------------------------" + print("") + print("Available Free Android Devices") + print("------------------------------") for device in self.get_devices(limit)['data']: if device['creditsPrice'] == 0 and device['locked'] == False and device['osType'] == "ANDROID": - print device['displayName'] - - print "" + print(device['displayName']) + print("") """ Print available frameworks """ def print_available_frameworks(self, os_type=None, limit=0): - print "" - print "Available frameworks" - print "------------------------------" + print("") + print("Available frameworks") + print("------------------------------") for framework in self.get_frameworks(limit)['data']: print("id: {}\tosType:{}\tname:{}".format(framework['id'], framework['osType'], framework['name'])) - + print("") """ Print available free iOS devices """ def print_available_free_ios_devices(self, limit=0): - print "" - print "Available Free iOS Devices" - print "--------------------------" + print("") + print("Available Free iOS Devices") + print("--------------------------") for device in self.get_devices(limit)['data']: if device['creditsPrice'] == 0 and device['locked'] == False and device['osType'] == "IOS": - print device['displayName'] + print(device['displayName']) - print "" + print("") """ Print available free devices """ @@ -347,7 +354,7 @@ def print_available_free_devices(self, limit=0): """ def create_project(self, project_name, project_type): project = self.post(path="me/projects", payload={"name": project_name, "type": project_type}) - print project + print(project) logger.info("Project %s: %s (%s) created" % (project['id'], project['name'], project['type'] )) return project @@ -373,10 +380,10 @@ def get_project(self, project_id): """ def print_projects(self, limit=0): me = self.get_me() - print "Projects for %s <%s>:" % (me['name'], me['email']) - print + print("Projects for %s <%s>:" % (me['name'], me['email'])) + for project in self.get_projects(limit)['data']: - print "%s %s \"%s\"" % (str(project['id']).ljust(10), project['type'].ljust(15), project['name']) + print("%s %s \"%s\"" % (str(project['id']).ljust(10), project['type'].ljust(15), project['name'])) """ Upload application file to project """ @@ -473,7 +480,7 @@ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None # check project validity project = self.get_project(project_id) if not 'id' in project: - print "Project %s not found" % project_id + print("Project %s not found" % project_id) sys.exit(1) # start populating parameters for the request payload... @@ -484,12 +491,12 @@ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None if device_group_id is not None: payload['usedDeviceGroupId'] = device_group_id - print "Starting test run on project %s \"%s\" using device group %s" % (project['id'], project['name'], device_group_id) + print("Starting test run on project %s \"%s\" using device group %s" % (project['id'], project['name'], device_group_id)) elif device_model_ids is not None: payload['usedDeviceIds[]'] = device_model_ids - print "Starting test run on project %s \"%s\" using device models ids %s" % (project['id'], project['name'], device_model_ids) + print("Starting test run on project %s \"%s\" using device models ids %s" % (project['id'], project['name'], device_model_ids)) else: - print "Either device group or device models must be defined" + print("Either device group or device models must be defined") sys.exit(1) # add optional request params that the user might have specified @@ -499,8 +506,8 @@ def start_test_run(self, project_id, device_group_id=None, device_model_ids=None me = self.get_me() path = "/users/%s/projects/%s/runs" % (me['id'], project_id) test_run = self.post(path=path, payload=payload) - print "Test run id: %s" % test_run['id'] - print "Name: %s" % test_run['displayName'] + print("Test run id: %s" % test_run['id']) + print("Name: %s" % test_run['displayName']) return test_run['id'] @@ -522,7 +529,7 @@ def start_wait_download_test_run(self, project_id, device_group_id=None, device_ """ def wait_test_run(self, project_id, test_run_id): if test_run_id: - print "Awaiting completion of test run with id %s. Will wait forever polling every %smins." % (test_run_id, Testdroid.polling_interval_mins) + print("Awaiting completion of test run with id %s. Will wait forever polling every %smins." % (test_run_id, Testdroid.polling_interval_mins)) while True: time.sleep(Testdroid.polling_interval_mins*60) if not self.api_key: @@ -534,19 +541,19 @@ def wait_test_run(self, project_id, test_run_id): self.get_token() #in case it expired testRunStatus = self.get_test_run(project_id, test_run_id) - if testRunStatus and testRunStatus.has_key('state'): + if testRunStatus and 'state' in testRunStatus: if testRunStatus['state'] == "FINISHED": - print "The test run with id: %s has FINISHED" % test_run_id + print("The test run with id: %s has FINISHED" % test_run_id) break elif testRunStatus['state'] == "WAITING": - print "[%s] The test run with id: %s is awaiting to be scheduled" % (time.strftime("%H:%M:%S"), test_run_id) + print("[%s] The test run with id: %s is awaiting to be scheduled" % (time.strftime("%H:%M:%S"), test_run_id)) continue elif testRunStatus['state'] == "RUNNING": - print "[%s] The test run with id: %s is running" % (time.strftime("%H:%M:%S"), test_run_id) + print("[%s] The test run with id: %s is running" % (time.strftime("%H:%M:%S"), test_run_id)) continue - print "Couldn't establish the state of the test run with id: %s. Aborting" % test_run_id - print testRunStatus + print("Couldn't establish the state of the test run with id: %s. Aborting" % test_run_id) + print(testRunStatus) sys.exit(1) @@ -571,7 +578,7 @@ def get_project_test_runs(self, project_id, limit=0): def print_project_test_runs(self, project_id, limit=0): test_runs = self.get_project_test_runs(project_id, limit)['data'] for test_run in test_runs: - print "%s %s %s %s" % (str(test_run['id']).ljust(10), ts_format(test_run['createTime']), test_run['displayName'].ljust(30), test_run['state']) + print("%s %s %s %s" % (str(test_run['id']).ljust(10), ts_format(test_run['createTime']), test_run['displayName'].ljust(30), test_run['state'])) """ Get a single test run """ @@ -633,7 +640,7 @@ def download_test_run(self, project_id, test_run_id): url = "me/files/%s/file" % (file['id']) prog = DownloadProgressBar() self.download(url, full_path, callback=lambda pos, total: prog.update(int(pos), int(total))) - print + print("") else: logger.info("File %s is not ready" % file['name']) if( len(files['data']) == 0 ): @@ -669,7 +676,7 @@ def download_test_screenshots(self, project_id, test_run_id): url = "me/projects/%s/runs/%s/device-runs/%s/screenshots/%s" % (project_id, test_run['id'], device_run['id'], screenshot['id']) prog = DownloadProgressBar() self.download(url, full_path, callback=lambda pos, total: prog.update(int(pos), int(total))) - print + print("") else: ''' Earlier downloaded images are checked, and if needed re-downloaded. ''' @@ -687,7 +694,7 @@ def download_test_screenshots(self, project_id, test_run_id): url = "me/projects/%s/runs/%s/device-runs/%s/screenshots/%s" % (project_id, test_run['id'], device_run['id'], screenshot['id']) prog = DownloadProgressBar() self.download(url, full_path, callback=lambda pos, total: prog.update(int(pos), int(total))) - print + print("") if no_screenshots: logger.info("Device %s has no screenshots - skipping" % device_run['device']['displayName']) @@ -750,8 +757,8 @@ def format_epilog(self, formatter): help="Username - the email address. Optional. You can use environment variable TESTDROID_USERNAME as well.") parser.add_option("-p", "--password", dest="password", help="Password. Required if username is used. You can use environment variable TESTDROID_PASSWORD as well.") - parser.add_option("-c", "--url", dest="url", default="https://cloud.testdroid.com", - help="Cloud endpoint. Default is https://cloud.testdroid.com. You can use environment variable TESTDROID_URL as well.") + parser.add_option("-c", "--url", dest="url", default="https://cloud.bitbar.com", + help="Cloud endpoint. Default is https://cloud.bitbar.com. You can use environment variable TESTDROID_URL as well.") parser.add_option("-q", "--quiet", action="store_true", dest="quiet", help="Quiet mode") parser.add_option("-d", "--debug", action="store_true", dest="debug", @@ -794,7 +801,7 @@ def cli(self, parser, commands): if options.debug: logger.setLevel(logging.DEBUG) - httplib.HTTPConnection.debuglevel = 1 + http.client.HTTPConnection.debuglevel = 1 logging.getLogger().setLevel(logging.DEBUG) requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) @@ -818,7 +825,7 @@ def cli(self, parser, commands): parser.print_help() sys.exit(1) - print command(*args[1:]) or "" + print(command(*args[1:]) or "") #print json.dumps(result, default=lambda o: o.__dict__, sort_keys=True, indent=4) From c326ba74cae6222a47e04eb3ce73bef292c64d8e Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Thu, 24 May 2018 17:04:04 -0700 Subject: [PATCH 08/10] Fixed problems reported by pyflakes --- testdroid/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/testdroid/__init__.py b/testdroid/__init__.py index b9eb380..827055a 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -6,6 +6,7 @@ import http.client else: import httplib + assert httplib from optparse import OptionParser from datetime import datetime @@ -464,10 +465,6 @@ def set_project_framework(self, project_id, frameworkId): client.start_test_run_using_config(json.dumps({"frameworkId":123213})) """ def start_test_run_using_config(self, test_run_config={}): - if type(test_run_config) == str: - payload = json.loads(test_run_config) - else: - payload = test_run_config me = self.get_me() path = "users/%s/runs" % (me['id']) From 848d9941f94e6baca8e11bc8d1f2c3d8ca99dc46 Mon Sep 17 00:00:00 2001 From: Jenkins Date: Fri, 25 May 2018 02:05:51 +0200 Subject: [PATCH 09/10] =?UTF-8?q?Bump=20version:=202.41.4=20=E2=86=92=202.?= =?UTF-8?q?42.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.py | 2 +- testdroid/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1c43050..badbedc 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import sys, os -version = '2.41.4' +version = '2.42.0' setup(name='testdroid', version=version, diff --git a/testdroid/__init__.py b/testdroid/__init__.py index 827055a..07957ea 100755 --- a/testdroid/__init__.py +++ b/testdroid/__init__.py @@ -11,7 +11,7 @@ from optparse import OptionParser from datetime import datetime -__version__ = '2.41.4' +__version__ = '2.42.0' FORMAT = "%(message)s" logging.basicConfig(format=FORMAT) From 47c11fef942c62e6c69b7ee6b8dce7e8a3bb29ed Mon Sep 17 00:00:00 2001 From: Sakari Rautiainen Date: Thu, 24 May 2018 17:35:15 -0700 Subject: [PATCH 10/10] Updated Changelog --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 5d5ef2f..49013a8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +2.42.0 + * Support for Python 3.x + * New method to launch test run using test run config (Requires: Testdroid v2.58 or higher) + * Api call to retrieve input files 2.41.4 * Don't separately update project when calling start_test_run() 2.41.3