robotframework-robotlog2rqm 1.2.4__py3-none-any.whl → 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
RobotLog2RQM/CRQM.py CHANGED
@@ -15,7 +15,7 @@
15
15
  #
16
16
  # File: CRQM.py
17
17
  #
18
- # Initialy created by Tran Duy Ngoan(RBVH/ECM11) / January 2021
18
+ # Initially created by Tran Duy Ngoan(RBVH/ECM11) / January 2021
19
19
  #
20
20
  # This is CRQMClient class which is used to interact with RQM via RQM REST APIs
21
21
  #
@@ -78,6 +78,14 @@ Parse xml object from file.
78
78
  exit(1)
79
79
  return oTree
80
80
 
81
+ class Identifier:
82
+ """
83
+ Identifier class used to identify RQM resource with name and id
84
+ """
85
+ def __init__(self, name='', id=None):
86
+ self.name = name
87
+ self.id = id
88
+
81
89
  #
82
90
  # IBM Rational Quality Manager
83
91
  #
@@ -87,7 +95,7 @@ class CRQMClient():
87
95
  CRQMClient class uses RQM REST APIs to get, create and update resources
88
96
  (testplan, testcase, test result, ...) on RQM - Rational Quality Manager
89
97
 
90
- Resoure type mapping:
98
+ Resource type mapping:
91
99
 
92
100
  * buildrecord: Build Record
93
101
  * configuration: Test Environment
@@ -131,6 +139,17 @@ Resoure type mapping:
131
139
  'ns21' : "http://www.w3.org/1999/XSL/Transform"
132
140
  }
133
141
 
142
+ # define the convention for naming new RQM resource
143
+ SUPPORTED_PLACEHOLDER = ["testplan", "build", "environment", "testsuite", "testcase"]
144
+ NAMING_CONVENTION = {
145
+ "testcase" : "{testcase}",
146
+ "tcer" : "TCER: {testcase}",
147
+ "testresult" : "Execution result: {testcase}",
148
+ "testsuite" : "{testsuite}",
149
+ "tser" : "TSER: {testsuite}",
150
+ "suiteresult" : "Testsuite result: {testsuite}"
151
+ }
152
+
134
153
  def __init__(self, user, password, project, host):
135
154
  """
136
155
  Constructor of class ``CRQMClient``.
@@ -175,7 +194,7 @@ Constructor of class ``CRQMClient``.
175
194
  'OSLC-Core-Version' : '2.0'
176
195
  }
177
196
 
178
- # Templates location which is uesd for importing
197
+ # Templates location which is used for importing
179
198
  self.templatesDir = os.path.join(os.path.dirname(__file__),'RQM_templates')
180
199
 
181
200
  # Data for mapping and linking
@@ -190,12 +209,17 @@ Constructor of class ``CRQMClient``.
190
209
  self.lEndTimes = list()
191
210
 
192
211
  # RQM configuration info
193
- self.testplan = None
194
- self.build = None
195
- self.configuration = None
196
212
  self.createmissing = None
197
213
  self.updatetestcase= None
198
- self.testsuite = None
214
+ self.testplan = Identifier()
215
+ self.build = Identifier()
216
+ self.configuration = Identifier()
217
+ self.testsuite = Identifier()
218
+ self.stream = Identifier()
219
+ self.baseline = Identifier()
220
+
221
+ # Naming convention
222
+ self.naming_convention = None
199
223
 
200
224
  def login(self):
201
225
  """
@@ -285,7 +309,8 @@ Disconnect from RQM.
285
309
  self.session.close()
286
310
 
287
311
  def config(self, plan_id, build_name=None, config_name=None,
288
- createmissing=False, updatetestcase=False,suite_id=None):
312
+ createmissing=False, updatetestcase=False, suite_id=None,
313
+ stream=None, baseline=None, naming_convention=None):
289
314
  """
290
315
  Configure RQMClient with testplan ID, build, configuration, createmissing, ...
291
316
 
@@ -338,15 +363,61 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
338
363
  (*no returns*)
339
364
  """
340
365
  try:
366
+ self.naming_convention = self.NAMING_CONVENTION
367
+ if naming_convention:
368
+ self.naming_convention.update(naming_convention)
369
+
341
370
  self.createmissing = createmissing
342
371
  self.updatetestcase = updatetestcase
343
- self.testsuite = suite_id
344
- self.testplan = plan_id
372
+ self.testplan.id = plan_id
373
+ self.testsuite.id = suite_id
374
+
375
+ # Add Configuration-Context header information due to given stream or baseline
376
+ if stream:
377
+ res = self.getAllByResource('stream')
378
+ if res['success']:
379
+ dStreams = res['data']
380
+ bFoundStream = False
381
+ for stream_id, stream_name in dStreams.items():
382
+ if stream_name == stream:
383
+ self.stream.id = stream_id
384
+ self.stream.name = stream_name
385
+ bFoundStream = True
386
+ self.headers['Configuration-Context'] = stream_id
387
+ self.session.headers = self.headers
388
+ break
389
+
390
+ if not bFoundStream:
391
+ raise Exception(f"Cannot found given stream '{stream}'")
392
+ else:
393
+ raise Exception("Get all streams failed. Reason: %s"%res['message'])
394
+ elif baseline:
395
+ res = self.getAllByResource('baseline')
396
+ if res['success']:
397
+ dBaselines = res['data']
398
+ bFoundBaseline = False
399
+ for baseline_id, baseline_name in dBaselines.items():
400
+ if baseline_name == baseline:
401
+ self.baseline.id = baseline_id
402
+ self.baseline.name = baseline_name
403
+ bFoundBaseline = True
404
+ self.headers['Configuration-Context'] = baseline_id
405
+ self.session.headers = self.headers
406
+ break
407
+
408
+ if not bFoundBaseline:
409
+ raise Exception(f"Cannot found given baseline '{baseline}'")
410
+ else:
411
+ raise Exception("Get all baselines failed. Reason: %s"%res['message'])
412
+
345
413
  # Verify testplan ID
346
414
  res_plan = self.getResourceByID('testplan', plan_id)
347
415
  if res_plan.status_code != 200:
348
416
  raise Exception('Testplan with ID %s is not existing!'%str(plan_id))
349
417
 
418
+ oTestplan = get_xml_tree(BytesIO(str(res_plan.text).encode()), bdtd_validation=False)
419
+ self.testplan.name = oTestplan.find('ns4:title', oTestplan.getroot().nsmap).text
420
+
350
421
  # Verify and create build version if required
351
422
  if build_name != None:
352
423
  if build_name == '':
@@ -354,7 +425,8 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
354
425
  self.getAllBuildRecords()
355
426
  res_build = self.createBuildRecord(build_name)
356
427
  if res_build['success'] or res_build['status_code'] == "303":
357
- self.build = res_build['id']
428
+ self.build.id = res_build['id']
429
+ self.build.name = build_name
358
430
  else:
359
431
  raise Exception("Cannot create build '%s': %s"%
360
432
  (build_name, res_build['message']))
@@ -366,13 +438,23 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
366
438
  self.getAllConfigurations()
367
439
  res_conf = self.createConfiguration(config_name)
368
440
  if res_conf['success'] or res_conf['status_code'] == "303":
369
- self.configuration = res_conf['id']
441
+ self.configuration.id = res_conf['id']
442
+ self.configuration.name = config_name
370
443
  else:
371
444
  raise Exception("Cannot create configuration '%s': %s"%
372
445
  (config_name, res_conf['message']))
373
446
 
447
+ # Verify testsuite if given
448
+ if (suite_id != None) and (suite_id != "new"):
449
+ res_suite = self.getResourceByID('testsuite', suite_id)
450
+ if res_suite.status_code != 200:
451
+ raise Exception('Testsuite with ID %s is not existing!'%str(suite_id))
452
+ oTestsuite = get_xml_tree(BytesIO(str(res_suite.text).encode()), bdtd_validation=False)
453
+ self.testsuite.name = oTestsuite.find('ns4:title', oTestsuite.getroot().nsmap).text
454
+
374
455
  # get all team-areas for testcase template
375
456
  self.getAllTeamAreas()
457
+
376
458
 
377
459
  except Exception as error:
378
460
  raise Exception('Configure RQMClient failed: %s'%error)
@@ -402,7 +484,7 @@ Return interaction URL of provided userID
402
484
 
403
485
  def integrationURL(self, resourceType, id=None, forceinternalID=False):
404
486
  """
405
- Return interaction URL of provided reource and ID.
487
+ Return interaction URL of provided resource and ID.
406
488
  The provided ID can be internalID (contains only digits) or externalID.
407
489
 
408
490
  **Arguments:**
@@ -434,7 +516,7 @@ The provided ID can be internalID (contains only digits) or externalID.
434
516
 
435
517
  / *Type*: str /
436
518
 
437
- The interaction URL of provided reource and ID.
519
+ The interaction URL of provided resource and ID.
438
520
  """
439
521
  integrationURL = self.host + "/qm/service/com.ibm.rqm.integration.service.IIntegrationService/resources/" + \
440
522
  self.projectID + '/' + resourceType
@@ -486,7 +568,7 @@ Note:
486
568
  raise Exception("Cannot get ID from response. Reason: %s"%str(error))
487
569
  return resultId
488
570
 
489
- def webIDfromGeneratedID(self, resourrceType, generateID):
571
+ def webIDfromGeneratedID(self, resourceType, generateID):
490
572
  """
491
573
  Return web ID (ns2:webId) from generate ID by get resource data from RQM.
492
574
 
@@ -497,7 +579,7 @@ Note:
497
579
 
498
580
  **Arguments:**
499
581
 
500
- * ``resourrceType``
582
+ * ``resourceType``
501
583
 
502
584
  / *Condition*: required / *Type*: str /
503
585
 
@@ -531,8 +613,8 @@ Note:
531
613
  'testscript',
532
614
  'testsuite',
533
615
  'testsuitelog']
534
- if resourrceType in lSupportedResources:
535
- resResource = self.getResourceByID(resourrceType, generateID)
616
+ if resourceType in lSupportedResources:
617
+ resResource = self.getResourceByID(resourceType, generateID)
536
618
  if resResource.status_code == 200:
537
619
  oResource = get_xml_tree(BytesIO(str(resResource.text).encode()),
538
620
  bdtd_validation=False)
@@ -543,6 +625,61 @@ Note:
543
625
  raise Exception("Cannot get web ID of generated testcase!")
544
626
  return webID
545
627
 
628
+ def __genResourceName(self, resource, name):
629
+ """
630
+ Return the name for given resource bases on the naming convention
631
+
632
+ **Arguments:**
633
+
634
+ * ``resource``
635
+
636
+ / *Condition*: required / *Type*: str /
637
+
638
+ The RQM resource type.
639
+
640
+ * ``name``
641
+
642
+ / *Condition*: required / *Type*: str /
643
+
644
+ Relevant resource name.
645
+
646
+ **Returns:**
647
+
648
+ * ``resourceName``
649
+
650
+ / *Type*: str /
651
+
652
+ Resource name after replacing bases on naming convention.
653
+ """
654
+ dPlaceHolders = {
655
+ "{testplan}": self.testplan.name
656
+ }
657
+ testcaseRelevant = ["testcase", "tcer", "testresult"]
658
+ testsuiteRelevant = ["testsuite", "tser", "suiteresult"]
659
+
660
+ # Define scope of place holders
661
+ if resource in testcaseRelevant:
662
+ dPlaceHolders.update({
663
+ "{build}": self.build.name,
664
+ "{environment}": self.configuration.name,
665
+ "{testsuite}": self.testsuite.name,
666
+ "{testcase}": name
667
+ })
668
+ elif resource in testsuiteRelevant:
669
+ dPlaceHolders.update({
670
+ "{build}": self.build.name,
671
+ "{environment}": self.configuration.name,
672
+ "{testsuite}": name,
673
+ })
674
+
675
+ try:
676
+ resourceName = self.naming_convention[resource]
677
+ for placeHolder, val in dPlaceHolders.items():
678
+ resourceName = resourceName.replace(placeHolder, val)
679
+ return resourceName
680
+ except:
681
+ raise Exception(f"Failed to generate name for {resource} '{name}'")
682
+
546
683
  #
547
684
  # Methods to get resources
548
685
  #
@@ -553,7 +690,7 @@ Return data of provided resource and ID by GET method
553
690
 
554
691
  **Arguments:**
555
692
 
556
- * ``resourrceType``
693
+ * ``resourceType``
557
694
 
558
695
  / *Condition*: required / *Type*: str /
559
696
 
@@ -583,7 +720,7 @@ Return all entries (in all pages) of provided resource by GET method.
583
720
 
584
721
  **Arguments:**
585
722
 
586
- * ``resourrceType``
723
+ * ``resourceType``
587
724
 
588
725
  / *Condition*: required / *Type*: str /
589
726
 
@@ -845,7 +982,7 @@ Return testcase template from provided information.
845
982
  root = oTree.getroot()
846
983
  nsmap = root.nsmap
847
984
  # prepare required data for template
848
- testcaseTittle = testcaseName
985
+ testcaseTittle = self.__genResourceName('testcase', testcaseName)
849
986
 
850
987
  # find nodes to change data
851
988
  oTittle = oTree.find(f'{{{self.NAMESPACES["ns3"]}}}title')
@@ -958,7 +1095,8 @@ Return testcase execution record template from provided information.
958
1095
  root = oTree.getroot()
959
1096
  nsmap = root.nsmap
960
1097
  # prepare required data for template
961
- TCERTittle = 'TCER: '+testcaseName
1098
+ TCERTittle = self.__genResourceName('tcer', testcaseName)
1099
+
962
1100
 
963
1101
  # Check tcid is internalid or externalid
964
1102
  testcaseURL = self.integrationURL('testcase', testcaseID)
@@ -1104,7 +1242,7 @@ Return testcase execution result template from provided information.
1104
1242
  nsmap = root.nsmap
1105
1243
  # prepare required data for template
1106
1244
  prefixState = 'com.ibm.rqm.execution.common.state.'
1107
- resultTittle = 'Execution result: '+testcaseName
1245
+ resultTittle = self.__genResourceName('testresult', testcaseName)
1108
1246
  testcaseURL = self.integrationURL('testcase', testcaseID)
1109
1247
  testplanURL = self.integrationURL('testplan', testplanID)
1110
1248
  TCERURL = self.integrationURL('executionworkitem', TCERID)
@@ -1287,7 +1425,7 @@ Return testsuite execution record (TSER) template from provided configuration na
1287
1425
  oTree = get_xml_tree(sTemplatePath, bdtd_validation=False)
1288
1426
  root = oTree.getroot()
1289
1427
  # prepare required data for template
1290
- TSERTittle = 'TSER: ' + testsuiteName
1428
+ TSERTittle = self.__genResourceName('tser', testsuiteName)
1291
1429
  testsuiteURL = self.integrationURL('testsuite', testsuiteID)
1292
1430
  testplanURL = self.integrationURL('testplan', testplanID)
1293
1431
  testerURL = self.userURL(self.userID)
@@ -1323,7 +1461,7 @@ Return testsuite execution record (TSER) template from provided configuration na
1323
1461
  return sTSxml
1324
1462
 
1325
1463
  def createTestsuiteResultTemplate(self, testsuiteID, testsuiteName, TSERID,
1326
- lTCER, lTCResults, startTime='',
1464
+ lTCER, lTCResults, resultState, startTime='',
1327
1465
  endTime='', duration='', sOwnerID=''):
1328
1466
  """
1329
1467
  Return testsuite execution result template from provided configuration name.
@@ -1395,9 +1533,10 @@ Return testsuite execution result template from provided configuration name.
1395
1533
  sTSResultxml = ''
1396
1534
  sTemplatePath = os.path.join(self.templatesDir, 'testsuitelog.xml')
1397
1535
  oTree = get_xml_tree(sTemplatePath, bdtd_validation=False)
1536
+ prefixState = 'com.ibm.rqm.execution.common.state.'
1398
1537
 
1399
1538
  # prepare required data for template
1400
- resultTittle = 'Testsuite result: ' + testsuiteName
1539
+ resultTittle = self.__genResourceName('suiteresult', testsuiteName)
1401
1540
  testsuiteURL = self.integrationURL('testsuite', testsuiteID)
1402
1541
  TSERURL = self.integrationURL('suiteexecutionrecord', TSERID)
1403
1542
  testerURL = self.userURL(self.userID)
@@ -1438,6 +1577,11 @@ Return testsuite execution result template from provided configuration name.
1438
1577
  oStarttime.text = str(startTime).replace(' ', 'T')
1439
1578
  oEndtime.text = str(endTime).replace(' ', 'T')
1440
1579
  oTotalRunTime.text = str(duration)
1580
+ # set default RQM state as inconclusive
1581
+ oState.text = prefixState + 'inconclusive'
1582
+ if resultState.lower() in self.RESULT_STATES:
1583
+ oState.text = prefixState +resultState.lower()
1584
+
1441
1585
  for idx, sTCER in enumerate(lTCER):
1442
1586
  sTCERURL = self.integrationURL('executionworkitem', sTCER)
1443
1587
  oSuiteElem = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/tsl/v0.1/}suiteelement', nsmap=nsmap)
@@ -1459,6 +1603,79 @@ Return testsuite execution result template from provided configuration name.
1459
1603
  sTSResultxml = etree.tostring(oTree)
1460
1604
  return sTSResultxml
1461
1605
 
1606
+ def createTestsuiteTemplate(self, testsuiteName, sDescription='', sOwnerID='', sTStemplate=None):
1607
+ """
1608
+ Return testcase template from provided information.
1609
+
1610
+ **Arguments:**
1611
+
1612
+ * ``testsuiteName``
1613
+
1614
+ / *Condition*: required / *Type*: str /
1615
+
1616
+ Testsuite name.
1617
+
1618
+ * ``sDescription``
1619
+
1620
+ / *Condition*: optional / *Type*: str / *Default*: '' /
1621
+
1622
+ Testsuite description.
1623
+
1624
+ * ``sOwnerID``
1625
+
1626
+ / *Condition*: optional / *Type*: str / *Default*: '' /
1627
+
1628
+ User ID of testsuite owner.
1629
+
1630
+ * ``sTStemplate``
1631
+
1632
+ / *Condition*: optional / *Type*: str / *Default*: None /
1633
+
1634
+ Existing testsuite template as xml string.
1635
+
1636
+ If not provided, template file under `RQM_templates` is used as default.
1637
+
1638
+ **Returns:**
1639
+
1640
+ * ``sTSxml``
1641
+
1642
+ / *Type*: str /
1643
+
1644
+ The xml testsuite template as string.
1645
+ """
1646
+ sTSxml = ''
1647
+ if not sTStemplate:
1648
+ sTemplatePath = os.path.join(self.templatesDir ,'testsuite.xml')
1649
+ oTree = get_xml_tree(sTemplatePath, bdtd_validation=False)
1650
+ else:
1651
+ oTree = get_xml_tree(BytesIO(sTStemplate.encode()),bdtd_validation=False)
1652
+
1653
+ root = oTree.getroot()
1654
+ nsmap = root.nsmap
1655
+
1656
+ # prepare required data for template
1657
+ testerURL = self.userURL(self.userID)
1658
+
1659
+ # find nodes to change data
1660
+ oTittle = oTree.find('ns4:title', nsmap)
1661
+ oDescription = oTree.find('ns4:description', nsmap)
1662
+ oOwner = oTree.find('ns6:owner', nsmap)
1663
+
1664
+ # change nodes's data
1665
+ oTittle.text = self.__genResourceName('testsuite', testsuiteName)
1666
+ oDescription.text = sDescription
1667
+
1668
+ # Incase not specify owner in template or input data, set it as provided user in cli
1669
+ if sOwnerID:
1670
+ oOwner.text = sOwnerID
1671
+ oOwner.attrib['{%s}resource' % nsmap['ns1']] = self.userURL(sOwnerID)
1672
+ elif not oOwner.text:
1673
+ oOwner.text = self.userID
1674
+ oOwner.attrib['{%s}resource' % nsmap['ns1']] = testerURL
1675
+
1676
+ # return xml template as string
1677
+ sTSxml = etree.tostring(oTree)
1678
+ return sTSxml
1462
1679
  #
1463
1680
  # Methods to create RQM resources
1464
1681
  #
@@ -1643,7 +1860,6 @@ Create new configuration - test environment.
1643
1860
  """
1644
1861
  returnObj = {'success' : False, 'id': None, 'message': '', 'status_code': ''}
1645
1862
  # check existing build record in this executioon
1646
- sConfID = ''
1647
1863
  if (sConfigurationName not in self.dConfiguation.values()) or forceCreate:
1648
1864
  sConfTemplate = self.createConfigurationTemplate(sConfigurationName)
1649
1865
  returnObj = self.createResource('configuration', sConfTemplate)
@@ -1827,3 +2043,66 @@ Link list of test cases to provided testsuite ID
1827
2043
  else:
1828
2044
  returnObj['message'] = "No testcase for linking."
1829
2045
  return returnObj
2046
+
2047
+ def addTestsuite2Testplan(self, testplanID, testsuiteID=None):
2048
+ """
2049
+ Add testsuite ID to provided testplan ID
2050
+
2051
+ **Arguments:**
2052
+
2053
+ * ``testplanID``
2054
+
2055
+ / *Condition*: required / *Type*: str /
2056
+
2057
+ Testplan ID to link given testsuite ID.
2058
+
2059
+ * ``testsuiteID``
2060
+
2061
+ / *Condition*: optional / *Type*: str / *Default*: None /
2062
+
2063
+ Testsuite to be linked with given testplan.
2064
+
2065
+ If not provide, `testsuite.id` value will be used as id of testsuite.
2066
+
2067
+ **Returns:**
2068
+
2069
+ * ``returnObj``
2070
+
2071
+ / *Type*: dict /
2072
+
2073
+ Response dictionary which contains status and error message.
2074
+
2075
+ Example:
2076
+
2077
+ .. code:: python
2078
+
2079
+ {
2080
+ 'success' : False,
2081
+ 'message': ''
2082
+ }
2083
+
2084
+ """
2085
+ returnObj = {'success' : False, 'message': ''}
2086
+ if testsuiteID == None:
2087
+ testsuiteID = self.testsuite.id
2088
+ if testsuiteID:
2089
+ resTestplanData = self.getResourceByID('testplan', testplanID)
2090
+ oTree = get_xml_tree(BytesIO(str(resTestplanData.text).encode()),bdtd_validation=False)
2091
+ # RQM XML response using namespace for nodes
2092
+ # use namespace mapping from root for access response XML
2093
+ root = oTree.getroot()
2094
+
2095
+ sTestsuiteURL = self.integrationURL('testsuite', testsuiteID)
2096
+ oTS = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/}testsuite', nsmap=root.nsmap)
2097
+ oTS.set('href', sTestsuiteURL)
2098
+ root.append(oTS)
2099
+
2100
+ # Update test plan data with linked testsuite and PUT to RQM
2101
+ resUpdateTestplan = self.updateResourceByID('testplan', testplanID, etree.tostring(oTree))
2102
+ if resUpdateTestplan.status_code == 200:
2103
+ returnObj['success'] = True
2104
+ else:
2105
+ returnObj['message'] = str(resUpdateTestplan.reason)
2106
+ else:
2107
+ returnObj['message'] = "No testsuite for adding."
2108
+ return returnObj
@@ -0,0 +1,27 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ns2:testsuite xmlns:ns2="http://jazz.net/xmlns/alm/qm/v0.1/"
3
+ xmlns:ns1="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4
+ xmlns:ns3="http://schema.ibm.com/vega/2008/"
5
+ xmlns:ns4="http://purl.org/dc/elements/1.1/"
6
+ xmlns:ns5="http://jazz.net/xmlns/prod/jazz/process/0.6/"
7
+ xmlns:ns6="http://jazz.net/xmlns/alm/v0.1/"
8
+ xmlns:ns7="http://purl.org/dc/terms/"
9
+ xmlns:ns8="http://jazz.net/xmlns/alm/qm/v0.1/testscript/v0.1/"
10
+ xmlns:ns9="http://jazz.net/xmlns/alm/qm/v0.1/executionworkitem/v0.1"
11
+ xmlns:ns10="http://open-services.net/ns/core#"
12
+ xmlns:ns11="http://open-services.net/ns/qm#"
13
+ xmlns:ns12="http://jazz.net/xmlns/prod/jazz/rqm/process/1.0/"
14
+ xmlns:ns13="http://www.w3.org/2002/07/owl#"
15
+ xmlns:ns14="http://jazz.net/xmlns/alm/qm/qmadapter/v0.1"
16
+ xmlns:ns15="http://jazz.net/xmlns/alm/qm/qmadapter/task/v0.1"
17
+ xmlns:ns16="http://jazz.net/xmlns/alm/qm/v0.1/executionresult/v0.1"
18
+ xmlns:ns17="http://jazz.net/xmlns/alm/qm/v0.1/catalog/v0.1"
19
+ xmlns:ns18="http://jazz.net/xmlns/alm/qm/v0.1/tsl/v0.1/"
20
+ xmlns:ns20="http://jazz.net/xmlns/alm/qm/styleinfo/v0.1/"
21
+ xmlns:ns21="http://www.w3.org/1999/XSL/Transform">
22
+ <ns4:title></ns4:title>
23
+ <ns4:description></ns4:description>
24
+ <ns4:creator></ns4:creator>
25
+ <ns6:owner></ns6:owner>
26
+ <ns2:testplan href=""/>
27
+ </ns2:testsuite>
Binary file
@@ -27,6 +27,7 @@
27
27
  #
28
28
  # ******************************************************************************
29
29
 
30
+ import json
30
31
  import re
31
32
  import argparse
32
33
  import os
@@ -64,6 +65,15 @@ DEFAULT_METADATA = {
64
65
  "team-area" : "",
65
66
  }
66
67
 
68
+ NAMING_CONVENTION_SCHEMA = {
69
+ "testcase" : str,
70
+ "tcer" : str,
71
+ "testresult" : str,
72
+ "testsuite" : str,
73
+ "tser" : str,
74
+ "suiteresult" : str
75
+ }
76
+
67
77
  #
68
78
  # Logger class
69
79
  #
@@ -158,7 +168,7 @@ Write log message to console/file output.
158
168
  return
159
169
 
160
170
  @classmethod
161
- def log_warning(cls, msg):
171
+ def log_warning(cls, msg, indent=0):
162
172
  """
163
173
  Write warning message to console/file output.
164
174
 
@@ -170,14 +180,20 @@ Write warning message to console/file output.
170
180
 
171
181
  Warning message which is written to output.
172
182
 
183
+ * ``indent``
184
+
185
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
186
+
187
+ Offset indent.
188
+
173
189
  **Returns:**
174
190
 
175
191
  (*no returns*)
176
192
  """
177
- cls.log(cls.prefix_warn+str(msg), cls.color_warn)
193
+ cls.log(cls.prefix_warn+str(msg), cls.color_warn, indent)
178
194
 
179
195
  @classmethod
180
- def log_error(cls, msg, fatal_error=False):
196
+ def log_error(cls, msg, fatal_error=False, indent=0):
181
197
  """
182
198
  Write error message to console/file output.
183
199
 
@@ -193,6 +209,12 @@ Write error message to console/file output.
193
209
 
194
210
  If set, tool will terminate after logging error message.
195
211
 
212
+ * ``indent``
213
+
214
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
215
+
216
+ Offset indent.
217
+
196
218
  **Returns:**
197
219
 
198
220
  (*no returns*)
@@ -201,7 +223,7 @@ Write error message to console/file output.
201
223
  if fatal_error:
202
224
  prefix = cls.prefix_fatalerror
203
225
 
204
- cls.log(prefix+str(msg), cls.color_error)
226
+ cls.log(prefix+str(msg), cls.color_error, indent)
205
227
  if fatal_error:
206
228
  cls.log(f"{sys.argv[0]} has been stopped!", cls.color_error)
207
229
  exit(1)
@@ -269,6 +291,108 @@ Convert time string to datetime.
269
291
  dt=datetime.datetime(tp[0],tp[1],tp[2],tp[3],tp[4],tp[5],tp[6])
270
292
  return dt
271
293
 
294
+ def process_config_file(path_file):
295
+ """
296
+ Parse and validate content of configuration file
297
+
298
+ **Arguments:**
299
+
300
+ * ``path_file``
301
+
302
+ / *Condition*: required / *Type*: str /
303
+
304
+ Path to the configuration json file.
305
+
306
+ **Returns:**
307
+
308
+ * ``dConfig``
309
+
310
+ / *Type*: dict /
311
+
312
+ Content of json file.
313
+ """
314
+ with open(path_file, encoding='utf-8') as f:
315
+ try:
316
+ dConfig = json.load(f)
317
+ except Exception as reason:
318
+ Logger.log_error(f"Cannot parse the json file '{path_file}'. Reason: {reason}",
319
+ fatal_error=True)
320
+
321
+ if not is_valid_config(dConfig, bExitOnFail=False):
322
+ Logger.log_error(f"Error in naming configuration file '{path_file}'.",
323
+ fatal_error=True)
324
+ return dConfig
325
+
326
+ def is_valid_config(dConfig, dSchema=NAMING_CONVENTION_SCHEMA, bExitOnFail=True):
327
+ """
328
+ Validate the json configuration base on given schema.
329
+
330
+ Default schema supports below information:
331
+
332
+ .. code:: python
333
+
334
+ NAMING_CONVENTION_SCHEMA = {
335
+ "testcase" : str,
336
+ "tcer" : str,
337
+ "testresult" : str,
338
+ "testsuite" : str,
339
+ "tser" : str,
340
+ "suiteresult" : str
341
+ }
342
+
343
+ **Arguments:**
344
+
345
+ * ``dConfig``
346
+
347
+ / *Condition*: required / *Type*: dict /
348
+
349
+ Json configuration object to be verified.
350
+
351
+ * ``dSchema``
352
+
353
+ / *Condition*: optional / *Type*: dict / *Default*: CONFIG_SCHEMA /
354
+
355
+ Schema for the validation.
356
+
357
+ * ``bExitOnFail``
358
+
359
+ / *Condition*: optional / *Type*: bool / *Default*: True /
360
+
361
+ If True, exit tool in case the validation is fail.
362
+
363
+ **Returns:**
364
+
365
+ * ``bValid``
366
+
367
+ / *Type*: bool /
368
+
369
+ True if the given json configuration data is valid.
370
+ """
371
+ bValid = True
372
+ for key in dConfig:
373
+ if key in dSchema.keys():
374
+ if type(dConfig[key]) != dSchema[key]:
375
+ bValid = False
376
+ Logger.log_error(f"Value of '{key}' has wrong type '{type(dConfig[key])}' in configuration json file.",
377
+ fatal_error=bExitOnFail)
378
+ break
379
+
380
+ # TESTCASE_NAME is not available for non-testcase relevant resources
381
+ # TESTSUITE_NAME is not available for `buildrecord` and `configuration` resources
382
+ # Warning user for using wrong place holders
383
+ oMatch = re.search(".*\{(.*)\}.*", dConfig[key])
384
+ if oMatch:
385
+ if oMatch.group(1) not in CRQMClient.SUPPORTED_PLACEHOLDER:
386
+ Logger.log_warning(f"Place holder '{{{oMatch.group(1)}}}' is not supported, it will not be replaced when generating {key} resource")
387
+
388
+ else:
389
+ bValid = False
390
+ Logger.log_error(f"resource '{key}' is not supported in naming conventions json file.",
391
+ fatal_error=bExitOnFail)
392
+ break
393
+
394
+ return bValid
395
+
272
396
  def __process_commandline():
273
397
  """
274
398
  Process provided argument(s) from command line.
@@ -281,9 +405,14 @@ Avalable arguments in command line:
281
405
  - `user` : user for RQM login.
282
406
  - `password` : user password for RQM login.
283
407
  - `testplan` : RQM testplan ID.
408
+ - `--testsuite` : RQM testsuite ID. If value is 'new', then create a new testsuite for this execution.
284
409
  - `--recursive` : if True, then the path is searched recursively for log files to be imported.
285
410
  - `--createmissing` : if True, then all testcases without tcid are created when importing.
411
+ - `--updatetestcase` : if set, then testcase information on RQM will be updated bases on robot testfile.
412
+ - `--naming_config` : configuration json file for naming conventions when creating RQM resources.
286
413
  - `--dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
414
+ - `--stream` : project stream. Note, requires Configuration Management (CM) to be enabled for the project area.
415
+ - `--baseline` : project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.
287
416
 
288
417
  **Arguments:**
289
418
 
@@ -312,14 +441,22 @@ Avalable arguments in command line:
312
441
  cmdParser.add_argument('password', type=str, help='password for RQM login.')
313
442
  cmdParser.add_argument('testplan', type=str,
314
443
  help='testplan ID for this execution.')
444
+ cmdParser.add_argument('--testsuite', type=str,
445
+ help="testsuite ID for this execution. If 'new', then create a new testsuite for this execution.")
315
446
  cmdParser.add_argument('--recursive',action="store_true",
316
447
  help='if set, then the path is searched recursively for log files to be imported.')
317
448
  cmdParser.add_argument('--createmissing', action="store_true",
318
449
  help='if set, then all testcases without tcid are created when importing.')
319
450
  cmdParser.add_argument('--updatetestcase', action="store_true",
320
451
  help='if set, then testcase information on RQM will be updated bases on robot testfile.')
452
+ cmdParser.add_argument('--naming_config', type=str,
453
+ help='configuration json file for naming conventions when creating RQM resources.')
321
454
  cmdParser.add_argument('--dryrun',action="store_true",
322
455
  help='if set, then verify all input arguments (includes RQM authentication) and show what would be done.')
456
+ cmdParser.add_argument('--stream', type=str,
457
+ help='project stream. Note, requires Configuration Management (CM) to be enabled for the project area.')
458
+ cmdParser.add_argument('--baseline', type=str,
459
+ help='project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.')
323
460
 
324
461
  return cmdParser.parse_args()
325
462
 
@@ -395,7 +532,7 @@ Extract metadata from suite result bases on DEFAULT_METADATA.
395
532
 
396
533
  return dMetadata
397
534
 
398
- def process_suite(RQMClient, suite):
535
+ def process_suite(RQMClient, suite, log_indent=0):
399
536
  """
400
537
  Process robot suite for importing to RQM.
401
538
 
@@ -413,15 +550,21 @@ Process robot suite for importing to RQM.
413
550
 
414
551
  Robot suite object.
415
552
 
553
+ * ``log_indent``
554
+
555
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
556
+
557
+ Indent for logging message.
558
+
416
559
  **Returns:**
417
560
 
418
561
  (*no returns*)
419
562
  """
420
563
  if len(list(suite.suites)) > 0:
421
564
  for subsuite in suite.suites:
422
- process_suite(RQMClient, subsuite)
565
+ process_suite(RQMClient, subsuite, log_indent=log_indent+2)
423
566
  else:
424
- Logger.log(f"Process suite: {suite.name}")
567
+ Logger.log(f"Process suite: {suite.name}", indent=log_indent)
425
568
 
426
569
  # update missing metadata from parent suite
427
570
  if suite.parent and suite.parent.metadata:
@@ -431,9 +574,9 @@ Process robot suite for importing to RQM.
431
574
 
432
575
  if len(list(suite.tests)) > 0:
433
576
  for test in suite.tests:
434
- process_test(RQMClient, test)
577
+ process_test(RQMClient, test, log_indent=log_indent+2)
435
578
 
436
- def process_test(RQMClient, test):
579
+ def process_test(RQMClient, test, log_indent=0):
437
580
  """
438
581
  Process robot test for importing to RQM.
439
582
 
@@ -451,11 +594,17 @@ Process robot test for importing to RQM.
451
594
 
452
595
  Robot test object.
453
596
 
597
+ * ``log_indent``
598
+
599
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
600
+
601
+ Indent for logging message.
602
+
454
603
  **Returns:**
455
604
 
456
605
  (*no returns*)
457
606
  """
458
- Logger.log(f"Process test: {test.name}")
607
+ Logger.log(f"Process test: {test.name}", indent=log_indent)
459
608
 
460
609
  # Avoid create resources with dryrun
461
610
  if Logger.dryrun:
@@ -474,9 +623,9 @@ Process robot test for importing to RQM.
474
623
  _tc_cmpt = metadata_info['component']
475
624
  _tc_team = metadata_info['team-area']
476
625
  # from RQMClient
477
- _tc_testplan_id = RQMClient.testplan
478
- _tc_config_id = RQMClient.configuration
479
- _tc_build_id = RQMClient.build
626
+ _tc_testplan_id = RQMClient.testplan.id
627
+ _tc_config_id = RQMClient.configuration.id
628
+ _tc_build_id = RQMClient.build.id
480
629
  _tc_createmissing = RQMClient.createmissing
481
630
  _tc_update = RQMClient.updatetestcase
482
631
  # from robot result object
@@ -485,7 +634,7 @@ Process robot test for importing to RQM.
485
634
  try:
486
635
  _tc_result = DRESULT_MAPPING[test.status]
487
636
  except Exception:
488
- Logger.log_error(f"Invalid Robotframework result state '{test.status}' of test '{_tc_name}'.")
637
+ Logger.log_error(f"Invalid Robotframework result state '{test.status}' of test '{_tc_name}'.", indent=log_indent)
489
638
  return
490
639
  _tc_message = test.message
491
640
  _tc_start_time = convert_to_datetime(test.starttime)
@@ -509,19 +658,19 @@ Process robot test for importing to RQM.
509
658
  res = RQMClient.createResource('testcase', oTCTemplate)
510
659
  if res['success']:
511
660
  _tc_id = res['id']
512
- Logger.log(f"Create testcase '{_tc_name}' with ID '{_tc_id}' successfully!")
661
+ Logger.log(f"Create testcase '{_tc_name}' with ID '{_tc_id}' successfully!", indent=log_indent)
513
662
  RQMClient.dMappingTCID[_tc_id] = _tc_name
514
663
  else:
515
- Logger.log_error(f"Create testcase '{_tc_name}' failed. Reason: {res['message']}")
664
+ Logger.log_error(f"Create testcase '{_tc_name}' failed. Reason: {res['message']}", indent=log_indent)
516
665
  return
517
666
  else:
518
- Logger.log_error(f"There is no 'tcid' information for importing test '{_tc_name}'.")
667
+ Logger.log_error(f"There is no 'tcid' information for importing test '{_tc_name}'.", indent=log_indent)
519
668
  return
520
669
  else:
521
670
  # If more than 1 tcid are defined in [Tags], the first one is used.
522
671
  if len(lTCIDTags) > 1:
523
672
  _tc_id = lTCIDTags[0]
524
- Logger.log_warning(f"More than 1 'tcid-' tags in test '{_tc_name}', '{_tc_id}' is used.")
673
+ Logger.log_warning(f"More than 1 'tcid-' tags in test '{_tc_name}', '{_tc_id}' is used.", indent=log_indent)
525
674
 
526
675
  # If --updatetestcase is set. Test case with provided tcid will be updated on RQM:
527
676
  # Get existing resource of testcase from RQM.
@@ -538,9 +687,9 @@ Process robot test for importing to RQM.
538
687
  _tc_link,
539
688
  sTCtemplate=str(resTC.text))
540
689
  RQMClient.updateResourceByID('testcase', _tc_id, oTCTemplate)
541
- Logger.log(f"Update testcase '{_tc_name}' with ID '{_tc_id}' successfully!")
690
+ Logger.log(f"Update testcase '{_tc_name}' with ID '{_tc_id}' successfully!", indent=log_indent)
542
691
  else:
543
- Logger.log_error(f"Update testcase with ID '{_tc_id}' failed. Please check whether it is existing on RQM.")
692
+ Logger.log_error(f"Update testcase with ID '{_tc_id}' failed. Please check whether it is existing on RQM.", indent=log_indent)
544
693
  return
545
694
 
546
695
  # Create TCER:
@@ -555,11 +704,11 @@ Process robot test for importing to RQM.
555
704
  res = RQMClient.createResource('executionworkitem', oTCERTemplate)
556
705
  _tc_tcer_id = res['id']
557
706
  if res['success']:
558
- Logger.log(f"Created TCER with ID '{_tc_tcer_id}' successfully.")
707
+ Logger.log(f"Created TCER with ID '{_tc_tcer_id}' successfully.", indent=log_indent+2)
559
708
  elif (res['status_code'] == 303 or res['status_code'] == 200) and res['id'] != '':
560
- Logger.log_warning(f"TCER for testcase '{_tc_id}' and testplan '{_tc_testplan_id}' is existing with ID: '{_tc_tcer_id}'")
709
+ Logger.log_warning(f"TCER for testcase '{_tc_id}' and testplan '{_tc_testplan_id}' is existing with ID: '{_tc_tcer_id}'", indent=log_indent+2)
561
710
  else:
562
- Logger.log_error(f"Create TCER failed. Please check whether test case with ID '{_tc_id}' is existing on RQM or not. Reason: {res['message']}.")
711
+ Logger.log_error(f"Create TCER failed. Please check whether test case with ID '{_tc_id}' is existing on RQM or not. Reason: {res['message']}.", indent=log_indent+2)
563
712
  return
564
713
 
565
714
  if _tc_tcer_id not in RQMClient.lTCERIDs:
@@ -584,10 +733,10 @@ Process robot test for importing to RQM.
584
733
  _tc_team)
585
734
  res = RQMClient.createResource('executionresult', oTCResultTemplate)
586
735
  if res['success']:
587
- Logger.log(f"Create result for test '{_tc_name}' successfully!")
736
+ Logger.log(f"Create result for test '{_tc_name}' successfully!", indent=log_indent+4)
588
737
  _tc_result_id = res['id']
589
738
  else:
590
- Logger.log_error(f"Create result for test '{_tc_name}' failed. Reason: {res['message']}.")
739
+ Logger.log_error(f"Create result for test '{_tc_name}' failed. Reason: {res['message']}.", indent=log_indent+4)
591
740
  return
592
741
  if _tc_result_id not in RQMClient.lTCResultIDs:
593
742
  RQMClient.lTCResultIDs.append(_tc_result_id)
@@ -595,6 +744,10 @@ Process robot test for importing to RQM.
595
744
  # Append lTestcaseIDs (for linking testplan/testsuite)
596
745
  if _tc_id not in RQMClient.lTestcaseIDs:
597
746
  RQMClient.lTestcaseIDs.append(_tc_id)
747
+
748
+ # Collect starttime and endtime for testsuite log creation
749
+ RQMClient.lStartTimes.append(_tc_start_time)
750
+ RQMClient.lEndTimes.append(_tc_end_time)
598
751
 
599
752
  def RobotLog2RQM(args=None):
600
753
  """
@@ -622,11 +775,14 @@ Flow to import Robot results to RQM:
622
775
  * `user` : user for RQM login.
623
776
  * `password` : user password for RQM login.
624
777
  * `testplan` : RQM testplan ID.
778
+ * `testsuite` : testsuite ID for this execution. If 'new', then create a new testsuite for this execution.
625
779
  * `recursive` : if True, then the path is searched recursively for log files to be imported.
626
780
  * `createmissing` : if True, then all testcases without tcid are created when importing.
627
781
  * `updatetestcase` : if True, then testcases information on RQM will be updated bases on robot testfile.
782
+ * `naming_config` : configuration json file for naming conventions when creating RQM resources.
628
783
  * `dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
629
-
784
+ * `stream` : project stream. Note, requires Configuration Management (CM) to be enabled for the project area.
785
+ * `baseline` : project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.
630
786
  **Returns:**
631
787
 
632
788
  (*no returns*)
@@ -671,11 +827,21 @@ Flow to import Robot results to RQM:
671
827
  result = ExecutionResult(*sources)
672
828
  result.configure()
673
829
 
830
+ # verify given configuration file
831
+ dNamingConvention = None
832
+ if args.naming_config != None:
833
+ if os.path.isfile(args.naming_config):
834
+ dNamingConvention = process_config_file(args.naming_config)
835
+ else:
836
+ Logger.log_error(f"The given naming configuration file is not existing: '{args.naming_config}'",
837
+ fatal_error=True)
838
+
674
839
  # 3. Login Rational Quality Management (RQM)
675
840
  RQMClient = CRQMClient(args.user, args.password, args.project, args.host)
676
841
  try:
677
842
  bSuccess = RQMClient.login()
678
843
  if bSuccess:
844
+ Logger.log()
679
845
  Logger.log(f"Login RQM as user '{args.user}' successfully!")
680
846
  else:
681
847
  Logger.log_error("Could not login to RQM: 'Unkown reason'.")
@@ -694,14 +860,86 @@ Flow to import Robot results to RQM:
694
860
  metadata_info['version_sw'] = None
695
861
  metadata_info['project'] = None
696
862
  RQMClient.config(args.testplan, metadata_info['version_sw'],
697
- metadata_info['project'], args.createmissing, args.updatetestcase)
863
+ metadata_info['project'], args.createmissing, args.updatetestcase,
864
+ args.testsuite, stream=args.stream, baseline=args.baseline,
865
+ naming_convention=dNamingConvention)
866
+
867
+ if args.testsuite == "new":
868
+ # Create new testsuite
869
+ if not args.dryrun:
870
+ testsuite_data = RQMClient.createTestsuiteTemplate(result.suite.name, result.suite.doc)
871
+ res_testsuite = RQMClient.createResource('testsuite', testsuite_data)
872
+ else:
873
+ # for dryrun
874
+ res_testsuite = {'success': True, 'id': 1111}
875
+
876
+ if res_testsuite['success']:
877
+ _ts_id = res_testsuite['id']
878
+ Logger.log(f"Create testsuite '{result.suite.name}' with ID '{_ts_id}' successfully!")
879
+ RQMClient.testsuite.id = _ts_id
880
+ RQMClient.testsuite.name = result.suite.name
881
+ else:
882
+ Logger.log_error(f"Create testsuite '{result.suite.name}' failed. Reason: {res_testsuite['message']}", fatal_error=True)
883
+
698
884
  # Process suite for importing
699
885
  process_suite(RQMClient, result.suite)
700
886
 
701
- # Link all imported testcase ID(s) with testplan
702
- Logger.log("Linking all imported testcase ID(s) with testplan ...")
703
- RQMClient.linkListTestcase2Testplan(args.testplan)
887
+ if RQMClient.testsuite.id:
888
+ if not args.dryrun:
889
+ # Create testsuite execution record if requires
890
+ testsuite_record_data = RQMClient.createTSERTemplate(RQMClient.testsuite.id, RQMClient.testsuite.name, args.testplan, RQMClient.configuration.id)
891
+ res_TSER = RQMClient.createResource('suiteexecutionrecord', testsuite_record_data)
892
+ sTSERID = res_TSER['id']
893
+ Logger.log()
894
+ if res_TSER['success']:
895
+ Logger.log(f"Create TSER with id {sTSERID} successfully!")
896
+ elif (res_TSER['status_code'] == 303 or res_TSER['status_code'] == 200) and res_TSER['id'] != '':
897
+ ### incase executionworkitem is existing, cannot create new one
898
+ ### Use the existing ID for new result
899
+ Logger.log_warning(f"TSER for testsuite {RQMClient.testsuite.id} is existing.\nAdd this execution result to existing TSER id: {sTSERID}")
900
+ else:
901
+ Logger.log_error(f"Create TSER failed, {res_TSER['message']}")
902
+
903
+ # Create new testsuite result and link all TCERs
904
+ testsuite_result_data = RQMClient.createTestsuiteResultTemplate(RQMClient.testsuite.id,
905
+ RQMClient.testsuite.name,
906
+ sTSERID,
907
+ RQMClient.lTCERIDs,
908
+ RQMClient.lTCResultIDs,
909
+ DRESULT_MAPPING[result.suite.status]
910
+ )
911
+ res_TSLog = RQMClient.createResource('testsuitelog', testsuite_result_data)
912
+ sSuiteResultID = res_TSLog['id']
913
+ if res_TSLog['success']:
914
+ Logger.log(f"Created testsuite result with id {sSuiteResultID} successfully.", indent=2)
915
+ else:
916
+ Logger.log_error(f"Create testsuite result failed, {res_TSLog['message']}", indent=2)
917
+ else:
918
+ Logger.log(f"Create TSER")
919
+ Logger.log(f"Created testsuite result")
920
+
921
+ # Link all imported testcase ID(s) with testsuite
922
+ try:
923
+ RQMClient.linkListTestcase2Testsuite(RQMClient.testsuite.id)
924
+ Logger.log(f"Link all imported test cases with testsuite {RQMClient.testsuite.id} successfully.")
925
+ except Exception as reason:
926
+ Logger.log_error(f"Link all imported test cases with testsuite failed.\nReason: {reason}", fatal_error=True)
927
+
928
+ # Add testsuite to given testplan
929
+ try:
930
+ RQMClient.addTestsuite2Testplan(args.testplan)
931
+ Logger.log(f"Add testsuite {RQMClient.testsuite.id} to testplan {args.testplan} successfully.")
932
+ except Exception as reason:
933
+ Logger.log_error(f"Add testsuite to testplan failed.\nReason: {reason}", fatal_error=True)
704
934
 
935
+ else:
936
+ # Link all imported testcase ID(s) with testplan
937
+ try:
938
+ RQMClient.linkListTestcase2Testplan(args.testplan)
939
+ Logger.log(f"Link all imported test cases with testplan {args.testplan} successfully.")
940
+ except Exception as reason:
941
+ Logger.log_error(f"Link all imported test cases with testplan failed.\nReason: {reason}", fatal_error=True)
942
+
705
943
  # Update testcase(s) with generated ID(s)
706
944
  # Under developing
707
945
 
@@ -710,7 +948,15 @@ Flow to import Robot results to RQM:
710
948
 
711
949
  # 5. Disconnect from RQM
712
950
  RQMClient.disconnect()
713
- Logger.log("All test results have been imported to RQM successfully.!")
951
+
952
+ testcnt_msg = f"All {len(RQMClient.lTestcaseIDs)}"
953
+ extended_msg = ""
954
+ if (len(RQMClient.lTestcaseIDs) > len(RQMClient.lTCResultIDs)):
955
+ testcnt_msg = f"{len(RQMClient.lTCResultIDs)} of {len(RQMClient.lTestcaseIDs)}"
956
+ extended_msg = f" {len(RQMClient.lTestcaseIDs)-len(RQMClient.lTCResultIDs)} test results are skipped because of errors."
957
+
958
+ Logger.log()
959
+ Logger.log(f"{testcnt_msg} test results are imported to RQM successfully.{extended_msg}")
714
960
 
715
961
  if __name__=="__main__":
716
962
  RobotLog2RQM()
RobotLog2RQM/version.py CHANGED
@@ -18,6 +18,6 @@
18
18
  #
19
19
  # Version and date of RobotLog2RQM
20
20
  #
21
- VERSION = "1.2.4"
22
- VERSION_DATE = "11.06.2024"
21
+ VERSION = "1.4.1"
22
+ VERSION_DATE = "20.09.2024"
23
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.2.4
3
+ Version: 1.4.1
4
4
  Summary: Imports robot result(s) to IBM Rational Quality Manager (RQM)
5
5
  Home-page: https://github.com/test-fullautomation/robotframework-robotlog2rqm
6
6
  Author: Tran Duy Ngoan
@@ -132,33 +132,40 @@ Use below command to get tools\'s usage:
132
132
 
133
133
  The usage should be showed as below:
134
134
 
135
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--recursive]
136
- [--createmissing] [--updatetestcase] [--dryrun]
137
- resultxmlfile host project user password testplan
135
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
136
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
137
+ resultxmlfile host project user password testplan
138
138
 
139
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
139
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
140
140
  Robot Framework into an IBM Rational Quality Manager.
141
141
 
142
142
  positional arguments:
143
- resultxmlfile absolute or relative path to the xml result file
144
- or directory of result files to be imported.
145
- host RQM host url.
146
- project project on RQM.
147
- user user for RQM login.
148
- password password for RQM login.
149
- testplan testplan ID for this execution.
143
+ resultxmlfile absolute or relative path to the xml result file
144
+ or directory of result files to be imported.
145
+ host RQM host url.
146
+ project project on RQM.
147
+ user user for RQM login.
148
+ password password for RQM login.
149
+ testplan testplan ID for this execution.
150
150
 
151
151
  optional arguments:
152
- -h, --help show this help message and exit
153
- -v, --version Version of the RobotLog2RQM importer.
154
- --recursive if set, then the path is searched recursively for
155
- log files to be imported.
156
- --createmissing if set, then all testcases without tcid are created
157
- when importing.
158
- --updatetestcase if set, then testcase information on RQM will be updated
159
- bases on robot testfile.
160
- --dryrun if set, then verify all input arguments
161
- (includes RQM authentication) and show what would be done.
152
+ -h, --help show this help message and exit
153
+ -v, --version Version of the RobotLog2RQM importer.
154
+ --testsuite TESTSUITE
155
+ testsuite ID for this execution. If 'new', then create a new
156
+ testsuite for this execution.
157
+ --recursive if set, then the path is searched recursively for
158
+ log files to be imported.
159
+ --createmissing if set, then all testcases without tcid are created
160
+ when importing.
161
+ --updatetestcase if set, then testcase information on RQM will be updated
162
+ bases on robot testfile.
163
+ --dryrun if set, then verify all input arguments
164
+ (includes RQM authentication) and show what would be done.
165
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
166
+ to be enabled for the project area.
167
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
168
+ or Baselines Only to be enabled for the project area.
162
169
 
163
170
  The below command is simple usage witth all required arguments to import
164
171
  Robot Framework results into RQM:
@@ -1,19 +1,20 @@
1
- RobotLog2RQM/CRQM.py,sha256=yj5q-_Mj_cB4vpwn1G5PzAQFTc756CYB1kIN9xAAA84,54877
2
- RobotLog2RQM/RobotLog2RQM.pdf,sha256=iaFtTHrkGNTP2G80qrbubDGBT7-fHvIhFJKNAZ_4Wnc,229406
1
+ RobotLog2RQM/CRQM.py,sha256=fyuU9F2e4RCZzkxOb3bShKrWEjPcXDwzi1HGbUmWjxQ,64123
2
+ RobotLog2RQM/RobotLog2RQM.pdf,sha256=sRhG1vvBX00YwBR2V9pKhMBpJH5AMniLHtPf9rQdK6Q,336238
3
3
  RobotLog2RQM/__init__.py,sha256=YKDTJjDsnQkr5X-gjjO8opwKUVKm6kc8sIUpURYMk48,596
4
4
  RobotLog2RQM/__main__.py,sha256=JabttEncy80antJWeGVmjoXyiF1DyXxkxdW4xLuHzT0,681
5
- RobotLog2RQM/robotlog2rqm.py,sha256=MIxcZOShuY4WhJrygEPz9ez4sExnqRChE8bS5KT7e_A,22809
6
- RobotLog2RQM/version.py,sha256=yqnuwM8tX61YINvlY0GOtFka-SLaeHKFTqJT9oju36M,917
5
+ RobotLog2RQM/robotlog2rqm.py,sha256=bhPaj43LazYO58mhchMt7ammaf6Qerxq7z2_aq8qesY,33437
6
+ RobotLog2RQM/version.py,sha256=lTcM8gNMLdiczaZBwoctKVZ7UBQ1h8jjocuoi3Qoc9A,917
7
7
  RobotLog2RQM/RQM_templates/buildrecord.xml,sha256=uGot7pNOjPR8do0JsJi0Lz3OCU9NMhODRd428QgvHh4,1498
8
8
  RobotLog2RQM/RQM_templates/configuration.xml,sha256=NrFDv51fuGhgeMiZuRhQ5q_UJ0u_pWzdxisIF5AJs74,1378
9
9
  RobotLog2RQM/RQM_templates/executionresult.xml,sha256=WTp4qDk29peBc0ll6GHVXX_kF_YBsOVjy9vBzoz7_2k,2160
10
10
  RobotLog2RQM/RQM_templates/executionworkitem.xml,sha256=3GYO-nvXcG-HDQZnDzGwYZSQOWAUNckT3_GUa8Ze2Eo,1511
11
11
  RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml,sha256=9GAs2WqZMkFJSNEZULm9BJvQy02dl_2JMecpQPHGTPA,1389
12
12
  RobotLog2RQM/RQM_templates/testcase.xml,sha256=zovFKj-37QHn2S8mMA_9RnAJ3zBmDJkJj31yelsnFFI,2167
13
+ RobotLog2RQM/RQM_templates/testsuite.xml,sha256=r2ijEsyPoE7qzCtUxgIHDOEcqUveDN4SMf9HSE9b0ZU,1326
13
14
  RobotLog2RQM/RQM_templates/testsuitelog.xml,sha256=l-NlaCyk6Ben76PElXKOHfMlEvyQ-e9MOZ6-F9HvwDQ,1920
14
- robotframework_robotlog2rqm-1.2.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- robotframework_robotlog2rqm-1.2.4.dist-info/METADATA,sha256=l4Iqizjf3T408fSOv1EegnqPsXVhZ0z7vlu_fb05RpE,9332
16
- robotframework_robotlog2rqm-1.2.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
17
- robotframework_robotlog2rqm-1.2.4.dist-info/entry_points.txt,sha256=-Xug2kDJW2LtcSADEVPtCwa337twCy2iGh5aK7xApHA,73
18
- robotframework_robotlog2rqm-1.2.4.dist-info/top_level.txt,sha256=jb_Gt6W44FoOLtGfBe7RzqCLaquhihkEWvSI1zjXDHc,13
19
- robotframework_robotlog2rqm-1.2.4.dist-info/RECORD,,
15
+ robotframework_robotlog2rqm-1.4.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ robotframework_robotlog2rqm-1.4.1.dist-info/METADATA,sha256=c_3TNFwH0GFeklIBKGmgJoYhnhXSVL1sNMiaw8IqorA,9951
17
+ robotframework_robotlog2rqm-1.4.1.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
18
+ robotframework_robotlog2rqm-1.4.1.dist-info/entry_points.txt,sha256=-Xug2kDJW2LtcSADEVPtCwa337twCy2iGh5aK7xApHA,73
19
+ robotframework_robotlog2rqm-1.4.1.dist-info/top_level.txt,sha256=jb_Gt6W44FoOLtGfBe7RzqCLaquhihkEWvSI1zjXDHc,13
20
+ robotframework_robotlog2rqm-1.4.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: bdist_wheel (0.44.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5