def AddTestToTestSet(self, test_case_id, test_set_id): ''' See https://sharenet-ims.inside.nsn.com/livelink/livelink/Download/392499262 Return value is very intersting, because it tells the test instance ID: <return_message> <status>True</status> <return_value> <test_instance_id>62</test_instance_id> </return_value> </return_message> ''' parameters = ElementTree.Element('parameters') ElementTree.SubElement(parameters, 'test_set_id').text = test_set_id instances = ElementTree.SubElement(parameters, 'test_instances') instance = ElementTree.SubElement(instances, 'test_instance') fields = ElementTree.SubElement(instance, 'fields') ElementTree.SubElement(fields, 'TC_TEST_ID').text = test_case_id ElementTree.SubElement(instance, 'attachments') input_xml = self._constructInputXML('AddTests2TestSet', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("AddTests2TestSet", input_xml) element = ElementTree.XML(returnXML) test_instance_ids = element.findall('return_value')[0].findall('test_instance_id') if len(test_instance_ids) != 1: raise Exception("Did not get unique (or any) test instance ID.") else: return test_instance_ids[0].text
def _report(self, testset_opt, testset_id_opt, release=None, build=None): Log.critical("Trying to connect QualityCenter...") # Log.info('Connected to QCXML version %s' % client.GetVersion()) Log.critical("Validating data...") if testset_id_opt: testset_qcid = testset_id_opt else: testset_path, testset_name = self._split_testset_argument( testset_opt) try: testset_qcid = self.client.FindTestSet(testset_path, testset_name) except: testset_qcid = self.client.AddTestSet(testset_path, testset_name) already_added_cases = self.client.GetTestSetInstances(testset_qcid) Log.info( 'QC internal ID for the test set is "%s". It contains instances for %s test cases' % (testset_qcid, len(already_added_cases))) results = [] for result in self.results: # Extend result dicts with QC internal IDs for test cases try: test_case_id = result['test_case_id'].split()[0] qcid = self.client.FindTestCase(test_case_id) Log.info('QC internal ID for test case "%s" is "%s"' % (test_case_id, qcid)) result['qcid'] = qcid qcstatus = "Passed" if result['status'] == "PASS" else "Failed" result['qcstatus'] = qcstatus results.append(result) except Exception, errmsg: Log.info("test case id <%s> error: %s" % (test_case_id, errmsg))
def initialize_client(self): self.client = QcRestClient(self.config.server_url, self.config.username, self.config.password, self.config.domain, self.config.project, self.config.proxy) Log.critical("Authenticating to QC REST API...") self.client.authenticate()
def wait(self): if self.attempt >= self.max_attempts: raise RetryLimitExceeded(self.max_attempts) Log.debug("Retry attempt:%s, next sleep:%s" % (self.attempt, self.next_sleep())) time.sleep(self.next_sleep()) self.attempt += 1
def AddTestSet(self, path, test_set_name): ''' Add test set by path and name More details, See: <https://qc-api.inside.nsn.com/QcXmlWebSite/> Excpected return value from API <return_message> <status>True</status> <return_value> <id>1803</id> </return_value> </return_message> ''' parameters = ElementTree.Element('parameters') ElementTree.SubElement(parameters, 'path').text = path test_set = ElementTree.SubElement(parameters, 'test_set') ElementTree.SubElement(test_set, 'attachments').text = '' fields = ElementTree.SubElement(test_set, 'fields') ElementTree.SubElement(fields, 'CY_CYCLE').text = test_set_name ElementTree.SubElement(fields, 'CY_USER_01').text = 'RCP0' ElementTree.SubElement(fields, 'CY_USER_02').text = 'EnTe - Functional Test' ElementTree.SubElement(fields, 'CY_USER_13').text = 'Draft' ElementTree.SubElement(fields, 'CY_USER_18').text = 'N/A' ElementTree.SubElement(fields, 'CY_USER_51').text = 'cWLC' input_xml = self._constructInputXML('AddTestSet', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("AddTestSet", input_xml) element = ElementTree.XML(returnXML) return element.findall('return_value')[0].findall('id')[0].text
def map_tc2req(self, qc_test_id, requirement_id): coverage = self.client.getReqCoverageByTCID(qc_test_id) if requirement_id in coverage: Log.info('Requirement map already exists - no need to create') else: Log.info('Requirement map missing - creating') self.client.addReqCoverage(qc_test_id, requirement_id)
def initialize_client(self): self.client = QcXmlClient(self.config.xml_api_url, self.config.username, self.config.password, self.config.domain, self.config.project, self.config.test_case_search_root, self.config.proxy) Log.critical("Initialized QC XML API client") self.client.initialize()
def authenticate(self): path = 'qcbin/authentication-point/alm-authenticate' data = ("<?xml version='1.0' encoding='utf-8'?>" "<alm-authentication>" "<user>%s</user>" "<password>%s</password>" "</alm-authentication>") % (self.username, self.password) q = RestHeaderQuery(path, "post", data=data) headers = q.request(self.server, proxies=self.proxy) self.cookie = headers['set-cookie'] Log.debug("Got cookie: %s" % self.cookie)
def __init__(self, server, username, password, domain, project, proxy): super(QcRestClient, self).__init__() self.server = server self.username = username self.password = password self.domain = domain self.project = project self.proxy = proxy self.cookie = None logvars = copy.deepcopy(vars()) del logvars['password'] Log.debug('Initialized QC REST client with: %s' % logvars)
def _split_testset_argument(testset_input): Log.debug("Parsing test set '%s'" % testset_input) match = re.search(r'\\', testset_input) if not match: raise QcReporterException( "Test set path must look like 'Root\\Folder\\...\\Test set name' " "and it must contain at least one path separator character ('\\')" ) testset_components = testset_input.rsplit('\\', 1) testset_path = testset_components[0] testset_name = testset_components[1] Log.debug("Looking for test set %s in path %s" % (testset_name, testset_path)) return (testset_path, testset_name)
def add_tc(self, test_path, test_id, test_name, test_prio): parent_id = self.client.resolveTestFolderQcId(test_path) existing_test_id = self.client.getTestCaseByTCID( test_id, parent_id=parent_id, raise_on_empty_match=False) if existing_test_id: Log.info('test %s: id=%s already exists - updating' % (test_id, existing_test_id)) self.client.updateTestCase(existing_test_id, parent_id, test_id, test_name, test_prio) else: Log.info('test %s: not existing - adding' % (test_id)) existing_test_id = self.client.addTestCase(parent_id, test_id, test_name, test_prio) return int(existing_test_id)
def _QcXmlApiCall(self, method_name, inputXML): Log.debug('Calling %s on %s' % (method_name, self.url)) delay = RetryDelay(40) while True: try: ret = getattr(self.client.service, method_name)(inputXML) return self._FilterQcXmlExceptions(ret) except QcXmlException: raise except Exception, e: Log.warn('Temporary problem in QualityCenter (%s). Retrying in %s s...' % (e, delay.next_sleep())) delay.wait() # Will throw exception after max attempts self.initialize()
def GetUserProjects(self): ''' Excpected return value from API <return_message> <status>True</status> <return_value> <project_name>FP</project_name> </return_value> </return_message> ''' input_xml = self._constructInputXML('GetUserProjects') Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("GetUserProjects", input_xml) element = ElementTree.XML(returnXML) return [x.text for x in element.findall('return_value')[0].findall('project_name')]
def equals_query(queries): """ Query to be appended to URL after "?" queries - dict of key-value pairs that must match. e.g. {'user-01': 'EXAMPLE_001'}} No support for spaces or special characters. Add the support in this fuction if you need them. """ key_value_pair_strings = [] for key, value in queries.items(): if type(value) is str: value = value.replace('&', '%26') if type(value) == int: value_str = '%s' % value else: value_str = "'%s'" % value Log.debug("############################# STR: %s" % value_str) key_value_pair_strings.append('%s[%s]' % (key, value_str)) return "query={%s}" % (';'.join(key_value_pair_strings))
def FindTestCase(self, test_case_id, path=None): ''' Finds test case's numeric ID by the test_case_id string. test_case_id is the "Test Case ID" in QC web UI. In the database this field is called TS_USER_01. Numeric test case id is internal to QC and not visible in the web user interface. Expected return value, e.g. <return_message> <status>True</status> <return_value> <test_case> <id>28</id> <name>Authentication configuration of NTP</name> <path>\Subject\VGP&LCC\NECC\Operability Interfaces\NTP\NECC provides clock synchronization to vNE</path> </test_case> </return_value> </return_message> ''' if not path: path = self.test_case_search_root parameters = ElementTree.Element('parameters') path_elem = ElementTree.SubElement(parameters, 'path') ElementTree.SubElement(parameters, 'recursive').text = 'true' ElementTree.SubElement(parameters, 'include_attachments').text = 'false' ElementTree.SubElement(parameters, 'include_test_case_details').text = 'false' fields = ElementTree.SubElement(parameters, 'fields') test_case_id_elem = ElementTree.SubElement(fields, 'TS_USER_01') path_elem.text = path test_case_id_elem.text = '"%s"' % test_case_id input_xml = self._constructInputXML('GetTestCaseList', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("GetTestCaseList", input_xml) element = ElementTree.XML(returnXML) testcases = element.findall('return_value')[0].findall('test_case') if len(testcases) > 1: raise QcClientException("Test case ID %s is not unique. Candidates are: %s" % (test_case_id, [x.findall('name')[0].text for x in testcases])) elif len(testcases) < 1: raise QcClientException('No test cases found with ID "%s" under path "%s"' % (test_case_id, self.test_case_search_root)) else: return testcases[0].findall('id')[0].text
def _analyze_result(self, response, headers=None): """ This function could be overriden if the response might contain more than one acceptable return codes """ Log.debug("result: %d" % response.status_code) Log.log(5, "response text: %s" % response.text) Log.log(5, "response headers: %s" % response.headers) if headers: Log.log(5, "headers: %s" % headers) if response.status_code == self.result: return self._parse_result_parameters(response) raise RestQueryException("Status code mismatch, expected %d, got %d" % (self.result, response.status_code), response.text)
def FindTestSetFolder(self, path, folder_name): ''' Finds test set folder by the folder name More details, See: <https://qc-api.inside.nsn.com/QcXmlWebSite/> Excpected return value from API <return_message> <status>True</status> <return_value> <folder_tree_node> <folder> <name>lidong_test</name> <path>Root\lidong_test</path> </folder> <folder_tree_node> <folder> <name>testfolder_cj_01</name> <path>Root\lidong_test\testfolder_cj_01</path> </folder> </folder_tree_node> <folder_tree_node> <folder> <name>testfolder_cj_02</name> <path>Root\lidong_test\testfolder_cj_02</path> </folder> </folder_tree_node> </folder_tree_node> </return_value> </return_message> ''' parameters = ElementTree.Element('parameters') ElementTree.SubElement(parameters, 'path').text = path ElementTree.SubElement(parameters, 'include_attachments').text = 'false' ElementTree.SubElement(parameters, 'recursive').text = 'false' input_xml = self._constructInputXML('GetTestSetFolderTree', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("GetTestSetFolderTree", input_xml) element = ElementTree.XML(returnXML) nodes = element.findall('return_value')[0].findall('folder_tree_node') # parent = nodes[0].findall('folder')[0].findall('name')[0].text childrens = [n[0].findall('name')[0].text for n in nodes[0].findall('folder_tree_node')] return (folder_name in childrens)
def FindTestSet(self, path, test_set_name): ''' Finds test set id by the test set name ''' parameters = ElementTree.Element('parameters') ElementTree.SubElement(parameters, 'path').text = path ElementTree.SubElement(parameters, 'include_attachments').text = 'false' ElementTree.SubElement(parameters, 'include_test_set_details').text = 'false' ElementTree.SubElement(parameters, 'recursive').text = 'false' fields = ElementTree.SubElement(parameters, 'fields') ElementTree.SubElement(fields, 'CY_CYCLE').text = '"%s"' % test_set_name input_xml = self._constructInputXML('GetTestSetList', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("GetTestSetList", input_xml) element = ElementTree.XML(returnXML) testsets = element.findall('return_value')[0].findall('test_set') if len(testsets) > 1: raise QcClientException("Test set %s is not unique. Candidates are: %s" % (test_set_name, [x.findall('name')[0].text for x in testsets])) elif len(testsets) < 1: raise QcClientException("No test sets found with name %s" % (test_set_name)) else: return testsets[0].findall('id')[0].text
def AddTestSetFolder(self, path, folder_name): ''' Add test set folder by path and folder name More details, See: <https://qc-api.inside.nsn.com/QcXmlWebSite/> Excpected return value from API <return_message> <status>True</status> <return_value> <id>62</id> </return_value> </return_message> ''' parameters = ElementTree.Element('parameters') test_set_folder = ElementTree.SubElement(parameters, 'folder') ElementTree.SubElement(test_set_folder, 'attachments').text = '' ElementTree.SubElement(test_set_folder, 'name').text = folder_name ElementTree.SubElement(test_set_folder, 'path').text = path input_xml = self._constructInputXML('AddTestSetFolder', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("AddTestSetFolder", input_xml) element = ElementTree.XML(returnXML) return element.findall('return_value')[0].findall('id')[0].text
def debug_print_fields(entity, title=None): if title: Log.debug(title) else: Log.debug("----------------------------------") for field in entity['Fields']: for value in field['values']: if 'value' in value: Log.debug("%-20s: %s" % (field['Name'], value['value']))
def resolveTestFolderQcId(self, folder_path): folder_path_dirs = re.split(r'[/\\]', folder_path) parent_id = 0 parent_name = "" for d in folder_path_dirs: path_frag = 'test-folders?' + self.equals_query({ 'parent-id': parent_id, 'name': d }) response = self.query(path_frag) if int(response['TotalResults']) < 1: raise QcClientException( 'No matches found with dir "%s" and parent \"%s\"' % (d, parent_name)) if int(response['TotalResults']) > 1: raise QcClientException( 'More than one match found with dir "%s" and parent \"%s\"' % (d, parent_name)) entity = response['entities'][0] self.debug_print_fields(entity) Log.debug('dir \"%s\" found' % (d)) parent_id = self.get_field_value(entity, 'id') parent_name = self.get_field_value(entity, 'name') return self.get_field_value(entity, 'id')
def AddRunToTestSet(self, test_case_id, test_set_id, result, release=None, build=None, set_date=True): ''' See https://sharenet-ims.inside.nsn.com/livelink/livelink/Download/392499262 ''' parameters = ElementTree.Element('parameters') ElementTree.SubElement(parameters, 'test_set_id').text = test_set_id runs = ElementTree.SubElement(parameters, 'runs') run = ElementTree.SubElement(runs, 'run') ElementTree.SubElement(run, 'attachments') fields = ElementTree.SubElement(run, 'fields') ElementTree.SubElement(fields, 'RN_RUN_NAME').text = 'CloudTAF %s' % time.ctime() if set_date: ElementTree.SubElement(fields, 'RN_EXECUTION_DATE').text = time.strftime("%Y-%m-%d", time.gmtime()) ElementTree.SubElement(fields, 'RN_EXECUTION_TIME').text = time.strftime("%H:%M:%S", time.gmtime()) if release: ElementTree.SubElement(fields, 'RN_USER_03').text = release if build: ElementTree.SubElement(fields, 'RN_USER_01').text = build ElementTree.SubElement(fields, 'RN_TESTCYCL_ID').text = test_case_id ElementTree.SubElement(fields, 'RN_STATUS').text = result run = ElementTree.SubElement(run, 'steps') input_xml = self._constructInputXML('AddTestSetRuns', parameters) Log.debug(self._removePassword(input_xml)) self._QcXmlApiCall("AddTestSetRuns", input_xml)
def GetTestSetInstances(self, test_set_id): ''' Get a list of test case IDs that have already been added in this test set. Return value from GetTestSet api call is very long. We need to check if the test instance corresponding the test case is already added, so the essentials are: <return_message> <status>True</status> <return_value> <test_set> <id>1207</id> <name>QC Integration Test Test Set</name> <fields> ... </fields> <test_instance> <id>63</id> <name>[7]Example TC</name> <fields> ... <TC_TEST_ID label="Test" type="number" size="10">1</TC_TEST_ID> ... </fields> </test_instance> ... </test_set> </return_value> </return_message> ''' parameters = ElementTree.Element('parameters') ElementTree.SubElement(parameters, 'include_attachments').text = 'false' ElementTree.SubElement(parameters, 'include_test_cases').text = 'false' set_fields = ElementTree.SubElement(parameters, 'testset_fields') ElementTree.SubElement(set_fields, 'CY_CYCLE_ID').text = 'CY_CYCLE_ID' instance_fields = ElementTree.SubElement(parameters, 'testinstance_fields') ElementTree.SubElement(instance_fields, 'TC_TEST_ID').text = 'TC_TEST_ID' ElementTree.SubElement(parameters, 'id').text = test_set_id input_xml = self._constructInputXML('GetTestSetByID', parameters) Log.debug(self._removePassword(input_xml)) returnXML = self._QcXmlApiCall("GetTestSetByID", input_xml) Log.debug(returnXML) element = ElementTree.XML(returnXML) test_instances = element.findall('return_value')[0].findall('test_set')[0].findall('test_instance') instances_for_cases = dict() for i in test_instances: instance_qcid = i.findall('id')[0].text test_qcid = i.findall('fields')[0].findall('TC_TEST_ID')[0].text Log.debug('Test Case "%s" has a Test Instance "%s" in this test set' % (test_qcid, instance_qcid)) instances_for_cases[test_qcid] = instance_qcid return instances_for_cases
def main(self, args=None): args = QcUtilsParser().parse_args(args) Log.set_logging(args.verbosity) Log.debug('args:%s' % (args)) try: command = args.cmd() command.initialize_client() command.run(args) self._exit(0) except QcUtilsDetailedException as e: Log.critical('\n\n----- Start error trace -----\n%s\n----- End error trace -----\n' % e.detail) Log.critical('Error: "%s"' % e) Log.critical('See stack trace above for more details') self._exit(1) except QcUtilsException as e: Log.critical('Error: "%s"' % e) self._exit(1)
def _send_request(self, url, headers, payload, proxies): print_payload = self._remove_xml_password(payload) Log.log(5, "payload: %s" % print_payload) Log.debug("url: %s" % url) Log.log(5, "headers: %s" % headers) return self._analyze_result(self.method(url, data=payload, headers=headers, proxies=proxies), headers=headers)
def test_case_search_root(self): Log.info('Password not given in configuration %s' % self.config_path) tc_root = self.config.get('test_case_search_root') return re.sub('&', '&', tc_root)
def print_results(results): Log.debug("Found the following test results to report") for r in results: Log.debug('%s (%s): %s' % (r['name'], r['test_case_id'], r['status']))
class RobotToQcReporter(QcXmlHandler): def __init__(self): super(RobotToQcReporter, self).__init__() self.results = None def run(self, args): self.results = RobotResultParser(args.report).get_results() self._report(args.testset, args.testset_id, args.release, args.build) @staticmethod def _split_testset_argument(testset_input): Log.debug("Parsing test set '%s'" % testset_input) match = re.search(r'\\', testset_input) if not match: raise QcReporterException( "Test set path must look like 'Root\\Folder\\...\\Test set name' " "and it must contain at least one path separator character ('\\')" ) testset_components = testset_input.rsplit('\\', 1) testset_path = testset_components[0] testset_name = testset_components[1] Log.debug("Looking for test set %s in path %s" % (testset_name, testset_path)) return (testset_path, testset_name) def _report(self, testset_opt, testset_id_opt, release=None, build=None): Log.critical("Trying to connect QualityCenter...") # Log.info('Connected to QCXML version %s' % client.GetVersion()) Log.critical("Validating data...") if testset_id_opt: testset_qcid = testset_id_opt else: testset_path, testset_name = self._split_testset_argument( testset_opt) try: testset_qcid = self.client.FindTestSet(testset_path, testset_name) except: testset_qcid = self.client.AddTestSet(testset_path, testset_name) already_added_cases = self.client.GetTestSetInstances(testset_qcid) Log.info( 'QC internal ID for the test set is "%s". It contains instances for %s test cases' % (testset_qcid, len(already_added_cases))) results = [] for result in self.results: # Extend result dicts with QC internal IDs for test cases try: test_case_id = result['test_case_id'].split()[0] qcid = self.client.FindTestCase(test_case_id) Log.info('QC internal ID for test case "%s" is "%s"' % (test_case_id, qcid)) result['qcid'] = qcid qcstatus = "Passed" if result['status'] == "PASS" else "Failed" result['qcstatus'] = qcstatus results.append(result) except Exception, errmsg: Log.info("test case id <%s> error: %s" % (test_case_id, errmsg)) Log.critical("Reporting results...") for r in results: Log.info('Reporting "%s" ("%s", QC: %s) as "%s"' % (r['name'], r['test_case_id'], r['qcid'], r['qcstatus'])) if r['qcid'] in already_added_cases: instance_qcid = already_added_cases[r['qcid']] else: instance_qcid = self.client.AddTestToTestSet( r['qcid'], testset_qcid) self.client.AddRunToTestSet(instance_qcid, testset_qcid, r['qcstatus'], release, build) Log.info("Source result [%s] -> uploaded result [%s]" % (self.results.__len__(), results.__len__())) Log.critical("Reporting completed")