robotframework-robotlog2rqm 1.2.3__tar.gz → 1.4.0__tar.gz

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.
Files changed (26) hide show
  1. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/PKG-INFO +29 -22
  2. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/README.rst +28 -21
  3. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/CRQM.py +198 -4
  4. robotframework-robotlog2rqm-1.4.0/RobotLog2RQM/RQM_templates/testsuite.xml +27 -0
  5. robotframework-robotlog2rqm-1.4.0/RobotLog2RQM/RobotLog2RQM.pdf +0 -0
  6. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/robotlog2rqm.py +149 -29
  7. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/version.py +2 -2
  8. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/robotframework_robotlog2rqm.egg-info/PKG-INFO +29 -22
  9. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/robotframework_robotlog2rqm.egg-info/SOURCES.txt +1 -0
  10. robotframework-robotlog2rqm-1.2.3/RobotLog2RQM/RobotLog2RQM.pdf +0 -0
  11. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/LICENSE +0 -0
  12. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/buildrecord.xml +0 -0
  13. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/configuration.xml +0 -0
  14. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/executionresult.xml +0 -0
  15. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/executionworkitem.xml +0 -0
  16. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml +0 -0
  17. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/testcase.xml +0 -0
  18. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/RQM_templates/testsuitelog.xml +0 -0
  19. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/__init__.py +0 -0
  20. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/RobotLog2RQM/__main__.py +0 -0
  21. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/robotframework_robotlog2rqm.egg-info/dependency_links.txt +0 -0
  22. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/robotframework_robotlog2rqm.egg-info/entry_points.txt +0 -0
  23. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/robotframework_robotlog2rqm.egg-info/requires.txt +0 -0
  24. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/robotframework_robotlog2rqm.egg-info/top_level.txt +0 -0
  25. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/setup.cfg +0 -0
  26. {robotframework-robotlog2rqm-1.2.3 → robotframework-robotlog2rqm-1.4.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.2.3
3
+ Version: 1.4.0
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
@@ -128,33 +128,40 @@ Use below command to get tools\'s usage:
128
128
 
129
129
  The usage should be showed as below:
130
130
 
131
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--recursive]
132
- [--createmissing] [--updatetestcase] [--dryrun]
133
- resultxmlfile host project user password testplan
131
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
132
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
133
+ resultxmlfile host project user password testplan
134
134
 
135
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
135
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
136
136
  Robot Framework into an IBM Rational Quality Manager.
137
137
 
138
138
  positional arguments:
139
- resultxmlfile absolute or relative path to the xml result file
140
- or directory of result files to be imported.
141
- host RQM host url.
142
- project project on RQM.
143
- user user for RQM login.
144
- password password for RQM login.
145
- testplan testplan ID for this execution.
139
+ resultxmlfile absolute or relative path to the xml result file
140
+ or directory of result files to be imported.
141
+ host RQM host url.
142
+ project project on RQM.
143
+ user user for RQM login.
144
+ password password for RQM login.
145
+ testplan testplan ID for this execution.
146
146
 
147
147
  optional arguments:
148
- -h, --help show this help message and exit
149
- -v, --version Version of the RobotLog2RQM importer.
150
- --recursive if set, then the path is searched recursively for
151
- log files to be imported.
152
- --createmissing if set, then all testcases without tcid are created
153
- when importing.
154
- --updatetestcase if set, then testcase information on RQM will be updated
155
- bases on robot testfile.
156
- --dryrun if set, then verify all input arguments
157
- (includes RQM authentication) and show what would be done.
148
+ -h, --help show this help message and exit
149
+ -v, --version Version of the RobotLog2RQM importer.
150
+ --testsuite TESTSUITE
151
+ testsuite ID for this execution. If 'new', then create a new
152
+ testsuite for this execution.
153
+ --recursive if set, then the path is searched recursively for
154
+ log files to be imported.
155
+ --createmissing if set, then all testcases without tcid are created
156
+ when importing.
157
+ --updatetestcase if set, then testcase information on RQM will be updated
158
+ bases on robot testfile.
159
+ --dryrun if set, then verify all input arguments
160
+ (includes RQM authentication) and show what would be done.
161
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
162
+ to be enabled for the project area.
163
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
164
+ or Baselines Only to be enabled for the project area.
158
165
 
159
166
  The below command is simple usage witth all required arguments to import
160
167
  Robot Framework results into RQM:
@@ -112,33 +112,40 @@ The usage should be showed as below:
112
112
 
113
113
  ::
114
114
 
115
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--recursive]
116
- [--createmissing] [--updatetestcase] [--dryrun]
117
- resultxmlfile host project user password testplan
115
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
116
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
117
+ resultxmlfile host project user password testplan
118
118
 
119
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
119
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
120
120
  Robot Framework into an IBM Rational Quality Manager.
121
121
 
122
122
  positional arguments:
123
- resultxmlfile absolute or relative path to the xml result file
124
- or directory of result files to be imported.
125
- host RQM host url.
126
- project project on RQM.
127
- user user for RQM login.
128
- password password for RQM login.
129
- testplan testplan ID for this execution.
123
+ resultxmlfile absolute or relative path to the xml result file
124
+ or directory of result files to be imported.
125
+ host RQM host url.
126
+ project project on RQM.
127
+ user user for RQM login.
128
+ password password for RQM login.
129
+ testplan testplan ID for this execution.
130
130
 
131
131
  optional arguments:
132
- -h, --help show this help message and exit
133
- -v, --version Version of the RobotLog2RQM importer.
134
- --recursive if set, then the path is searched recursively for
135
- log files to be imported.
136
- --createmissing if set, then all testcases without tcid are created
137
- when importing.
138
- --updatetestcase if set, then testcase information on RQM will be updated
139
- bases on robot testfile.
140
- --dryrun if set, then verify all input arguments
141
- (includes RQM authentication) and show what would be done.
132
+ -h, --help show this help message and exit
133
+ -v, --version Version of the RobotLog2RQM importer.
134
+ --testsuite TESTSUITE
135
+ testsuite ID for this execution. If 'new', then create a new
136
+ testsuite for this execution.
137
+ --recursive if set, then the path is searched recursively for
138
+ log files to be imported.
139
+ --createmissing if set, then all testcases without tcid are created
140
+ when importing.
141
+ --updatetestcase if set, then testcase information on RQM will be updated
142
+ bases on robot testfile.
143
+ --dryrun if set, then verify all input arguments
144
+ (includes RQM authentication) and show what would be done.
145
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
146
+ to be enabled for the project area.
147
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
148
+ or Baselines Only to be enabled for the project area.
142
149
 
143
150
 
144
151
  The below command is simple usage witth all required arguments to import
@@ -195,7 +195,12 @@ Constructor of class ``CRQMClient``.
195
195
  self.configuration = None
196
196
  self.createmissing = None
197
197
  self.updatetestcase= None
198
- self.testsuite = None
198
+ self.testsuite = {
199
+ "id": None,
200
+ "name": None
201
+ }
202
+ self.stream = None
203
+ self.baseline = None
199
204
 
200
205
  def login(self):
201
206
  """
@@ -285,7 +290,8 @@ Disconnect from RQM.
285
290
  self.session.close()
286
291
 
287
292
  def config(self, plan_id, build_name=None, config_name=None,
288
- createmissing=False, updatetestcase=False,suite_id=None):
293
+ createmissing=False, updatetestcase=False, suite_id=None,
294
+ stream=None, baseline=None):
289
295
  """
290
296
  Configure RQMClient with testplan ID, build, configuration, createmissing, ...
291
297
 
@@ -340,8 +346,45 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
340
346
  try:
341
347
  self.createmissing = createmissing
342
348
  self.updatetestcase = updatetestcase
343
- self.testsuite = suite_id
344
349
  self.testplan = plan_id
350
+ self.testsuite['id'] = suite_id
351
+
352
+ # Add Configuration-Context header information due to given stream or baseline
353
+ if stream:
354
+ res = self.getAllByResource('stream')
355
+ if res['success']:
356
+ dStreams = res['data']
357
+ bFoundStream = False
358
+ for stream_id, stream_name in dStreams.items():
359
+ if stream_name == stream:
360
+ self.stream = stream_id
361
+ bFoundStream = True
362
+ self.headers['Configuration-Context'] = stream_id
363
+ self.session.headers = self.headers
364
+ break
365
+
366
+ if not bFoundStream:
367
+ raise Exception(f"Cannot found given stream '{stream}'")
368
+ else:
369
+ raise Exception("Get all streams failed. Reason: %s"%res['message'])
370
+ elif baseline:
371
+ res = self.getAllByResource('baseline')
372
+ if res['success']:
373
+ dBaselines = res['data']
374
+ bFoundBaseline = False
375
+ for baseline_id, baseline_name in dBaselines.items():
376
+ if baseline_name == baseline:
377
+ self.baseline = baseline_id
378
+ bFoundBaseline = True
379
+ self.headers['Configuration-Context'] = baseline_id
380
+ self.session.headers = self.headers
381
+ break
382
+
383
+ if not bFoundBaseline:
384
+ raise Exception(f"Cannot found given baseline '{baseline}'")
385
+ else:
386
+ raise Exception("Get all baselines failed. Reason: %s"%res['message'])
387
+
345
388
  # Verify testplan ID
346
389
  res_plan = self.getResourceByID('testplan', plan_id)
347
390
  if res_plan.status_code != 200:
@@ -371,8 +414,17 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
371
414
  raise Exception("Cannot create configuration '%s': %s"%
372
415
  (config_name, res_conf['message']))
373
416
 
417
+ # Verify testsuite if given
418
+ if (suite_id != None) and (suite_id != "new"):
419
+ res_suite = self.getResourceByID('testsuite', suite_id)
420
+ if res_suite.status_code != 200:
421
+ raise Exception('Testsuite with ID %s is not existing!'%str(suite_id))
422
+ oTestsuite = get_xml_tree(BytesIO(str(res_suite.text).encode()), bdtd_validation=False)
423
+ self.testsuite['name'] = oTestsuite.find('ns4:title', oTestsuite.getroot().nsmap).text
424
+
374
425
  # get all team-areas for testcase template
375
426
  self.getAllTeamAreas()
427
+
376
428
 
377
429
  except Exception as error:
378
430
  raise Exception('Configure RQMClient failed: %s'%error)
@@ -1323,7 +1375,7 @@ Return testsuite execution record (TSER) template from provided configuration na
1323
1375
  return sTSxml
1324
1376
 
1325
1377
  def createTestsuiteResultTemplate(self, testsuiteID, testsuiteName, TSERID,
1326
- lTCER, lTCResults, startTime='',
1378
+ lTCER, lTCResults, resultState, startTime='',
1327
1379
  endTime='', duration='', sOwnerID=''):
1328
1380
  """
1329
1381
  Return testsuite execution result template from provided configuration name.
@@ -1395,6 +1447,7 @@ Return testsuite execution result template from provided configuration name.
1395
1447
  sTSResultxml = ''
1396
1448
  sTemplatePath = os.path.join(self.templatesDir, 'testsuitelog.xml')
1397
1449
  oTree = get_xml_tree(sTemplatePath, bdtd_validation=False)
1450
+ prefixState = 'com.ibm.rqm.execution.common.state.'
1398
1451
 
1399
1452
  # prepare required data for template
1400
1453
  resultTittle = 'Testsuite result: ' + testsuiteName
@@ -1438,6 +1491,11 @@ Return testsuite execution result template from provided configuration name.
1438
1491
  oStarttime.text = str(startTime).replace(' ', 'T')
1439
1492
  oEndtime.text = str(endTime).replace(' ', 'T')
1440
1493
  oTotalRunTime.text = str(duration)
1494
+ # set default RQM state as inconclusive
1495
+ oState.text = prefixState + 'inconclusive'
1496
+ if resultState.lower() in self.RESULT_STATES:
1497
+ oState.text = prefixState +resultState.lower()
1498
+
1441
1499
  for idx, sTCER in enumerate(lTCER):
1442
1500
  sTCERURL = self.integrationURL('executionworkitem', sTCER)
1443
1501
  oSuiteElem = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/tsl/v0.1/}suiteelement', nsmap=nsmap)
@@ -1459,6 +1517,79 @@ Return testsuite execution result template from provided configuration name.
1459
1517
  sTSResultxml = etree.tostring(oTree)
1460
1518
  return sTSResultxml
1461
1519
 
1520
+ def createTestsuiteTemplate(self, testsuiteName, sDescription='', sOwnerID='', sTStemplate=None):
1521
+ """
1522
+ Return testcase template from provided information.
1523
+
1524
+ **Arguments:**
1525
+
1526
+ * ``testsuiteName``
1527
+
1528
+ / *Condition*: required / *Type*: str /
1529
+
1530
+ Testsuite name.
1531
+
1532
+ * ``sDescription``
1533
+
1534
+ / *Condition*: optional / *Type*: str / *Default*: '' /
1535
+
1536
+ Testsuite description.
1537
+
1538
+ * ``sOwnerID``
1539
+
1540
+ / *Condition*: optional / *Type*: str / *Default*: '' /
1541
+
1542
+ User ID of testsuite owner.
1543
+
1544
+ * ``sTStemplate``
1545
+
1546
+ / *Condition*: optional / *Type*: str / *Default*: None /
1547
+
1548
+ Existing testsuite template as xml string.
1549
+
1550
+ If not provided, template file under `RQM_templates` is used as default.
1551
+
1552
+ **Returns:**
1553
+
1554
+ * ``sTSxml``
1555
+
1556
+ / *Type*: str /
1557
+
1558
+ The xml testsuite template as string.
1559
+ """
1560
+ sTSxml = ''
1561
+ if not sTStemplate:
1562
+ sTemplatePath = os.path.join(self.templatesDir ,'testsuite.xml')
1563
+ oTree = get_xml_tree(sTemplatePath, bdtd_validation=False)
1564
+ else:
1565
+ oTree = get_xml_tree(BytesIO(sTStemplate.encode()),bdtd_validation=False)
1566
+
1567
+ root = oTree.getroot()
1568
+ nsmap = root.nsmap
1569
+
1570
+ # prepare required data for template
1571
+ testerURL = self.userURL(self.userID)
1572
+
1573
+ # find nodes to change data
1574
+ oTittle = oTree.find('ns4:title', nsmap)
1575
+ oDescription = oTree.find('ns4:description', nsmap)
1576
+ oOwner = oTree.find('ns6:owner', nsmap)
1577
+
1578
+ # change nodes's data
1579
+ oTittle.text = testsuiteName
1580
+ oDescription.text = sDescription
1581
+
1582
+ # Incase not specify owner in template or input data, set it as provided user in cli
1583
+ if sOwnerID:
1584
+ oOwner.text = sOwnerID
1585
+ oOwner.attrib['{%s}resource' % nsmap['ns1']] = self.userURL(sOwnerID)
1586
+ elif not oOwner.text:
1587
+ oOwner.text = self.userID
1588
+ oOwner.attrib['{%s}resource' % nsmap['ns1']] = testerURL
1589
+
1590
+ # return xml template as string
1591
+ sTSxml = etree.tostring(oTree)
1592
+ return sTSxml
1462
1593
  #
1463
1594
  # Methods to create RQM resources
1464
1595
  #
@@ -1827,3 +1958,66 @@ Link list of test cases to provided testsuite ID
1827
1958
  else:
1828
1959
  returnObj['message'] = "No testcase for linking."
1829
1960
  return returnObj
1961
+
1962
+ def addTestsuite2Testplan(self, testplanID, testsuiteID=None):
1963
+ """
1964
+ Add testsuite ID to provided testplan ID
1965
+
1966
+ **Arguments:**
1967
+
1968
+ * ``testplanID``
1969
+
1970
+ / *Condition*: required / *Type*: str /
1971
+
1972
+ Testplan ID to link given testsuite ID.
1973
+
1974
+ * ``testsuiteID``
1975
+
1976
+ / *Condition*: optional / *Type*: str / *Default*: None /
1977
+
1978
+ Testsuite to be linked with given testplan.
1979
+
1980
+ If not provide, `testsuite['id']` value will be used as id of testsuite.
1981
+
1982
+ **Returns:**
1983
+
1984
+ * ``returnObj``
1985
+
1986
+ / *Type*: dict /
1987
+
1988
+ Response dictionary which contains status and error message.
1989
+
1990
+ Example:
1991
+
1992
+ .. code:: python
1993
+
1994
+ {
1995
+ 'success' : False,
1996
+ 'message': ''
1997
+ }
1998
+
1999
+ """
2000
+ returnObj = {'success' : False, 'message': ''}
2001
+ if testsuiteID == None:
2002
+ testsuiteID = self.testsuite['id']
2003
+ if testsuiteID:
2004
+ resTestplanData = self.getResourceByID('testplan', testplanID)
2005
+ oTree = get_xml_tree(BytesIO(str(resTestplanData.text).encode()),bdtd_validation=False)
2006
+ # RQM XML response using namespace for nodes
2007
+ # use namespace mapping from root for access response XML
2008
+ root = oTree.getroot()
2009
+
2010
+ sTestsuiteURL = self.integrationURL('testsuite', testsuiteID)
2011
+ oTS = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/}testsuite', nsmap=root.nsmap)
2012
+ oTS.set('href', sTestsuiteURL)
2013
+ root.append(oTS)
2014
+
2015
+ # Update test plan data with linked testsuite and PUT to RQM
2016
+ resUpdateTestplan = self.updateResourceByID('testplan', testplanID, etree.tostring(oTree))
2017
+ if resUpdateTestplan.status_code == 200:
2018
+ returnObj['success'] = True
2019
+ else:
2020
+ returnObj['message'] = str(resUpdateTestplan.reason)
2021
+ else:
2022
+ returnObj['message'] = "No testsuite for adding."
2023
+ 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>
@@ -41,7 +41,8 @@ from RobotLog2RQM.version import VERSION, VERSION_DATE
41
41
  DRESULT_MAPPING = {
42
42
  "PASS": "Passed",
43
43
  "FAIL": "Failed",
44
- "UNKNOWN": "Inconclusive"
44
+ "UNKNOWN": "Inconclusive",
45
+ "SKIP": "Blocked"
45
46
  }
46
47
 
47
48
  DEFAULT_METADATA = {
@@ -157,7 +158,7 @@ Write log message to console/file output.
157
158
  return
158
159
 
159
160
  @classmethod
160
- def log_warning(cls, msg):
161
+ def log_warning(cls, msg, indent=0):
161
162
  """
162
163
  Write warning message to console/file output.
163
164
 
@@ -169,14 +170,20 @@ Write warning message to console/file output.
169
170
 
170
171
  Warning message which is written to output.
171
172
 
173
+ * ``indent``
174
+
175
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
176
+
177
+ Offset indent.
178
+
172
179
  **Returns:**
173
180
 
174
181
  (*no returns*)
175
182
  """
176
- cls.log(cls.prefix_warn+str(msg), cls.color_warn)
183
+ cls.log(cls.prefix_warn+str(msg), cls.color_warn, indent)
177
184
 
178
185
  @classmethod
179
- def log_error(cls, msg, fatal_error=False):
186
+ def log_error(cls, msg, fatal_error=False, indent=0):
180
187
  """
181
188
  Write error message to console/file output.
182
189
 
@@ -192,6 +199,12 @@ Write error message to console/file output.
192
199
 
193
200
  If set, tool will terminate after logging error message.
194
201
 
202
+ * ``indent``
203
+
204
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
205
+
206
+ Offset indent.
207
+
195
208
  **Returns:**
196
209
 
197
210
  (*no returns*)
@@ -200,7 +213,7 @@ Write error message to console/file output.
200
213
  if fatal_error:
201
214
  prefix = cls.prefix_fatalerror
202
215
 
203
- cls.log(prefix+str(msg), cls.color_error)
216
+ cls.log(prefix+str(msg), cls.color_error, indent)
204
217
  if fatal_error:
205
218
  cls.log(f"{sys.argv[0]} has been stopped!", cls.color_error)
206
219
  exit(1)
@@ -280,9 +293,12 @@ Avalable arguments in command line:
280
293
  - `user` : user for RQM login.
281
294
  - `password` : user password for RQM login.
282
295
  - `testplan` : RQM testplan ID.
296
+ - `--testsuite` : RQM testsuite ID. If value is 'new', then create a new testsuite for this execution.
283
297
  - `--recursive` : if True, then the path is searched recursively for log files to be imported.
284
298
  - `--createmissing` : if True, then all testcases without tcid are created when importing.
285
299
  - `--dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
300
+ - `--stream` : project stream. Note, requires Configuration Management (CM) to be enabled for the project area.
301
+ - `--baseline` : project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.
286
302
 
287
303
  **Arguments:**
288
304
 
@@ -311,6 +327,8 @@ Avalable arguments in command line:
311
327
  cmdParser.add_argument('password', type=str, help='password for RQM login.')
312
328
  cmdParser.add_argument('testplan', type=str,
313
329
  help='testplan ID for this execution.')
330
+ cmdParser.add_argument('--testsuite', type=str,
331
+ help="testsuite ID for this execution. If 'new', then create a new testsuite for this execution.")
314
332
  cmdParser.add_argument('--recursive',action="store_true",
315
333
  help='if set, then the path is searched recursively for log files to be imported.')
316
334
  cmdParser.add_argument('--createmissing', action="store_true",
@@ -319,6 +337,10 @@ Avalable arguments in command line:
319
337
  help='if set, then testcase information on RQM will be updated bases on robot testfile.')
320
338
  cmdParser.add_argument('--dryrun',action="store_true",
321
339
  help='if set, then verify all input arguments (includes RQM authentication) and show what would be done.')
340
+ cmdParser.add_argument('--stream', type=str,
341
+ help='project stream. Note, requires Configuration Management (CM) to be enabled for the project area.')
342
+ cmdParser.add_argument('--baseline', type=str,
343
+ help='project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.')
322
344
 
323
345
  return cmdParser.parse_args()
324
346
 
@@ -394,7 +416,7 @@ Extract metadata from suite result bases on DEFAULT_METADATA.
394
416
 
395
417
  return dMetadata
396
418
 
397
- def process_suite(RQMClient, suite):
419
+ def process_suite(RQMClient, suite, log_indent=0):
398
420
  """
399
421
  Process robot suite for importing to RQM.
400
422
 
@@ -412,15 +434,21 @@ Process robot suite for importing to RQM.
412
434
 
413
435
  Robot suite object.
414
436
 
437
+ * ``log_indent``
438
+
439
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
440
+
441
+ Indent for logging message.
442
+
415
443
  **Returns:**
416
444
 
417
445
  (*no returns*)
418
446
  """
419
447
  if len(list(suite.suites)) > 0:
420
448
  for subsuite in suite.suites:
421
- process_suite(RQMClient, subsuite)
449
+ process_suite(RQMClient, subsuite, log_indent=log_indent+2)
422
450
  else:
423
- Logger.log(f"Process suite: {suite.name}")
451
+ Logger.log(f"Process suite: {suite.name}", indent=log_indent)
424
452
 
425
453
  # update missing metadata from parent suite
426
454
  if suite.parent and suite.parent.metadata:
@@ -430,9 +458,9 @@ Process robot suite for importing to RQM.
430
458
 
431
459
  if len(list(suite.tests)) > 0:
432
460
  for test in suite.tests:
433
- process_test(RQMClient, test)
461
+ process_test(RQMClient, test, log_indent=log_indent+2)
434
462
 
435
- def process_test(RQMClient, test):
463
+ def process_test(RQMClient, test, log_indent=0):
436
464
  """
437
465
  Process robot test for importing to RQM.
438
466
 
@@ -450,11 +478,17 @@ Process robot test for importing to RQM.
450
478
 
451
479
  Robot test object.
452
480
 
481
+ * ``log_indent``
482
+
483
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
484
+
485
+ Indent for logging message.
486
+
453
487
  **Returns:**
454
488
 
455
489
  (*no returns*)
456
490
  """
457
- Logger.log(f"Process test: {test.name}")
491
+ Logger.log(f"Process test: {test.name}", indent=log_indent)
458
492
 
459
493
  # Avoid create resources with dryrun
460
494
  if Logger.dryrun:
@@ -484,7 +518,7 @@ Process robot test for importing to RQM.
484
518
  try:
485
519
  _tc_result = DRESULT_MAPPING[test.status]
486
520
  except Exception:
487
- Logger.log_error(f"Invalid Robotframework result state '{test.status}' of test '{_tc_name}'.")
521
+ Logger.log_error(f"Invalid Robotframework result state '{test.status}' of test '{_tc_name}'.", indent=log_indent)
488
522
  return
489
523
  _tc_message = test.message
490
524
  _tc_start_time = convert_to_datetime(test.starttime)
@@ -508,19 +542,19 @@ Process robot test for importing to RQM.
508
542
  res = RQMClient.createResource('testcase', oTCTemplate)
509
543
  if res['success']:
510
544
  _tc_id = res['id']
511
- Logger.log(f"Create testcase '{_tc_name}' with ID '{_tc_id}' successfully!")
545
+ Logger.log(f"Create testcase '{_tc_name}' with ID '{_tc_id}' successfully!", indent=log_indent)
512
546
  RQMClient.dMappingTCID[_tc_id] = _tc_name
513
547
  else:
514
- Logger.log_error(f"Create testcase '{_tc_name}' failed. Reason: {res['message']}")
548
+ Logger.log_error(f"Create testcase '{_tc_name}' failed. Reason: {res['message']}", indent=log_indent)
515
549
  return
516
550
  else:
517
- Logger.log_error(f"There is no 'tcid' information for importing test '{_tc_name}'.")
551
+ Logger.log_error(f"There is no 'tcid' information for importing test '{_tc_name}'.", indent=log_indent)
518
552
  return
519
553
  else:
520
554
  # If more than 1 tcid are defined in [Tags], the first one is used.
521
555
  if len(lTCIDTags) > 1:
522
556
  _tc_id = lTCIDTags[0]
523
- Logger.log_warning(f"More than 1 'tcid-' tags in test '{_tc_name}', '{_tc_id}' is used.")
557
+ Logger.log_warning(f"More than 1 'tcid-' tags in test '{_tc_name}', '{_tc_id}' is used.", indent=log_indent)
524
558
 
525
559
  # If --updatetestcase is set. Test case with provided tcid will be updated on RQM:
526
560
  # Get existing resource of testcase from RQM.
@@ -537,9 +571,9 @@ Process robot test for importing to RQM.
537
571
  _tc_link,
538
572
  sTCtemplate=str(resTC.text))
539
573
  RQMClient.updateResourceByID('testcase', _tc_id, oTCTemplate)
540
- Logger.log(f"Update testcase '{_tc_name}' with ID '{_tc_id}' successfully!")
574
+ Logger.log(f"Update testcase '{_tc_name}' with ID '{_tc_id}' successfully!", indent=log_indent)
541
575
  else:
542
- Logger.log_error(f"Update testcase with ID '{_tc_id}' failed. Please check whether it is existing on RQM.")
576
+ Logger.log_error(f"Update testcase with ID '{_tc_id}' failed. Please check whether it is existing on RQM.", indent=log_indent)
543
577
  return
544
578
 
545
579
  # Create TCER:
@@ -554,11 +588,11 @@ Process robot test for importing to RQM.
554
588
  res = RQMClient.createResource('executionworkitem', oTCERTemplate)
555
589
  _tc_tcer_id = res['id']
556
590
  if res['success']:
557
- Logger.log(f"Created TCER with ID '{_tc_tcer_id}' successfully.")
591
+ Logger.log(f"Created TCER with ID '{_tc_tcer_id}' successfully.", indent=log_indent+2)
558
592
  elif (res['status_code'] == 303 or res['status_code'] == 200) and res['id'] != '':
559
- Logger.log_warning(f"TCER for testcase '{_tc_id}' and testplan '{_tc_testplan_id}' is existing with ID: '{_tc_tcer_id}'")
593
+ 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)
560
594
  else:
561
- 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']}.")
595
+ 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)
562
596
  return
563
597
 
564
598
  if _tc_tcer_id not in RQMClient.lTCERIDs:
@@ -583,10 +617,10 @@ Process robot test for importing to RQM.
583
617
  _tc_team)
584
618
  res = RQMClient.createResource('executionresult', oTCResultTemplate)
585
619
  if res['success']:
586
- Logger.log(f"Create result for test '{_tc_name}' successfully!")
620
+ Logger.log(f"Create result for test '{_tc_name}' successfully!", indent=log_indent+4)
587
621
  _tc_result_id = res['id']
588
622
  else:
589
- Logger.log_error(f"Create result for test '{_tc_name}' failed. Reason: {res['message']}.")
623
+ Logger.log_error(f"Create result for test '{_tc_name}' failed. Reason: {res['message']}.", indent=log_indent+4)
590
624
  return
591
625
  if _tc_result_id not in RQMClient.lTCResultIDs:
592
626
  RQMClient.lTCResultIDs.append(_tc_result_id)
@@ -594,6 +628,10 @@ Process robot test for importing to RQM.
594
628
  # Append lTestcaseIDs (for linking testplan/testsuite)
595
629
  if _tc_id not in RQMClient.lTestcaseIDs:
596
630
  RQMClient.lTestcaseIDs.append(_tc_id)
631
+
632
+ # Collect starttime and endtime for testsuite log creation
633
+ RQMClient.lStartTimes.append(_tc_start_time)
634
+ RQMClient.lEndTimes.append(_tc_end_time)
597
635
 
598
636
  def RobotLog2RQM(args=None):
599
637
  """
@@ -621,11 +659,13 @@ Flow to import Robot results to RQM:
621
659
  * `user` : user for RQM login.
622
660
  * `password` : user password for RQM login.
623
661
  * `testplan` : RQM testplan ID.
662
+ * `testsuite` : testsuite ID for this execution. If 'new', then create a new testsuite for this execution.
624
663
  * `recursive` : if True, then the path is searched recursively for log files to be imported.
625
664
  * `createmissing` : if True, then all testcases without tcid are created when importing.
626
665
  * `updatetestcase` : if True, then testcases information on RQM will be updated bases on robot testfile.
627
666
  * `dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
628
-
667
+ * `stream` : project stream. Note, requires Configuration Management (CM) to be enabled for the project area.
668
+ * `baseline` : project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.
629
669
  **Returns:**
630
670
 
631
671
  (*no returns*)
@@ -675,6 +715,7 @@ Flow to import Robot results to RQM:
675
715
  try:
676
716
  bSuccess = RQMClient.login()
677
717
  if bSuccess:
718
+ Logger.log()
678
719
  Logger.log(f"Login RQM as user '{args.user}' successfully!")
679
720
  else:
680
721
  Logger.log_error("Could not login to RQM: 'Unkown reason'.")
@@ -693,14 +734,85 @@ Flow to import Robot results to RQM:
693
734
  metadata_info['version_sw'] = None
694
735
  metadata_info['project'] = None
695
736
  RQMClient.config(args.testplan, metadata_info['version_sw'],
696
- metadata_info['project'], args.createmissing, args.updatetestcase)
737
+ metadata_info['project'], args.createmissing, args.updatetestcase,
738
+ args.testsuite, stream=args.stream, baseline=args.baseline)
739
+
740
+ if args.testsuite == "new":
741
+ # Create new testsuite
742
+ if not args.dryrun:
743
+ testsuite_data = RQMClient.createTestsuiteTemplate(result.suite.name, result.suite.doc)
744
+ res_testsuite = RQMClient.createResource('testsuite', testsuite_data)
745
+ else:
746
+ # for dryrun
747
+ res_testsuite = {'success': True, 'id': 1111}
748
+
749
+ if res_testsuite['success']:
750
+ _ts_id = res_testsuite['id']
751
+ Logger.log(f"Create testsuite '{result.suite.name}' with ID '{_ts_id}' successfully!")
752
+ RQMClient.testsuite['id'] = _ts_id
753
+ RQMClient.testsuite['name'] = result.suite.name
754
+ else:
755
+ Logger.log_error(f"Create testsuite '{result.suite.name}' failed. Reason: {res_testsuite['message']}", fatal_error=True)
756
+
697
757
  # Process suite for importing
698
758
  process_suite(RQMClient, result.suite)
699
759
 
700
- # Link all imported testcase ID(s) with testplan
701
- Logger.log("Linking all imported testcase ID(s) with testplan ...")
702
- RQMClient.linkListTestcase2Testplan(args.testplan)
760
+ if RQMClient.testsuite['id']:
761
+ if not args.dryrun:
762
+ # Create testsuite execution record if requires
763
+ testsuite_record_data = RQMClient.createTSERTemplate(RQMClient.testsuite['id'], RQMClient.testsuite['name'], args.testplan, RQMClient.configuration)
764
+ res_TSER = RQMClient.createResource('suiteexecutionrecord', testsuite_record_data)
765
+ sTSERID = res_TSER['id']
766
+ Logger.log()
767
+ if res_TSER['success']:
768
+ Logger.log(f"Create TSER with id {sTSERID} successfully!")
769
+ elif (res_TSER['status_code'] == 303 or res_TSER['status_code'] == 200) and res_TSER['id'] != '':
770
+ ### incase executionworkitem is existing, cannot create new one
771
+ ### Use the existing ID for new result
772
+ Logger.log_warning(f"TSER for testsuite {RQMClient.testsuite['id']} is existing.\nAdd this execution result to existing TSER id: {sTSERID}")
773
+ else:
774
+ Logger.log_error(f"Create TSER failed, {res_TSER['message']}")
775
+
776
+ # Create new testsuite result and link all TCERs
777
+ testsuite_result_data = RQMClient.createTestsuiteResultTemplate(RQMClient.testsuite['id'],
778
+ RQMClient.testsuite['name'],
779
+ sTSERID,
780
+ RQMClient.lTCERIDs,
781
+ RQMClient.lTCResultIDs,
782
+ DRESULT_MAPPING[result.suite.status]
783
+ )
784
+ res_TSLog = RQMClient.createResource('testsuitelog', testsuite_result_data)
785
+ sSuiteResultID = res_TSLog['id']
786
+ if res_TSLog['success']:
787
+ Logger.log(f"Created testsuite result with id {sSuiteResultID} successfully.", indent=2)
788
+ else:
789
+ Logger.log_error(f"Create testsuite result failed, {res_TSLog['message']}", indent=2)
790
+ else:
791
+ Logger.log(f"Create TSER")
792
+ Logger.log(f"Created testsuite result")
793
+
794
+ # Link all imported testcase ID(s) with testsuite
795
+ try:
796
+ RQMClient.linkListTestcase2Testsuite(RQMClient.testsuite['id'])
797
+ Logger.log(f"Link all imported test cases with testsuite {RQMClient.testsuite['id']} successfully.")
798
+ except Exception as reason:
799
+ Logger.log_error(f"Link all imported test cases with testsuite failed.\nReason: {reason}", fatal_error=True)
800
+
801
+ # Add testsuite to given testplan
802
+ try:
803
+ RQMClient.addTestsuite2Testplan(args.testplan)
804
+ Logger.log(f"Add testsuite {RQMClient.testsuite['id']} to testplan {args.testplan} successfully.")
805
+ except Exception as reason:
806
+ Logger.log_error(f"Add testsuite to testplan failed.\nReason: {reason}", fatal_error=True)
703
807
 
808
+ else:
809
+ # Link all imported testcase ID(s) with testplan
810
+ try:
811
+ RQMClient.linkListTestcase2Testplan(args.testplan)
812
+ Logger.log(f"Link all imported test cases with testplan {args.testplan} successfully.")
813
+ except Exception as reason:
814
+ Logger.log_error(f"Link all imported test cases with testplan failed.\nReason: {reason}", fatal_error=True)
815
+
704
816
  # Update testcase(s) with generated ID(s)
705
817
  # Under developing
706
818
 
@@ -709,7 +821,15 @@ Flow to import Robot results to RQM:
709
821
 
710
822
  # 5. Disconnect from RQM
711
823
  RQMClient.disconnect()
712
- Logger.log("All test results have been imported to RQM successfully.!")
824
+
825
+ testcnt_msg = f"All {len(RQMClient.lTestcaseIDs)}"
826
+ extended_msg = ""
827
+ if (len(RQMClient.lTestcaseIDs) > len(RQMClient.lTCResultIDs)):
828
+ testcnt_msg = f"{len(RQMClient.lTCResultIDs)} of {len(RQMClient.lTestcaseIDs)}"
829
+ extended_msg = f" {len(RQMClient.lTestcaseIDs)-len(RQMClient.lTCResultIDs)} test results are skipped because of errors."
830
+
831
+ Logger.log()
832
+ Logger.log(f"{testcnt_msg} test results are imported to RQM successfully.{extended_msg}")
713
833
 
714
834
  if __name__=="__main__":
715
835
  RobotLog2RQM()
@@ -18,6 +18,6 @@
18
18
  #
19
19
  # Version and date of RobotLog2RQM
20
20
  #
21
- VERSION = "1.2.3"
22
- VERSION_DATE = "14.03.2024"
21
+ VERSION = "1.4.0"
22
+ VERSION_DATE = "24.07.2024"
23
23
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.2.3
3
+ Version: 1.4.0
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
@@ -128,33 +128,40 @@ Use below command to get tools\'s usage:
128
128
 
129
129
  The usage should be showed as below:
130
130
 
131
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--recursive]
132
- [--createmissing] [--updatetestcase] [--dryrun]
133
- resultxmlfile host project user password testplan
131
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
132
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
133
+ resultxmlfile host project user password testplan
134
134
 
135
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
135
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
136
136
  Robot Framework into an IBM Rational Quality Manager.
137
137
 
138
138
  positional arguments:
139
- resultxmlfile absolute or relative path to the xml result file
140
- or directory of result files to be imported.
141
- host RQM host url.
142
- project project on RQM.
143
- user user for RQM login.
144
- password password for RQM login.
145
- testplan testplan ID for this execution.
139
+ resultxmlfile absolute or relative path to the xml result file
140
+ or directory of result files to be imported.
141
+ host RQM host url.
142
+ project project on RQM.
143
+ user user for RQM login.
144
+ password password for RQM login.
145
+ testplan testplan ID for this execution.
146
146
 
147
147
  optional arguments:
148
- -h, --help show this help message and exit
149
- -v, --version Version of the RobotLog2RQM importer.
150
- --recursive if set, then the path is searched recursively for
151
- log files to be imported.
152
- --createmissing if set, then all testcases without tcid are created
153
- when importing.
154
- --updatetestcase if set, then testcase information on RQM will be updated
155
- bases on robot testfile.
156
- --dryrun if set, then verify all input arguments
157
- (includes RQM authentication) and show what would be done.
148
+ -h, --help show this help message and exit
149
+ -v, --version Version of the RobotLog2RQM importer.
150
+ --testsuite TESTSUITE
151
+ testsuite ID for this execution. If 'new', then create a new
152
+ testsuite for this execution.
153
+ --recursive if set, then the path is searched recursively for
154
+ log files to be imported.
155
+ --createmissing if set, then all testcases without tcid are created
156
+ when importing.
157
+ --updatetestcase if set, then testcase information on RQM will be updated
158
+ bases on robot testfile.
159
+ --dryrun if set, then verify all input arguments
160
+ (includes RQM authentication) and show what would be done.
161
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
162
+ to be enabled for the project area.
163
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
164
+ or Baselines Only to be enabled for the project area.
158
165
 
159
166
  The below command is simple usage witth all required arguments to import
160
167
  Robot Framework results into RQM:
@@ -13,6 +13,7 @@ RobotLog2RQM/RQM_templates/executionresult.xml
13
13
  RobotLog2RQM/RQM_templates/executionworkitem.xml
14
14
  RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml
15
15
  RobotLog2RQM/RQM_templates/testcase.xml
16
+ RobotLog2RQM/RQM_templates/testsuite.xml
16
17
  RobotLog2RQM/RQM_templates/testsuitelog.xml
17
18
  robotframework_robotlog2rqm.egg-info/PKG-INFO
18
19
  robotframework_robotlog2rqm.egg-info/SOURCES.txt