robotframework-robotlog2rqm 1.2.4__py3-none-any.whl → 1.4.0__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
@@ -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>
Binary file
@@ -158,7 +158,7 @@ Write log message to console/file output.
158
158
  return
159
159
 
160
160
  @classmethod
161
- def log_warning(cls, msg):
161
+ def log_warning(cls, msg, indent=0):
162
162
  """
163
163
  Write warning message to console/file output.
164
164
 
@@ -170,14 +170,20 @@ Write warning message to console/file output.
170
170
 
171
171
  Warning message which is written to output.
172
172
 
173
+ * ``indent``
174
+
175
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
176
+
177
+ Offset indent.
178
+
173
179
  **Returns:**
174
180
 
175
181
  (*no returns*)
176
182
  """
177
- cls.log(cls.prefix_warn+str(msg), cls.color_warn)
183
+ cls.log(cls.prefix_warn+str(msg), cls.color_warn, indent)
178
184
 
179
185
  @classmethod
180
- def log_error(cls, msg, fatal_error=False):
186
+ def log_error(cls, msg, fatal_error=False, indent=0):
181
187
  """
182
188
  Write error message to console/file output.
183
189
 
@@ -193,6 +199,12 @@ Write error message to console/file output.
193
199
 
194
200
  If set, tool will terminate after logging error message.
195
201
 
202
+ * ``indent``
203
+
204
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
205
+
206
+ Offset indent.
207
+
196
208
  **Returns:**
197
209
 
198
210
  (*no returns*)
@@ -201,7 +213,7 @@ Write error message to console/file output.
201
213
  if fatal_error:
202
214
  prefix = cls.prefix_fatalerror
203
215
 
204
- cls.log(prefix+str(msg), cls.color_error)
216
+ cls.log(prefix+str(msg), cls.color_error, indent)
205
217
  if fatal_error:
206
218
  cls.log(f"{sys.argv[0]} has been stopped!", cls.color_error)
207
219
  exit(1)
@@ -281,9 +293,12 @@ Avalable arguments in command line:
281
293
  - `user` : user for RQM login.
282
294
  - `password` : user password for RQM login.
283
295
  - `testplan` : RQM testplan ID.
296
+ - `--testsuite` : RQM testsuite ID. If value is 'new', then create a new testsuite for this execution.
284
297
  - `--recursive` : if True, then the path is searched recursively for log files to be imported.
285
298
  - `--createmissing` : if True, then all testcases without tcid are created when importing.
286
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.
287
302
 
288
303
  **Arguments:**
289
304
 
@@ -312,6 +327,8 @@ Avalable arguments in command line:
312
327
  cmdParser.add_argument('password', type=str, help='password for RQM login.')
313
328
  cmdParser.add_argument('testplan', type=str,
314
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.")
315
332
  cmdParser.add_argument('--recursive',action="store_true",
316
333
  help='if set, then the path is searched recursively for log files to be imported.')
317
334
  cmdParser.add_argument('--createmissing', action="store_true",
@@ -320,6 +337,10 @@ Avalable arguments in command line:
320
337
  help='if set, then testcase information on RQM will be updated bases on robot testfile.')
321
338
  cmdParser.add_argument('--dryrun',action="store_true",
322
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.')
323
344
 
324
345
  return cmdParser.parse_args()
325
346
 
@@ -395,7 +416,7 @@ Extract metadata from suite result bases on DEFAULT_METADATA.
395
416
 
396
417
  return dMetadata
397
418
 
398
- def process_suite(RQMClient, suite):
419
+ def process_suite(RQMClient, suite, log_indent=0):
399
420
  """
400
421
  Process robot suite for importing to RQM.
401
422
 
@@ -413,15 +434,21 @@ Process robot suite for importing to RQM.
413
434
 
414
435
  Robot suite object.
415
436
 
437
+ * ``log_indent``
438
+
439
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
440
+
441
+ Indent for logging message.
442
+
416
443
  **Returns:**
417
444
 
418
445
  (*no returns*)
419
446
  """
420
447
  if len(list(suite.suites)) > 0:
421
448
  for subsuite in suite.suites:
422
- process_suite(RQMClient, subsuite)
449
+ process_suite(RQMClient, subsuite, log_indent=log_indent+2)
423
450
  else:
424
- Logger.log(f"Process suite: {suite.name}")
451
+ Logger.log(f"Process suite: {suite.name}", indent=log_indent)
425
452
 
426
453
  # update missing metadata from parent suite
427
454
  if suite.parent and suite.parent.metadata:
@@ -431,9 +458,9 @@ Process robot suite for importing to RQM.
431
458
 
432
459
  if len(list(suite.tests)) > 0:
433
460
  for test in suite.tests:
434
- process_test(RQMClient, test)
461
+ process_test(RQMClient, test, log_indent=log_indent+2)
435
462
 
436
- def process_test(RQMClient, test):
463
+ def process_test(RQMClient, test, log_indent=0):
437
464
  """
438
465
  Process robot test for importing to RQM.
439
466
 
@@ -451,11 +478,17 @@ Process robot test for importing to RQM.
451
478
 
452
479
  Robot test object.
453
480
 
481
+ * ``log_indent``
482
+
483
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
484
+
485
+ Indent for logging message.
486
+
454
487
  **Returns:**
455
488
 
456
489
  (*no returns*)
457
490
  """
458
- Logger.log(f"Process test: {test.name}")
491
+ Logger.log(f"Process test: {test.name}", indent=log_indent)
459
492
 
460
493
  # Avoid create resources with dryrun
461
494
  if Logger.dryrun:
@@ -485,7 +518,7 @@ Process robot test for importing to RQM.
485
518
  try:
486
519
  _tc_result = DRESULT_MAPPING[test.status]
487
520
  except Exception:
488
- 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)
489
522
  return
490
523
  _tc_message = test.message
491
524
  _tc_start_time = convert_to_datetime(test.starttime)
@@ -509,19 +542,19 @@ Process robot test for importing to RQM.
509
542
  res = RQMClient.createResource('testcase', oTCTemplate)
510
543
  if res['success']:
511
544
  _tc_id = res['id']
512
- 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)
513
546
  RQMClient.dMappingTCID[_tc_id] = _tc_name
514
547
  else:
515
- 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)
516
549
  return
517
550
  else:
518
- 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)
519
552
  return
520
553
  else:
521
554
  # If more than 1 tcid are defined in [Tags], the first one is used.
522
555
  if len(lTCIDTags) > 1:
523
556
  _tc_id = lTCIDTags[0]
524
- 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)
525
558
 
526
559
  # If --updatetestcase is set. Test case with provided tcid will be updated on RQM:
527
560
  # Get existing resource of testcase from RQM.
@@ -538,9 +571,9 @@ Process robot test for importing to RQM.
538
571
  _tc_link,
539
572
  sTCtemplate=str(resTC.text))
540
573
  RQMClient.updateResourceByID('testcase', _tc_id, oTCTemplate)
541
- 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)
542
575
  else:
543
- 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)
544
577
  return
545
578
 
546
579
  # Create TCER:
@@ -555,11 +588,11 @@ Process robot test for importing to RQM.
555
588
  res = RQMClient.createResource('executionworkitem', oTCERTemplate)
556
589
  _tc_tcer_id = res['id']
557
590
  if res['success']:
558
- 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)
559
592
  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}'")
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)
561
594
  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']}.")
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)
563
596
  return
564
597
 
565
598
  if _tc_tcer_id not in RQMClient.lTCERIDs:
@@ -584,10 +617,10 @@ Process robot test for importing to RQM.
584
617
  _tc_team)
585
618
  res = RQMClient.createResource('executionresult', oTCResultTemplate)
586
619
  if res['success']:
587
- 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)
588
621
  _tc_result_id = res['id']
589
622
  else:
590
- 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)
591
624
  return
592
625
  if _tc_result_id not in RQMClient.lTCResultIDs:
593
626
  RQMClient.lTCResultIDs.append(_tc_result_id)
@@ -595,6 +628,10 @@ Process robot test for importing to RQM.
595
628
  # Append lTestcaseIDs (for linking testplan/testsuite)
596
629
  if _tc_id not in RQMClient.lTestcaseIDs:
597
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)
598
635
 
599
636
  def RobotLog2RQM(args=None):
600
637
  """
@@ -622,11 +659,13 @@ Flow to import Robot results to RQM:
622
659
  * `user` : user for RQM login.
623
660
  * `password` : user password for RQM login.
624
661
  * `testplan` : RQM testplan ID.
662
+ * `testsuite` : testsuite ID for this execution. If 'new', then create a new testsuite for this execution.
625
663
  * `recursive` : if True, then the path is searched recursively for log files to be imported.
626
664
  * `createmissing` : if True, then all testcases without tcid are created when importing.
627
665
  * `updatetestcase` : if True, then testcases information on RQM will be updated bases on robot testfile.
628
666
  * `dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
629
-
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.
630
669
  **Returns:**
631
670
 
632
671
  (*no returns*)
@@ -676,6 +715,7 @@ Flow to import Robot results to RQM:
676
715
  try:
677
716
  bSuccess = RQMClient.login()
678
717
  if bSuccess:
718
+ Logger.log()
679
719
  Logger.log(f"Login RQM as user '{args.user}' successfully!")
680
720
  else:
681
721
  Logger.log_error("Could not login to RQM: 'Unkown reason'.")
@@ -694,14 +734,85 @@ Flow to import Robot results to RQM:
694
734
  metadata_info['version_sw'] = None
695
735
  metadata_info['project'] = None
696
736
  RQMClient.config(args.testplan, metadata_info['version_sw'],
697
- 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
+
698
757
  # Process suite for importing
699
758
  process_suite(RQMClient, result.suite)
700
759
 
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)
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)
704
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
+
705
816
  # Update testcase(s) with generated ID(s)
706
817
  # Under developing
707
818
 
@@ -710,7 +821,15 @@ Flow to import Robot results to RQM:
710
821
 
711
822
  # 5. Disconnect from RQM
712
823
  RQMClient.disconnect()
713
- 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}")
714
833
 
715
834
  if __name__=="__main__":
716
835
  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.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.4
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
@@ -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=GbxmrYflkWbY0Fo2BVh3GjMlnXcrGAH43ML-wKBXZgQ,61275
2
+ RobotLog2RQM/RobotLog2RQM.pdf,sha256=NThOpMfAIV1uVVREK1vCrUv2a6dyBNB35fKsi_RB8dA,317424
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=ZcLWAtxdNQU8emzwodaV2pl62VKatpVJOo5CF-JDw64,29645
6
+ RobotLog2RQM/version.py,sha256=5VtcZiEhD8N84tPIQQGf2bUl3sghf68iEDTEdA4K7Q8,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.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ robotframework_robotlog2rqm-1.4.0.dist-info/METADATA,sha256=ulY-9zs6RoMKgk0mx9QkVt_QEvSxkMy2DpqbuxJTeZc,9951
17
+ robotframework_robotlog2rqm-1.4.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
18
+ robotframework_robotlog2rqm-1.4.0.dist-info/entry_points.txt,sha256=-Xug2kDJW2LtcSADEVPtCwa337twCy2iGh5aK7xApHA,73
19
+ robotframework_robotlog2rqm-1.4.0.dist-info/top_level.txt,sha256=jb_Gt6W44FoOLtGfBe7RzqCLaquhihkEWvSI1zjXDHc,13
20
+ robotframework_robotlog2rqm-1.4.0.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