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