robotframework-robotlog2rqm 1.4.0__tar.gz → 1.4.2__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.4.0 → robotframework-robotlog2rqm-1.4.2}/PKG-INFO +13 -20
  2. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/CRQM.py +135 -44
  3. robotframework-robotlog2rqm-1.4.2/RobotLog2RQM/RobotLog2RQM.pdf +0 -0
  4. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/robotlog2rqm.py +149 -21
  5. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/version.py +2 -3
  6. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/robotframework_robotlog2rqm.egg-info/PKG-INFO +13 -20
  7. robotframework-robotlog2rqm-1.4.0/RobotLog2RQM/RobotLog2RQM.pdf +0 -0
  8. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/LICENSE +0 -0
  9. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/README.rst +0 -0
  10. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/buildrecord.xml +0 -0
  11. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/configuration.xml +0 -0
  12. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/executionresult.xml +0 -0
  13. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/executionworkitem.xml +0 -0
  14. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml +0 -0
  15. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/testcase.xml +0 -0
  16. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/testsuite.xml +0 -0
  17. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/RQM_templates/testsuitelog.xml +0 -0
  18. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/__init__.py +0 -0
  19. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/RobotLog2RQM/__main__.py +0 -0
  20. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/robotframework_robotlog2rqm.egg-info/SOURCES.txt +0 -0
  21. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/robotframework_robotlog2rqm.egg-info/dependency_links.txt +0 -0
  22. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/robotframework_robotlog2rqm.egg-info/entry_points.txt +0 -0
  23. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/robotframework_robotlog2rqm.egg-info/requires.txt +0 -0
  24. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/robotframework_robotlog2rqm.egg-info/top_level.txt +0 -0
  25. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/setup.cfg +0 -0
  26. {robotframework-robotlog2rqm-1.4.0 → robotframework-robotlog2rqm-1.4.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.4.0
3
+ Version: 1.4.2
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
@@ -17,8 +17,7 @@ Requires-Python: >=3.0
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
 
20
- RobotLog2RQM Description
21
- ========================
20
+ # RobotLog2RQM Description
22
21
 
23
22
  The Python package **RobotLog2RQM** provides ability to import [Robot
24
23
  Framework test
@@ -38,14 +37,13 @@ Manager](https://www.ibm.com/support/knowledgecenter/SSYMRC_6.0.2/com.ibm.ration
38
37
  **RobotLog2RQM** tool is operating system independent and only works
39
38
  with Python 3.
40
39
 
41
- How to install
42
- --------------
40
+ ## How to install
43
41
 
44
42
  **RobotLog2RQM** can be installed in two different ways.
45
43
 
46
44
  1. Installation via PyPi (recommended for users)
47
45
 
48
- ``` {.}
46
+ ```
49
47
  pip install RobotLog2RQM
50
48
  ```
51
49
 
@@ -57,7 +55,7 @@ How to install
57
55
  - Clone the **robotframework-robotlog2rqm** repository to your
58
56
  machine.
59
57
 
60
- ``` {.}
58
+ ```
61
59
  git clone https://github.com/test-fullautomation/robotframework-robotlog2rqm.git
62
60
  ```
63
61
 
@@ -72,7 +70,7 @@ How to install
72
70
  packages you can find in the file `requirements.txt` in the
73
71
  repository root folder. Use pip to install them:
74
72
 
75
- ``` {.}
73
+ ```
76
74
  pip install -r ./requirements.txt
77
75
  ```
78
76
 
@@ -91,7 +89,7 @@ How to install
91
89
  to find **LaTeX**. This is defined in the **GenPackageDoc**
92
90
  configuration file
93
91
 
94
- ``` {.}
92
+ ```
95
93
  packagedoc\packagedoc_config.json
96
94
  ```
97
95
 
@@ -103,7 +101,7 @@ How to install
103
101
 
104
102
  - Use the following command to install **RobotLog2RQM**:
105
103
 
106
- ``` {.}
104
+ ```
107
105
  python setup.py install
108
106
  ```
109
107
 
@@ -114,8 +112,7 @@ be available (under *Scripts* folder of Python on Windows and
114
112
  In case above location is added to **PATH** environment variable then
115
113
  you can run it directly as operation system\'s command.
116
114
 
117
- How to use
118
- ----------
115
+ ## How to use
119
116
 
120
117
  **RobotLog2RQM** tool requires the Robot Framework `output.xml` result
121
118
  file(s) which will be imported, RQM information(e.g. host url, project,
@@ -219,8 +216,7 @@ cases and their results are reflected on RQM, please refer to
219
216
  [RobotLog2RQM tool's
220
217
  Documentation](https://github.com/test-fullautomation/robotframework-robotlog2rqm/blob/develop/RobotLog2RQM/RobotLog2RQM.pdf).
221
218
 
222
- Feedback
223
- --------
219
+ ## Feedback
224
220
 
225
221
  To give us a feedback, you can send an email to [Thomas
226
222
  Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com).
@@ -228,15 +224,13 @@ Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com).
228
224
  In case you want to report a bug or request any interesting feature,
229
225
  please don\'t hesitate to raise a ticket.
230
226
 
231
- Maintainers
232
- -----------
227
+ ## Maintainers
233
228
 
234
229
  [Thomas Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com)
235
230
 
236
231
  [Tran Duy Ngoan](mailto:Ngoan.TranDuy@vn.bosch.com)
237
232
 
238
- Contributors
239
- ------------
233
+ ## Contributors
240
234
 
241
235
  [Nguyen Huynh Tri Cuong](mailto:Cuong.NguyenHuynhTri@vn.bosch.com)
242
236
 
@@ -246,8 +240,7 @@ Contributors
246
240
 
247
241
  [Holger Queckenstedt](mailto:Holger.Queckenstedt@de.bosch.com)
248
242
 
249
- License
250
- -------
243
+ ## License
251
244
 
252
245
  Copyright 2020-2024 Robert Bosch GmbH
253
246
 
@@ -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,17 +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 = {
199
- "id": None,
200
- "name": None
201
- }
202
- self.stream = None
203
- self.baseline = 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
204
223
 
205
224
  def login(self):
206
225
  """
@@ -290,8 +309,8 @@ Disconnect from RQM.
290
309
  self.session.close()
291
310
 
292
311
  def config(self, plan_id, build_name=None, config_name=None,
293
- createmissing=False, updatetestcase=False, suite_id=None,
294
- stream=None, baseline=None):
312
+ createmissing=False, updatetestcase=False, suite_id=None,
313
+ stream=None, baseline=None, naming_convention=None):
295
314
  """
296
315
  Configure RQMClient with testplan ID, build, configuration, createmissing, ...
297
316
 
@@ -344,11 +363,15 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
344
363
  (*no returns*)
345
364
  """
346
365
  try:
366
+ self.naming_convention = self.NAMING_CONVENTION
367
+ if naming_convention:
368
+ self.naming_convention.update(naming_convention)
369
+
347
370
  self.createmissing = createmissing
348
371
  self.updatetestcase = updatetestcase
349
- self.testplan = plan_id
350
- self.testsuite['id'] = suite_id
351
-
372
+ self.testplan.id = plan_id
373
+ self.testsuite.id = suite_id
374
+
352
375
  # Add Configuration-Context header information due to given stream or baseline
353
376
  if stream:
354
377
  res = self.getAllByResource('stream')
@@ -357,12 +380,13 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
357
380
  bFoundStream = False
358
381
  for stream_id, stream_name in dStreams.items():
359
382
  if stream_name == stream:
360
- self.stream = stream_id
383
+ self.stream.id = stream_id
384
+ self.stream.name = stream_name
361
385
  bFoundStream = True
362
386
  self.headers['Configuration-Context'] = stream_id
363
387
  self.session.headers = self.headers
364
388
  break
365
-
389
+
366
390
  if not bFoundStream:
367
391
  raise Exception(f"Cannot found given stream '{stream}'")
368
392
  else:
@@ -374,12 +398,13 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
374
398
  bFoundBaseline = False
375
399
  for baseline_id, baseline_name in dBaselines.items():
376
400
  if baseline_name == baseline:
377
- self.baseline = baseline_id
401
+ self.baseline.id = baseline_id
402
+ self.baseline.name = baseline_name
378
403
  bFoundBaseline = True
379
404
  self.headers['Configuration-Context'] = baseline_id
380
405
  self.session.headers = self.headers
381
406
  break
382
-
407
+
383
408
  if not bFoundBaseline:
384
409
  raise Exception(f"Cannot found given baseline '{baseline}'")
385
410
  else:
@@ -390,6 +415,9 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
390
415
  if res_plan.status_code != 200:
391
416
  raise Exception('Testplan with ID %s is not existing!'%str(plan_id))
392
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
+
393
421
  # Verify and create build version if required
394
422
  if build_name != None:
395
423
  if build_name == '':
@@ -397,7 +425,8 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
397
425
  self.getAllBuildRecords()
398
426
  res_build = self.createBuildRecord(build_name)
399
427
  if res_build['success'] or res_build['status_code'] == "303":
400
- self.build = res_build['id']
428
+ self.build.id = res_build['id']
429
+ self.build.name = build_name
401
430
  else:
402
431
  raise Exception("Cannot create build '%s': %s"%
403
432
  (build_name, res_build['message']))
@@ -409,7 +438,8 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
409
438
  self.getAllConfigurations()
410
439
  res_conf = self.createConfiguration(config_name)
411
440
  if res_conf['success'] or res_conf['status_code'] == "303":
412
- self.configuration = res_conf['id']
441
+ self.configuration.id = res_conf['id']
442
+ self.configuration.name = config_name
413
443
  else:
414
444
  raise Exception("Cannot create configuration '%s': %s"%
415
445
  (config_name, res_conf['message']))
@@ -420,11 +450,11 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
420
450
  if res_suite.status_code != 200:
421
451
  raise Exception('Testsuite with ID %s is not existing!'%str(suite_id))
422
452
  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
453
+ self.testsuite.name = oTestsuite.find('ns4:title', oTestsuite.getroot().nsmap).text
424
454
 
425
455
  # get all team-areas for testcase template
426
456
  self.getAllTeamAreas()
427
-
457
+
428
458
 
429
459
  except Exception as error:
430
460
  raise Exception('Configure RQMClient failed: %s'%error)
@@ -454,7 +484,7 @@ Return interaction URL of provided userID
454
484
 
455
485
  def integrationURL(self, resourceType, id=None, forceinternalID=False):
456
486
  """
457
- Return interaction URL of provided reource and ID.
487
+ Return interaction URL of provided resource and ID.
458
488
  The provided ID can be internalID (contains only digits) or externalID.
459
489
 
460
490
  **Arguments:**
@@ -486,7 +516,7 @@ The provided ID can be internalID (contains only digits) or externalID.
486
516
 
487
517
  / *Type*: str /
488
518
 
489
- The interaction URL of provided reource and ID.
519
+ The interaction URL of provided resource and ID.
490
520
  """
491
521
  integrationURL = self.host + "/qm/service/com.ibm.rqm.integration.service.IIntegrationService/resources/" + \
492
522
  self.projectID + '/' + resourceType
@@ -538,7 +568,7 @@ Note:
538
568
  raise Exception("Cannot get ID from response. Reason: %s"%str(error))
539
569
  return resultId
540
570
 
541
- def webIDfromGeneratedID(self, resourrceType, generateID):
571
+ def webIDfromGeneratedID(self, resourceType, generateID):
542
572
  """
543
573
  Return web ID (ns2:webId) from generate ID by get resource data from RQM.
544
574
 
@@ -549,7 +579,7 @@ Note:
549
579
 
550
580
  **Arguments:**
551
581
 
552
- * ``resourrceType``
582
+ * ``resourceType``
553
583
 
554
584
  / *Condition*: required / *Type*: str /
555
585
 
@@ -583,8 +613,8 @@ Note:
583
613
  'testscript',
584
614
  'testsuite',
585
615
  'testsuitelog']
586
- if resourrceType in lSupportedResources:
587
- resResource = self.getResourceByID(resourrceType, generateID)
616
+ if resourceType in lSupportedResources:
617
+ resResource = self.getResourceByID(resourceType, generateID)
588
618
  if resResource.status_code == 200:
589
619
  oResource = get_xml_tree(BytesIO(str(resResource.text).encode()),
590
620
  bdtd_validation=False)
@@ -595,6 +625,61 @@ Note:
595
625
  raise Exception("Cannot get web ID of generated testcase!")
596
626
  return webID
597
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
+
598
683
  #
599
684
  # Methods to get resources
600
685
  #
@@ -605,7 +690,7 @@ Return data of provided resource and ID by GET method
605
690
 
606
691
  **Arguments:**
607
692
 
608
- * ``resourrceType``
693
+ * ``resourceType``
609
694
 
610
695
  / *Condition*: required / *Type*: str /
611
696
 
@@ -635,7 +720,7 @@ Return all entries (in all pages) of provided resource by GET method.
635
720
 
636
721
  **Arguments:**
637
722
 
638
- * ``resourrceType``
723
+ * ``resourceType``
639
724
 
640
725
  / *Condition*: required / *Type*: str /
641
726
 
@@ -897,7 +982,7 @@ Return testcase template from provided information.
897
982
  root = oTree.getroot()
898
983
  nsmap = root.nsmap
899
984
  # prepare required data for template
900
- testcaseTittle = testcaseName
985
+ testcaseTittle = self.__genResourceName('testcase', testcaseName)
901
986
 
902
987
  # find nodes to change data
903
988
  oTittle = oTree.find(f'{{{self.NAMESPACES["ns3"]}}}title')
@@ -1010,7 +1095,8 @@ Return testcase execution record template from provided information.
1010
1095
  root = oTree.getroot()
1011
1096
  nsmap = root.nsmap
1012
1097
  # prepare required data for template
1013
- TCERTittle = 'TCER: '+testcaseName
1098
+ TCERTittle = self.__genResourceName('tcer', testcaseName)
1099
+
1014
1100
 
1015
1101
  # Check tcid is internalid or externalid
1016
1102
  testcaseURL = self.integrationURL('testcase', testcaseID)
@@ -1156,7 +1242,7 @@ Return testcase execution result template from provided information.
1156
1242
  nsmap = root.nsmap
1157
1243
  # prepare required data for template
1158
1244
  prefixState = 'com.ibm.rqm.execution.common.state.'
1159
- resultTittle = 'Execution result: '+testcaseName
1245
+ resultTittle = self.__genResourceName('testresult', testcaseName)
1160
1246
  testcaseURL = self.integrationURL('testcase', testcaseID)
1161
1247
  testplanURL = self.integrationURL('testplan', testplanID)
1162
1248
  TCERURL = self.integrationURL('executionworkitem', TCERID)
@@ -1339,7 +1425,7 @@ Return testsuite execution record (TSER) template from provided configuration na
1339
1425
  oTree = get_xml_tree(sTemplatePath, bdtd_validation=False)
1340
1426
  root = oTree.getroot()
1341
1427
  # prepare required data for template
1342
- TSERTittle = 'TSER: ' + testsuiteName
1428
+ TSERTittle = self.__genResourceName('tser', testsuiteName)
1343
1429
  testsuiteURL = self.integrationURL('testsuite', testsuiteID)
1344
1430
  testplanURL = self.integrationURL('testplan', testplanID)
1345
1431
  testerURL = self.userURL(self.userID)
@@ -1376,7 +1462,7 @@ Return testsuite execution record (TSER) template from provided configuration na
1376
1462
 
1377
1463
  def createTestsuiteResultTemplate(self, testsuiteID, testsuiteName, TSERID,
1378
1464
  lTCER, lTCResults, resultState, startTime='',
1379
- endTime='', duration='', sOwnerID=''):
1465
+ endTime='', duration='', sOwnerID='', buildrecordID=''):
1380
1466
  """
1381
1467
  Return testsuite execution result template from provided configuration name.
1382
1468
 
@@ -1450,7 +1536,7 @@ Return testsuite execution result template from provided configuration name.
1450
1536
  prefixState = 'com.ibm.rqm.execution.common.state.'
1451
1537
 
1452
1538
  # prepare required data for template
1453
- resultTittle = 'Testsuite result: ' + testsuiteName
1539
+ resultTittle = self.__genResourceName('suiteresult', testsuiteName)
1454
1540
  testsuiteURL = self.integrationURL('testsuite', testsuiteID)
1455
1541
  TSERURL = self.integrationURL('suiteexecutionrecord', TSERID)
1456
1542
  testerURL = self.userURL(self.userID)
@@ -1495,7 +1581,7 @@ Return testsuite execution result template from provided configuration name.
1495
1581
  oState.text = prefixState + 'inconclusive'
1496
1582
  if resultState.lower() in self.RESULT_STATES:
1497
1583
  oState.text = prefixState +resultState.lower()
1498
-
1584
+
1499
1585
  for idx, sTCER in enumerate(lTCER):
1500
1586
  sTCERURL = self.integrationURL('executionworkitem', sTCER)
1501
1587
  oSuiteElem = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/tsl/v0.1/}suiteelement', nsmap=nsmap)
@@ -1513,6 +1599,12 @@ Return testsuite execution result template from provided configuration name.
1513
1599
  oExecutionResult.set('href', sTCResultURL)
1514
1600
  root.append(oExecutionResult)
1515
1601
 
1602
+ if buildrecordID:
1603
+ oBuildRecord = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/}buildrecord', nsmap=nsmap)
1604
+ buildrecordURL = self.integrationURL('buildrecord', buildrecordID)
1605
+ oBuildRecord.set('href', buildrecordURL)
1606
+ root.append(oBuildRecord)
1607
+
1516
1608
  # return xml template as string
1517
1609
  sTSResultxml = etree.tostring(oTree)
1518
1610
  return sTSResultxml
@@ -1576,7 +1668,7 @@ Return testcase template from provided information.
1576
1668
  oOwner = oTree.find('ns6:owner', nsmap)
1577
1669
 
1578
1670
  # change nodes's data
1579
- oTittle.text = testsuiteName
1671
+ oTittle.text = self.__genResourceName('testsuite', testsuiteName)
1580
1672
  oDescription.text = sDescription
1581
1673
 
1582
1674
  # Incase not specify owner in template or input data, set it as provided user in cli
@@ -1774,7 +1866,6 @@ Create new configuration - test environment.
1774
1866
  """
1775
1867
  returnObj = {'success' : False, 'id': None, 'message': '', 'status_code': ''}
1776
1868
  # check existing build record in this executioon
1777
- sConfID = ''
1778
1869
  if (sConfigurationName not in self.dConfiguation.values()) or forceCreate:
1779
1870
  sConfTemplate = self.createConfigurationTemplate(sConfigurationName)
1780
1871
  returnObj = self.createResource('configuration', sConfTemplate)
@@ -1977,7 +2068,7 @@ Add testsuite ID to provided testplan ID
1977
2068
 
1978
2069
  Testsuite to be linked with given testplan.
1979
2070
 
1980
- If not provide, `testsuite['id']` value will be used as id of testsuite.
2071
+ If not provide, `testsuite.id` value will be used as id of testsuite.
1981
2072
 
1982
2073
  **Returns:**
1983
2074
 
@@ -1999,7 +2090,7 @@ Add testsuite ID to provided testplan ID
1999
2090
  """
2000
2091
  returnObj = {'success' : False, 'message': ''}
2001
2092
  if testsuiteID == None:
2002
- testsuiteID = self.testsuite['id']
2093
+ testsuiteID = self.testsuite.id
2003
2094
  if testsuiteID:
2004
2095
  resTestplanData = self.getResourceByID('testplan', testplanID)
2005
2096
  oTree = get_xml_tree(BytesIO(str(resTestplanData.text).encode()),bdtd_validation=False)
@@ -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
  #
@@ -281,6 +291,108 @@ Convert time string to datetime.
281
291
  dt=datetime.datetime(tp[0],tp[1],tp[2],tp[3],tp[4],tp[5],tp[6])
282
292
  return dt
283
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
+
284
396
  def __process_commandline():
285
397
  """
286
398
  Process provided argument(s) from command line.
@@ -296,6 +408,8 @@ Avalable arguments in command line:
296
408
  - `--testsuite` : RQM testsuite ID. If value is 'new', then create a new testsuite for this execution.
297
409
  - `--recursive` : if True, then the path is searched recursively for log files to be imported.
298
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.
299
413
  - `--dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
300
414
  - `--stream` : project stream. Note, requires Configuration Management (CM) to be enabled for the project area.
301
415
  - `--baseline` : project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.
@@ -327,7 +441,7 @@ Avalable arguments in command line:
327
441
  cmdParser.add_argument('password', type=str, help='password for RQM login.')
328
442
  cmdParser.add_argument('testplan', type=str,
329
443
  help='testplan ID for this execution.')
330
- cmdParser.add_argument('--testsuite', type=str,
444
+ cmdParser.add_argument('--testsuite', type=str,
331
445
  help="testsuite ID for this execution. If 'new', then create a new testsuite for this execution.")
332
446
  cmdParser.add_argument('--recursive',action="store_true",
333
447
  help='if set, then the path is searched recursively for log files to be imported.')
@@ -335,6 +449,8 @@ Avalable arguments in command line:
335
449
  help='if set, then all testcases without tcid are created when importing.')
336
450
  cmdParser.add_argument('--updatetestcase', action="store_true",
337
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.')
338
454
  cmdParser.add_argument('--dryrun',action="store_true",
339
455
  help='if set, then verify all input arguments (includes RQM authentication) and show what would be done.')
340
456
  cmdParser.add_argument('--stream', type=str,
@@ -507,9 +623,9 @@ Process robot test for importing to RQM.
507
623
  _tc_cmpt = metadata_info['component']
508
624
  _tc_team = metadata_info['team-area']
509
625
  # from RQMClient
510
- _tc_testplan_id = RQMClient.testplan
511
- _tc_config_id = RQMClient.configuration
512
- _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
513
629
  _tc_createmissing = RQMClient.createmissing
514
630
  _tc_update = RQMClient.updatetestcase
515
631
  # from robot result object
@@ -628,7 +744,7 @@ Process robot test for importing to RQM.
628
744
  # Append lTestcaseIDs (for linking testplan/testsuite)
629
745
  if _tc_id not in RQMClient.lTestcaseIDs:
630
746
  RQMClient.lTestcaseIDs.append(_tc_id)
631
-
747
+
632
748
  # Collect starttime and endtime for testsuite log creation
633
749
  RQMClient.lStartTimes.append(_tc_start_time)
634
750
  RQMClient.lEndTimes.append(_tc_end_time)
@@ -663,6 +779,7 @@ Flow to import Robot results to RQM:
663
779
  * `recursive` : if True, then the path is searched recursively for log files to be imported.
664
780
  * `createmissing` : if True, then all testcases without tcid are created when importing.
665
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.
666
783
  * `dryrun` : if True, then verify all input arguments (includes RQM authentication) and show what would be done.
667
784
  * `stream` : project stream. Note, requires Configuration Management (CM) to be enabled for the project area.
668
785
  * `baseline` : project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.
@@ -710,6 +827,15 @@ Flow to import Robot results to RQM:
710
827
  result = ExecutionResult(*sources)
711
828
  result.configure()
712
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
+
713
839
  # 3. Login Rational Quality Management (RQM)
714
840
  RQMClient = CRQMClient(args.user, args.password, args.project, args.host)
715
841
  try:
@@ -718,7 +844,7 @@ Flow to import Robot results to RQM:
718
844
  Logger.log()
719
845
  Logger.log(f"Login RQM as user '{args.user}' successfully!")
720
846
  else:
721
- Logger.log_error("Could not login to RQM: 'Unkown reason'.")
847
+ Logger.log_error("Could not login to RQM: 'Unknown reason'.")
722
848
  except Exception as reason:
723
849
  Logger.log_error(f"Could not login to RQM: '{str(reason)}'.")
724
850
 
@@ -734,8 +860,9 @@ Flow to import Robot results to RQM:
734
860
  metadata_info['version_sw'] = None
735
861
  metadata_info['project'] = None
736
862
  RQMClient.config(args.testplan, metadata_info['version_sw'],
737
- metadata_info['project'], args.createmissing, args.updatetestcase,
738
- args.testsuite, stream=args.stream, baseline=args.baseline)
863
+ metadata_info['project'], args.createmissing, args.updatetestcase,
864
+ args.testsuite, stream=args.stream, baseline=args.baseline,
865
+ naming_convention=dNamingConvention)
739
866
 
740
867
  if args.testsuite == "new":
741
868
  # Create new testsuite
@@ -749,18 +876,18 @@ Flow to import Robot results to RQM:
749
876
  if res_testsuite['success']:
750
877
  _ts_id = res_testsuite['id']
751
878
  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
879
+ RQMClient.testsuite.id = _ts_id
880
+ RQMClient.testsuite.name = result.suite.name
754
881
  else:
755
882
  Logger.log_error(f"Create testsuite '{result.suite.name}' failed. Reason: {res_testsuite['message']}", fatal_error=True)
756
883
 
757
884
  # Process suite for importing
758
885
  process_suite(RQMClient, result.suite)
759
886
 
760
- if RQMClient.testsuite['id']:
887
+ if RQMClient.testsuite.id:
761
888
  if not args.dryrun:
762
889
  # Create testsuite execution record if requires
763
- testsuite_record_data = RQMClient.createTSERTemplate(RQMClient.testsuite['id'], RQMClient.testsuite['name'], args.testplan, RQMClient.configuration)
890
+ testsuite_record_data = RQMClient.createTSERTemplate(RQMClient.testsuite.id, RQMClient.testsuite.name, args.testplan, RQMClient.configuration.id)
764
891
  res_TSER = RQMClient.createResource('suiteexecutionrecord', testsuite_record_data)
765
892
  sTSERID = res_TSER['id']
766
893
  Logger.log()
@@ -769,20 +896,21 @@ Flow to import Robot results to RQM:
769
896
  elif (res_TSER['status_code'] == 303 or res_TSER['status_code'] == 200) and res_TSER['id'] != '':
770
897
  ### incase executionworkitem is existing, cannot create new one
771
898
  ### 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}")
899
+ Logger.log_warning(f"TSER for testsuite {RQMClient.testsuite.id} is existing.\nAdd this execution result to existing TSER id: {sTSERID}")
773
900
  else:
774
901
  Logger.log_error(f"Create TSER failed, {res_TSER['message']}")
775
902
 
776
903
  # Create new testsuite result and link all TCERs
777
- testsuite_result_data = RQMClient.createTestsuiteResultTemplate(RQMClient.testsuite['id'],
778
- RQMClient.testsuite['name'],
904
+ testsuite_result_data = RQMClient.createTestsuiteResultTemplate(RQMClient.testsuite.id,
905
+ RQMClient.testsuite.name,
779
906
  sTSERID,
780
907
  RQMClient.lTCERIDs,
781
908
  RQMClient.lTCResultIDs,
782
- DRESULT_MAPPING[result.suite.status]
909
+ DRESULT_MAPPING[result.suite.status],
910
+ buildrecordID=RQMClient.build.id
783
911
  )
784
912
  res_TSLog = RQMClient.createResource('testsuitelog', testsuite_result_data)
785
- sSuiteResultID = res_TSLog['id']
913
+ sSuiteResultID = res_TSLog['id']
786
914
  if res_TSLog['success']:
787
915
  Logger.log(f"Created testsuite result with id {sSuiteResultID} successfully.", indent=2)
788
916
  else:
@@ -793,15 +921,15 @@ Flow to import Robot results to RQM:
793
921
 
794
922
  # Link all imported testcase ID(s) with testsuite
795
923
  try:
796
- RQMClient.linkListTestcase2Testsuite(RQMClient.testsuite['id'])
797
- Logger.log(f"Link all imported test cases with testsuite {RQMClient.testsuite['id']} successfully.")
924
+ RQMClient.linkListTestcase2Testsuite(RQMClient.testsuite.id)
925
+ Logger.log(f"Link all imported test cases with testsuite {RQMClient.testsuite.id} successfully.")
798
926
  except Exception as reason:
799
927
  Logger.log_error(f"Link all imported test cases with testsuite failed.\nReason: {reason}", fatal_error=True)
800
928
 
801
929
  # Add testsuite to given testplan
802
930
  try:
803
931
  RQMClient.addTestsuite2Testplan(args.testplan)
804
- Logger.log(f"Add testsuite {RQMClient.testsuite['id']} to testplan {args.testplan} successfully.")
932
+ Logger.log(f"Add testsuite {RQMClient.testsuite.id} to testplan {args.testplan} successfully.")
805
933
  except Exception as reason:
806
934
  Logger.log_error(f"Add testsuite to testplan failed.\nReason: {reason}", fatal_error=True)
807
935
 
@@ -812,7 +940,7 @@ Flow to import Robot results to RQM:
812
940
  Logger.log(f"Link all imported test cases with testplan {args.testplan} successfully.")
813
941
  except Exception as reason:
814
942
  Logger.log_error(f"Link all imported test cases with testplan failed.\nReason: {reason}", fatal_error=True)
815
-
943
+
816
944
  # Update testcase(s) with generated ID(s)
817
945
  # Under developing
818
946
 
@@ -18,6 +18,5 @@
18
18
  #
19
19
  # Version and date of RobotLog2RQM
20
20
  #
21
- VERSION = "1.4.0"
22
- VERSION_DATE = "24.07.2024"
23
-
21
+ VERSION = "1.4.2"
22
+ VERSION_DATE = "18.02.2025"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.4.0
3
+ Version: 1.4.2
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
@@ -17,8 +17,7 @@ Requires-Python: >=3.0
17
17
  Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
 
20
- RobotLog2RQM Description
21
- ========================
20
+ # RobotLog2RQM Description
22
21
 
23
22
  The Python package **RobotLog2RQM** provides ability to import [Robot
24
23
  Framework test
@@ -38,14 +37,13 @@ Manager](https://www.ibm.com/support/knowledgecenter/SSYMRC_6.0.2/com.ibm.ration
38
37
  **RobotLog2RQM** tool is operating system independent and only works
39
38
  with Python 3.
40
39
 
41
- How to install
42
- --------------
40
+ ## How to install
43
41
 
44
42
  **RobotLog2RQM** can be installed in two different ways.
45
43
 
46
44
  1. Installation via PyPi (recommended for users)
47
45
 
48
- ``` {.}
46
+ ```
49
47
  pip install RobotLog2RQM
50
48
  ```
51
49
 
@@ -57,7 +55,7 @@ How to install
57
55
  - Clone the **robotframework-robotlog2rqm** repository to your
58
56
  machine.
59
57
 
60
- ``` {.}
58
+ ```
61
59
  git clone https://github.com/test-fullautomation/robotframework-robotlog2rqm.git
62
60
  ```
63
61
 
@@ -72,7 +70,7 @@ How to install
72
70
  packages you can find in the file `requirements.txt` in the
73
71
  repository root folder. Use pip to install them:
74
72
 
75
- ``` {.}
73
+ ```
76
74
  pip install -r ./requirements.txt
77
75
  ```
78
76
 
@@ -91,7 +89,7 @@ How to install
91
89
  to find **LaTeX**. This is defined in the **GenPackageDoc**
92
90
  configuration file
93
91
 
94
- ``` {.}
92
+ ```
95
93
  packagedoc\packagedoc_config.json
96
94
  ```
97
95
 
@@ -103,7 +101,7 @@ How to install
103
101
 
104
102
  - Use the following command to install **RobotLog2RQM**:
105
103
 
106
- ``` {.}
104
+ ```
107
105
  python setup.py install
108
106
  ```
109
107
 
@@ -114,8 +112,7 @@ be available (under *Scripts* folder of Python on Windows and
114
112
  In case above location is added to **PATH** environment variable then
115
113
  you can run it directly as operation system\'s command.
116
114
 
117
- How to use
118
- ----------
115
+ ## How to use
119
116
 
120
117
  **RobotLog2RQM** tool requires the Robot Framework `output.xml` result
121
118
  file(s) which will be imported, RQM information(e.g. host url, project,
@@ -219,8 +216,7 @@ cases and their results are reflected on RQM, please refer to
219
216
  [RobotLog2RQM tool's
220
217
  Documentation](https://github.com/test-fullautomation/robotframework-robotlog2rqm/blob/develop/RobotLog2RQM/RobotLog2RQM.pdf).
221
218
 
222
- Feedback
223
- --------
219
+ ## Feedback
224
220
 
225
221
  To give us a feedback, you can send an email to [Thomas
226
222
  Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com).
@@ -228,15 +224,13 @@ Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com).
228
224
  In case you want to report a bug or request any interesting feature,
229
225
  please don\'t hesitate to raise a ticket.
230
226
 
231
- Maintainers
232
- -----------
227
+ ## Maintainers
233
228
 
234
229
  [Thomas Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com)
235
230
 
236
231
  [Tran Duy Ngoan](mailto:Ngoan.TranDuy@vn.bosch.com)
237
232
 
238
- Contributors
239
- ------------
233
+ ## Contributors
240
234
 
241
235
  [Nguyen Huynh Tri Cuong](mailto:Cuong.NguyenHuynhTri@vn.bosch.com)
242
236
 
@@ -246,8 +240,7 @@ Contributors
246
240
 
247
241
  [Holger Queckenstedt](mailto:Holger.Queckenstedt@de.bosch.com)
248
242
 
249
- License
250
- -------
243
+ ## License
251
244
 
252
245
  Copyright 2020-2024 Robert Bosch GmbH
253
246