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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
RobotLog2RQM/CRQM.py CHANGED
@@ -147,7 +147,7 @@ Resource type mapping:
147
147
  "testresult" : "Execution result: {testcase}",
148
148
  "testsuite" : "{testsuite}",
149
149
  "tser" : "TSER: {testsuite}",
150
- "suiteresult" : "Testsuite result: {testsuite}"
150
+ "suiteresult" : "Testsuite result: {testsuite}"
151
151
  }
152
152
 
153
153
  def __init__(self, user, password, project, host):
@@ -309,7 +309,7 @@ Disconnect from RQM.
309
309
  self.session.close()
310
310
 
311
311
  def config(self, plan_id, build_name=None, config_name=None,
312
- createmissing=False, updatetestcase=False, suite_id=None,
312
+ createmissing=False, updatetestcase=False, suite_id=None,
313
313
  stream=None, baseline=None, naming_convention=None):
314
314
  """
315
315
  Configure RQMClient with testplan ID, build, configuration, createmissing, ...
@@ -371,7 +371,7 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
371
371
  self.updatetestcase = updatetestcase
372
372
  self.testplan.id = plan_id
373
373
  self.testsuite.id = suite_id
374
-
374
+
375
375
  # Add Configuration-Context header information due to given stream or baseline
376
376
  if stream:
377
377
  res = self.getAllByResource('stream')
@@ -386,7 +386,7 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
386
386
  self.headers['Configuration-Context'] = stream_id
387
387
  self.session.headers = self.headers
388
388
  break
389
-
389
+
390
390
  if not bFoundStream:
391
391
  raise Exception(f"Cannot found given stream '{stream}'")
392
392
  else:
@@ -404,7 +404,7 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
404
404
  self.headers['Configuration-Context'] = baseline_id
405
405
  self.session.headers = self.headers
406
406
  break
407
-
407
+
408
408
  if not bFoundBaseline:
409
409
  raise Exception(f"Cannot found given baseline '{baseline}'")
410
410
  else:
@@ -454,7 +454,7 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
454
454
 
455
455
  # get all team-areas for testcase template
456
456
  self.getAllTeamAreas()
457
-
457
+
458
458
 
459
459
  except Exception as error:
460
460
  raise Exception('Configure RQMClient failed: %s'%error)
@@ -849,6 +849,83 @@ Example:
849
849
  else:
850
850
  raise Exception(f"Could not get 'team-areas' of project '{self.projectname}'.")
851
851
 
852
+ def getTestsFromTestplan(self, testplan_id, artifact_types):
853
+ """
854
+ Get all test cases and test suites associated with a given test plan.
855
+
856
+ **Arguments:**
857
+
858
+ * ``testplan_id``
859
+
860
+ / *Condition*: required / *Type*: str /
861
+
862
+ The RQM test plan to get test artifact(s).
863
+
864
+ * ``artifact_types``
865
+
866
+ / *Condition*: required / *Type*: list /
867
+
868
+ List of artifact types (`testcase`, `testsuite`) for fetching.
869
+
870
+ **Returns:**
871
+
872
+ * / *Type*: dict /
873
+
874
+ A dictionary containing fetched artifacts:
875
+
876
+ .. code:: python
877
+
878
+ {
879
+ 'testcase': [{'id': ..., 'name': ..., 'url': ...}, ...],
880
+ 'testsuite': [{'id': ..., 'name': ..., 'url': ...}, ...]
881
+ }
882
+ """
883
+ ALLOW_ARTIFACT_TYPES = ['testcase', 'testsuite']
884
+ result = {}
885
+
886
+ if isinstance(artifact_types, str):
887
+ artifact_types = [artifact_types]
888
+ elif not isinstance(artifact_types, (list, tuple)):
889
+ raise TypeError(
890
+ f"Invalid type for 'artifact_types': expected str or list, got {type(artifact_types).__name__}."
891
+ )
892
+
893
+ for item in artifact_types:
894
+ artifact_type = item.lower()
895
+ if artifact_type not in ALLOW_ARTIFACT_TYPES:
896
+ raise ValueError(
897
+ f"Unsupported artifact type '{artifact_type}'. "
898
+ f"Please use one of the following: {', '.join(ALLOW_ARTIFACT_TYPES)}."
899
+ )
900
+ result[artifact_type] = []
901
+
902
+ res = self.getResourceByID('testplan', testplan_id)
903
+ if res.status_code != 200:
904
+ raise Exception(f"Failed to get testplan {testplan_id}: {res.reason}")
905
+
906
+ oTree = get_xml_tree(BytesIO(str(res.text).encode()), bdtd_validation=False)
907
+ root = oTree.getroot()
908
+ nsmap = root.nsmap
909
+
910
+ for item in artifact_types:
911
+ artifact_type = item.lower()
912
+ # Find all linked test artifact
913
+ for oTest in root.findall(f'.//ns2:{artifact_type}', nsmap):
914
+ href = oTest.attrib.get('href')
915
+ if href:
916
+ test_id = href.split('/')[-1]
917
+ # Get testcase name
918
+ test_res = self.getResourceByID(artifact_type, test_id)
919
+ if test_res.status_code == 200:
920
+ test_tree = get_xml_tree(BytesIO(str(test_res.text).encode()), bdtd_validation=False)
921
+ test_name = test_tree.find('ns4:title', test_tree.getroot().nsmap)
922
+ test_web_id = test_tree.find('ns2:webId', test_tree.getroot().nsmap)
923
+ test_url = test_tree.find('ns4:identifier', test_tree.getroot().nsmap)
924
+ result[artifact_type].append({'id': test_web_id.text,
925
+ 'name': test_name.text if test_name is not None else '',
926
+ 'url': test_url.text})
927
+
928
+ return result
852
929
 
853
930
  #
854
931
  # Methods to create XML template for resources
@@ -1096,7 +1173,7 @@ Return testcase execution record template from provided information.
1096
1173
  nsmap = root.nsmap
1097
1174
  # prepare required data for template
1098
1175
  TCERTittle = self.__genResourceName('tcer', testcaseName)
1099
-
1176
+
1100
1177
 
1101
1178
  # Check tcid is internalid or externalid
1102
1179
  testcaseURL = self.integrationURL('testcase', testcaseID)
@@ -1462,7 +1539,7 @@ Return testsuite execution record (TSER) template from provided configuration na
1462
1539
 
1463
1540
  def createTestsuiteResultTemplate(self, testsuiteID, testsuiteName, TSERID,
1464
1541
  lTCER, lTCResults, resultState, startTime='',
1465
- endTime='', duration='', sOwnerID=''):
1542
+ endTime='', duration='', sOwnerID='', buildrecordID=''):
1466
1543
  """
1467
1544
  Return testsuite execution result template from provided configuration name.
1468
1545
 
@@ -1581,7 +1658,7 @@ Return testsuite execution result template from provided configuration name.
1581
1658
  oState.text = prefixState + 'inconclusive'
1582
1659
  if resultState.lower() in self.RESULT_STATES:
1583
1660
  oState.text = prefixState +resultState.lower()
1584
-
1661
+
1585
1662
  for idx, sTCER in enumerate(lTCER):
1586
1663
  sTCERURL = self.integrationURL('executionworkitem', sTCER)
1587
1664
  oSuiteElem = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/tsl/v0.1/}suiteelement', nsmap=nsmap)
@@ -1599,6 +1676,12 @@ Return testsuite execution result template from provided configuration name.
1599
1676
  oExecutionResult.set('href', sTCResultURL)
1600
1677
  root.append(oExecutionResult)
1601
1678
 
1679
+ if buildrecordID:
1680
+ oBuildRecord = etree.Element('{http://jazz.net/xmlns/alm/qm/v0.1/}buildrecord', nsmap=nsmap)
1681
+ buildrecordURL = self.integrationURL('buildrecord', buildrecordID)
1682
+ oBuildRecord.set('href', buildrecordURL)
1683
+ root.append(oBuildRecord)
1684
+
1602
1685
  # return xml template as string
1603
1686
  sTSResultxml = etree.tostring(oTree)
1604
1687
  return sTSResultxml
Binary file
RobotLog2RQM/logger.py ADDED
@@ -0,0 +1,180 @@
1
+ # Copyright 2020-2023 Robert Bosch GmbH
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ******************************************************************************
15
+ #
16
+ # File: logging.py
17
+ #
18
+ # Initially created by Tran Duy Ngoan(RBVH/EMC51) / October 2025
19
+ #
20
+ # Logging class
21
+ #
22
+ # History:
23
+ #
24
+ # 2025-10-20:
25
+ # - initial version
26
+ #
27
+ # ******************************************************************************
28
+ import sys
29
+ import colorama as col
30
+ import os
31
+
32
+ class Logger():
33
+ """
34
+ Logger class for logging message.
35
+ """
36
+ output_logfile = None
37
+ output_console = True
38
+ color_normal = col.Fore.WHITE + col.Style.NORMAL
39
+ color_error = col.Fore.RED + col.Style.BRIGHT
40
+ color_warn = col.Fore.YELLOW + col.Style.BRIGHT
41
+ color_reset = col.Style.RESET_ALL + col.Fore.RESET + col.Back.RESET
42
+ prefix_warn = "WARN: "
43
+ prefix_error = "ERROR: "
44
+ prefix_fatalerror = "FATAL ERROR: "
45
+ prefix_all = ""
46
+ dryrun = False
47
+
48
+ @classmethod
49
+ def config(cls, output_console=True, output_logfile=None, dryrun=False):
50
+ """
51
+ Configure Logger class.
52
+
53
+ **Arguments:**
54
+
55
+ * ``output_console``
56
+
57
+ / *Condition*: optional / *Type*: bool / *Default*: True /
58
+
59
+ Write message to console output.
60
+
61
+ * ``output_logfile``
62
+
63
+ / *Condition*: optional / *Type*: str / *Default*: None /
64
+
65
+ Path to log file output.
66
+
67
+ * ``dryrun``
68
+
69
+ / *Condition*: optional / *Type*: bool / *Default*: True /
70
+
71
+ If set, a prefix as 'dryrun' is added for all messages.
72
+
73
+ **Returns:**
74
+
75
+ (*no returns*)
76
+ """
77
+ cls.output_console = output_console
78
+ cls.output_logfile = output_logfile
79
+ cls.dryrun = dryrun
80
+ if cls.dryrun:
81
+ cls.prefix_all = cls.color_warn + "DRYRUN " + cls.color_reset
82
+
83
+ @classmethod
84
+ def log(cls, msg='', color=None, indent=0):
85
+ """
86
+ Write log message to console/file output.
87
+
88
+ **Arguments:**
89
+
90
+ * ``msg``
91
+
92
+ / *Condition*: optional / *Type*: str / *Default*: '' /
93
+
94
+ Message which is written to output.
95
+
96
+ * ``color``
97
+
98
+ / *Condition*: optional / *Type*: str / *Default*: None /
99
+
100
+ Color style for the message.
101
+
102
+ * ``indent``
103
+
104
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
105
+
106
+ Offset indent.
107
+
108
+ **Returns:**
109
+
110
+ (*no returns*)
111
+ """
112
+ if color is None:
113
+ color = cls.color_normal
114
+ if cls.output_console:
115
+ print(cls.prefix_all + cls.color_reset + color + " "*indent + msg + cls.color_reset)
116
+ if cls.output_logfile is not None and os.path.isfile(cls.output_logfile):
117
+ with open(cls.output_logfile, 'a') as f:
118
+ f.write(" "*indent + msg)
119
+ return
120
+
121
+ @classmethod
122
+ def log_warning(cls, msg, indent=0):
123
+ """
124
+ Write warning message to console/file output.
125
+
126
+ **Arguments:**
127
+
128
+ * ``msg``
129
+
130
+ / *Condition*: required / *Type*: str /
131
+
132
+ Warning message which is written to output.
133
+
134
+ * ``indent``
135
+
136
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
137
+
138
+ Offset indent.
139
+
140
+ **Returns:**
141
+
142
+ (*no returns*)
143
+ """
144
+ cls.log(cls.prefix_warn+str(msg), cls.color_warn, indent)
145
+
146
+ @classmethod
147
+ def log_error(cls, msg, fatal_error=False, indent=0):
148
+ """
149
+ Write error message to console/file output.
150
+
151
+ * ``msg``
152
+
153
+ / *Condition*: required / *Type*: str /
154
+
155
+ Error message which is written to output.
156
+
157
+ * ``fatal_error``
158
+
159
+ / *Condition*: optional / *Type*: bool / *Default*: False /
160
+
161
+ If set, tool will terminate after logging error message.
162
+
163
+ * ``indent``
164
+
165
+ / *Condition*: optional / *Type*: int / *Default*: 0 /
166
+
167
+ Offset indent.
168
+
169
+ **Returns:**
170
+
171
+ (*no returns*)
172
+ """
173
+ prefix = cls.prefix_error
174
+ if fatal_error:
175
+ prefix = cls.prefix_fatalerror
176
+
177
+ cls.log(prefix+str(msg), cls.color_error, indent)
178
+ if fatal_error:
179
+ cls.log(f"{sys.argv[0]} has been stopped!", cls.color_error)
180
+ exit(1)
@@ -15,7 +15,7 @@
15
15
  #
16
16
  # File: robotlog2rqm.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 tool is used to parse the robot framework results output.xml
21
21
  # then import them into RQM - IBM Rational Quality Manager
@@ -31,13 +31,12 @@ import json
31
31
  import re
32
32
  import argparse
33
33
  import os
34
- import sys
35
34
  import datetime
36
- import colorama as col
37
35
 
38
36
  from robot.api import ExecutionResult
39
37
  from RobotLog2RQM.CRQM import CRQMClient
40
38
  from RobotLog2RQM.version import VERSION, VERSION_DATE
39
+ from RobotLog2RQM.logger import Logger
41
40
 
42
41
  DRESULT_MAPPING = {
43
42
  "PASS": "Passed",
@@ -74,160 +73,6 @@ NAMING_CONVENTION_SCHEMA = {
74
73
  "suiteresult" : str
75
74
  }
76
75
 
77
- #
78
- # Logger class
79
- #
80
- ########################################################################
81
- class Logger():
82
- """
83
- Logger class for logging message.
84
- """
85
- output_logfile = None
86
- output_console = True
87
- color_normal = col.Fore.WHITE + col.Style.NORMAL
88
- color_error = col.Fore.RED + col.Style.BRIGHT
89
- color_warn = col.Fore.YELLOW + col.Style.BRIGHT
90
- color_reset = col.Style.RESET_ALL + col.Fore.RESET + col.Back.RESET
91
- prefix_warn = "WARN: "
92
- prefix_error = "ERROR: "
93
- prefix_fatalerror = "FATAL ERROR: "
94
- prefix_all = ""
95
- dryrun = False
96
-
97
- @classmethod
98
- def config(cls, output_console=True, output_logfile=None, dryrun=False):
99
- """
100
- Configure Logger class.
101
-
102
- **Arguments:**
103
-
104
- * ``output_console``
105
-
106
- / *Condition*: optional / *Type*: bool / *Default*: True /
107
-
108
- Write message to console output.
109
-
110
- * ``output_logfile``
111
-
112
- / *Condition*: optional / *Type*: str / *Default*: None /
113
-
114
- Path to log file output.
115
-
116
- * ``dryrun``
117
-
118
- / *Condition*: optional / *Type*: bool / *Default*: True /
119
-
120
- If set, a prefix as 'dryrun' is added for all messages.
121
-
122
- **Returns:**
123
-
124
- (*no returns*)
125
- """
126
- cls.output_console = output_console
127
- cls.output_logfile = output_logfile
128
- cls.dryrun = dryrun
129
- if cls.dryrun:
130
- cls.prefix_all = cls.color_warn + "DRYRUN " + cls.color_reset
131
-
132
- @classmethod
133
- def log(cls, msg='', color=None, indent=0):
134
- """
135
- Write log message to console/file output.
136
-
137
- **Arguments:**
138
-
139
- * ``msg``
140
-
141
- / *Condition*: optional / *Type*: str / *Default*: '' /
142
-
143
- Message which is written to output.
144
-
145
- * ``color``
146
-
147
- / *Condition*: optional / *Type*: str / *Default*: None /
148
-
149
- Color style for the message.
150
-
151
- * ``indent``
152
-
153
- / *Condition*: optional / *Type*: int / *Default*: 0 /
154
-
155
- Offset indent.
156
-
157
- **Returns:**
158
-
159
- (*no returns*)
160
- """
161
- if color==None:
162
- color = cls.color_normal
163
- if cls.output_console:
164
- print(cls.prefix_all + cls.color_reset + color + " "*indent + msg + cls.color_reset)
165
- if cls.output_logfile!=None and os.path.isfile(cls.output_logfile):
166
- with open(cls.output_logfile, 'a') as f:
167
- f.write(" "*indent + msg)
168
- return
169
-
170
- @classmethod
171
- def log_warning(cls, msg, indent=0):
172
- """
173
- Write warning message to console/file output.
174
-
175
- **Arguments:**
176
-
177
- * ``msg``
178
-
179
- / *Condition*: required / *Type*: str /
180
-
181
- Warning message which is written to output.
182
-
183
- * ``indent``
184
-
185
- / *Condition*: optional / *Type*: int / *Default*: 0 /
186
-
187
- Offset indent.
188
-
189
- **Returns:**
190
-
191
- (*no returns*)
192
- """
193
- cls.log(cls.prefix_warn+str(msg), cls.color_warn, indent)
194
-
195
- @classmethod
196
- def log_error(cls, msg, fatal_error=False, indent=0):
197
- """
198
- Write error message to console/file output.
199
-
200
- * ``msg``
201
-
202
- / *Condition*: required / *Type*: str /
203
-
204
- Error message which is written to output.
205
-
206
- * ``fatal_error``
207
-
208
- / *Condition*: optional / *Type*: bool / *Default*: False /
209
-
210
- If set, tool will terminate after logging error message.
211
-
212
- * ``indent``
213
-
214
- / *Condition*: optional / *Type*: int / *Default*: 0 /
215
-
216
- Offset indent.
217
-
218
- **Returns:**
219
-
220
- (*no returns*)
221
- """
222
- prefix = cls.prefix_error
223
- if fatal_error:
224
- prefix = cls.prefix_fatalerror
225
-
226
- cls.log(prefix+str(msg), cls.color_error, indent)
227
- if fatal_error:
228
- cls.log(f"{sys.argv[0]} has been stopped!", cls.color_error)
229
- exit(1)
230
-
231
76
 
232
77
  def get_from_tags(lTags, reInfo):
233
78
  """
@@ -319,7 +164,7 @@ Parse and validate content of configuration file
319
164
  fatal_error=True)
320
165
 
321
166
  if not is_valid_config(dConfig, bExitOnFail=False):
322
- Logger.log_error(f"Error in naming configuration file '{path_file}'.",
167
+ Logger.log_error(f"Error in naming configuration file '{path_file}'.",
323
168
  fatal_error=True)
324
169
  return dConfig
325
170
 
@@ -441,7 +286,7 @@ Avalable arguments in command line:
441
286
  cmdParser.add_argument('password', type=str, help='password for RQM login.')
442
287
  cmdParser.add_argument('testplan', type=str,
443
288
  help='testplan ID for this execution.')
444
- cmdParser.add_argument('--testsuite', type=str,
289
+ cmdParser.add_argument('--testsuite', type=str,
445
290
  help="testsuite ID for this execution. If 'new', then create a new testsuite for this execution.")
446
291
  cmdParser.add_argument('--recursive',action="store_true",
447
292
  help='if set, then the path is searched recursively for log files to be imported.')
@@ -744,7 +589,7 @@ Process robot test for importing to RQM.
744
589
  # Append lTestcaseIDs (for linking testplan/testsuite)
745
590
  if _tc_id not in RQMClient.lTestcaseIDs:
746
591
  RQMClient.lTestcaseIDs.append(_tc_id)
747
-
592
+
748
593
  # Collect starttime and endtime for testsuite log creation
749
594
  RQMClient.lStartTimes.append(_tc_start_time)
750
595
  RQMClient.lEndTimes.append(_tc_end_time)
@@ -844,7 +689,7 @@ Flow to import Robot results to RQM:
844
689
  Logger.log()
845
690
  Logger.log(f"Login RQM as user '{args.user}' successfully!")
846
691
  else:
847
- Logger.log_error("Could not login to RQM: 'Unkown reason'.")
692
+ Logger.log_error("Could not login to RQM: 'Unknown reason'.")
848
693
  except Exception as reason:
849
694
  Logger.log_error(f"Could not login to RQM: '{str(reason)}'.")
850
695
 
@@ -860,7 +705,7 @@ Flow to import Robot results to RQM:
860
705
  metadata_info['version_sw'] = None
861
706
  metadata_info['project'] = None
862
707
  RQMClient.config(args.testplan, metadata_info['version_sw'],
863
- metadata_info['project'], args.createmissing, args.updatetestcase,
708
+ metadata_info['project'], args.createmissing, args.updatetestcase,
864
709
  args.testsuite, stream=args.stream, baseline=args.baseline,
865
710
  naming_convention=dNamingConvention)
866
711
 
@@ -906,10 +751,11 @@ Flow to import Robot results to RQM:
906
751
  sTSERID,
907
752
  RQMClient.lTCERIDs,
908
753
  RQMClient.lTCResultIDs,
909
- DRESULT_MAPPING[result.suite.status]
754
+ DRESULT_MAPPING[result.suite.status],
755
+ buildrecordID=RQMClient.build.id
910
756
  )
911
757
  res_TSLog = RQMClient.createResource('testsuitelog', testsuite_result_data)
912
- sSuiteResultID = res_TSLog['id']
758
+ sSuiteResultID = res_TSLog['id']
913
759
  if res_TSLog['success']:
914
760
  Logger.log(f"Created testsuite result with id {sSuiteResultID} successfully.", indent=2)
915
761
  else:
@@ -939,7 +785,7 @@ Flow to import Robot results to RQM:
939
785
  Logger.log(f"Link all imported test cases with testplan {args.testplan} successfully.")
940
786
  except Exception as reason:
941
787
  Logger.log_error(f"Link all imported test cases with testplan failed.\nReason: {reason}", fatal_error=True)
942
-
788
+
943
789
  # Update testcase(s) with generated ID(s)
944
790
  # Under developing
945
791
 
@@ -0,0 +1,316 @@
1
+ # Copyright 2020-2023 Robert Bosch GmbH
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ******************************************************************************
15
+ #
16
+ # File: rqmtool.py
17
+ #
18
+ # Initially created by Tran Duy Ngoan(RBVH/EMC51) / October 2025
19
+ #
20
+ # This tool is used to fetch RQM resources
21
+ #
22
+ # History:
23
+ #
24
+ # 2025-10-20:
25
+ # - initial version
26
+ #
27
+ # ******************************************************************************
28
+
29
+ import argparse
30
+ import os
31
+ import csv
32
+ import json
33
+
34
+ from RobotLog2RQM.CRQM import CRQMClient
35
+ from RobotLog2RQM.logger import Logger
36
+ from RobotLog2RQM.version import VERSION, VERSION_DATE
37
+
38
+ OUTPUT_FORMATS = ['json', 'csv']
39
+ ARTIFACT_TYPES = ['testcase', 'testsuite']
40
+
41
+ def __process_commandline():
42
+ """
43
+ Process provided argument(s) from command line.
44
+
45
+ **Arguments:**
46
+
47
+ (*no arguments*)
48
+
49
+ **Returns:**
50
+
51
+ / *Type*: `ArgumentParser` object /
52
+
53
+ ArgumentParser object.
54
+ """
55
+ parser = argparse.ArgumentParser(
56
+ prog="RQMTool",
57
+ description="Fetch RQM resources."
58
+ )
59
+
60
+ parser.add_argument(
61
+ '-v', '--version',
62
+ action='version',
63
+ version=f'v{VERSION} ({VERSION_DATE})',
64
+ help='Version of the RQMTool.'
65
+ )
66
+ parser.add_argument(
67
+ "--host",
68
+ required=True,
69
+ help="RQM server URL."
70
+ )
71
+ parser.add_argument(
72
+ "--project",
73
+ required=True,
74
+ help="RQM project."
75
+ )
76
+ parser.add_argument(
77
+ "--user",
78
+ required=True,
79
+ help="RQM username."
80
+ )
81
+ parser.add_argument(
82
+ "--password",
83
+ required=True,
84
+ help="RQM password."
85
+ )
86
+ parser.add_argument(
87
+ "--testplan",
88
+ required=True,
89
+ help="RQM testplan ID."
90
+ )
91
+ parser.add_argument(
92
+ "--dryrun",
93
+ action="store_true",
94
+ help='if set, then verify all input arguments (includes RQM authentication) and show what would be done.')
95
+ parser.add_argument(
96
+ "--format",
97
+ default="csv",
98
+ choices=OUTPUT_FORMATS,
99
+ help="Output format (csv or json). Default is csv.")
100
+ parser.add_argument(
101
+ "--types",
102
+ default="testcase,testsuite",
103
+ help="Comma-separated list of artifact types to fetch. Allowed: testcase, testsuite.")
104
+ parser.add_argument(
105
+ "--output-dir",
106
+ default=".",
107
+ help="Directory to save output files.")
108
+ parser.add_argument(
109
+ "--basename",
110
+ default="testplan_export",
111
+ help="Base name for output files.")
112
+ return parser.parse_args()
113
+
114
+ def __validate_arguments(arguments):
115
+ """
116
+ Validate and normalize command line arguments.
117
+
118
+ **Arguments:**
119
+
120
+ * ``arguments``
121
+
122
+ / *Condition*: required / *Type*: `ArgumentParser` object /
123
+
124
+ Parsed arguments from __process_commandline().
125
+
126
+ **Returns:**
127
+
128
+ * ``arguments``
129
+
130
+ / *Type*: `ArgumentParser` object /
131
+
132
+ ArgumentParser object.
133
+ """
134
+ artifact_types = arguments.types
135
+ if isinstance(artifact_types, str):
136
+ artifact_types = [x.strip().lower() for x in artifact_types.split(',')]
137
+ elif isinstance(artifact_types, (list, tuple)):
138
+ artifact_types = [x.lower() for x in artifact_types]
139
+ else:
140
+ raise TypeError(f"Invalid type for '--types': {type(artifact_types).__name__}, expected str or list.")
141
+
142
+ for t in artifact_types:
143
+ if t not in ARTIFACT_TYPES:
144
+ raise ValueError(
145
+ f"Invalid artifact type '{t}'. Allowed: {', '.join(ARTIFACT_TYPES)}."
146
+ )
147
+ arguments.types = artifact_types
148
+
149
+ return arguments
150
+
151
+ def write_json_file(file_name, data):
152
+ """
153
+ Write data to a JSON file.
154
+
155
+ **Arguments:**
156
+
157
+ * ``file_name``
158
+
159
+ / *Condition*: required / *Type*: str /
160
+
161
+ Path of the JSON file to write.
162
+
163
+ * ``data``
164
+
165
+ / *Condition*: required / *Type*: dict /
166
+
167
+ Data to export.
168
+
169
+ **Returns:**
170
+
171
+ (*no returns*)
172
+ """
173
+ with open(file_name, 'w', encoding='utf-8') as f:
174
+ json.dump(data, f, indent=2, ensure_ascii=False)
175
+ Logger.log(f"Exported data to: {file_name}")
176
+
177
+ def write_csv_file(file_name, data, artifact_type):
178
+ """
179
+ Write data to a CSV file for a specific artifact type.
180
+
181
+ **Arguments:**
182
+
183
+ * ``file_name``
184
+
185
+ / *Condition*: required / *Type*: str /
186
+
187
+ Path of the CSV file to write.
188
+
189
+ * ``data``
190
+
191
+ / *Condition*: required / *Type*: dict /
192
+
193
+ Data dictionary containing artifacts.
194
+
195
+ * ``artifact_type``
196
+
197
+ / *Condition*: required / *Type*: str /
198
+
199
+ Artifact type (`testcase` or `testsuite`) to export.
200
+
201
+ **Returns:**
202
+
203
+ (*no returns*)
204
+ """
205
+ artifact_data = data.get(artifact_type, [])
206
+ if not artifact_data:
207
+ Logger.log_warning(f"No data for '{artifact_type}', skipping CSV export.")
208
+ return
209
+
210
+ fieldnames = list(data[artifact_type][0].keys()) if data[artifact_type] else ["id", "name"]
211
+ with open(file_name, mode="w", newline='', encoding="utf-8") as f:
212
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
213
+ writer.writeheader()
214
+ for row in data[artifact_type]:
215
+ writer.writerow(row)
216
+ Logger.log(f"Exported {artifact_type} to: {file_name}")
217
+
218
+ def write_output_file(data, output_dir=".", basename="testplan_export", extension="csv", artifact_types=None):
219
+ """
220
+ Write data to output files (JSON or CSV) according to specified options.
221
+
222
+ **Arguments:**
223
+
224
+ * ``data``
225
+
226
+ / *Condition*: required / *Type*: dict /
227
+
228
+ Data dictionary containing artifacts.
229
+
230
+ * ``output_dir``
231
+
232
+ / *Condition*: optional / *Type*: str /
233
+
234
+ Directory to save output files. Default is current directory.
235
+
236
+ * ``basename``
237
+
238
+ / *Condition*: optional / *Type*: str /
239
+
240
+ Base name for output files. Default is "testplan_export".
241
+
242
+ * ``extension``
243
+
244
+ / *Condition*: optional / *Type*: str /
245
+
246
+ Output format: `json` or `csv`. Default is `csv`.
247
+
248
+ * ``artifact_types``
249
+
250
+ / *Condition*: optional / *Type*: list /
251
+
252
+ Artifact types to export. Default is all supported types.
253
+
254
+ **Returns:**
255
+
256
+ (*no returns*)
257
+ """
258
+ if extension == 'json':
259
+ file_name = os.path.join(output_dir, f"{basename}.json")
260
+ write_json_file(file_name, data)
261
+ else:
262
+ # CSV export: one file per artifact_type
263
+ for artifact_type in (artifact_types or ARTIFACT_TYPES):
264
+ file_name = os.path.join(output_dir, f"{basename}_{artifact_type}s.csv")
265
+ write_csv_file(file_name, data, artifact_type)
266
+
267
+ def RQMTool():
268
+ """
269
+ Main entry point for RQMTool CLI.
270
+
271
+ **Arguments:**
272
+
273
+ (*no arguments*)
274
+
275
+ **Returns:**
276
+
277
+ (*no returns*)
278
+ """
279
+ args = __process_commandline()
280
+ __validate_arguments(args)
281
+ Logger.config(dryrun=args.dryrun)
282
+
283
+ RQMClient = CRQMClient(args.user, args.password, args.project, args.host)
284
+ try:
285
+ bSuccess = RQMClient.login()
286
+ if bSuccess:
287
+ Logger.log()
288
+ Logger.log(f"Login RQM as user '{args.user}' successfully!")
289
+ else:
290
+ Logger.log_error("Could not login to RQM: 'Unknown reason'.")
291
+ except Exception as reason:
292
+ Logger.log_error(f"Could not login to RQM: '{str(reason)}'.")
293
+
294
+ if not args.dryrun:
295
+ testplan_data = RQMClient.getTestsFromTestplan(args.testplan, args.types)
296
+
297
+ basename_with_id = f"{args.basename}_{args.testplan}"
298
+
299
+ write_output_file(
300
+ testplan_data,
301
+ output_dir=args.output_dir,
302
+ basename=basename_with_id,
303
+ extension=args.format,
304
+ artifact_types=args.types
305
+ )
306
+
307
+ for artifact_type in args.types:
308
+ items = testplan_data.get(artifact_type, [])
309
+ Logger.log(f"Found {len(items)} {artifact_type}(s)")
310
+ cnt = 1
311
+ for item in items:
312
+ Logger.log(f"{cnt:>3}. {item['id']} - {item['name']}", indent=2)
313
+ cnt += 1
314
+
315
+ if __name__ == "__main__":
316
+ RQMTool()
RobotLog2RQM/version.py CHANGED
@@ -18,6 +18,5 @@
18
18
  #
19
19
  # Version and date of RobotLog2RQM
20
20
  #
21
- VERSION = "1.4.1"
22
- VERSION_DATE = "20.09.2024"
23
-
21
+ VERSION = "1.5.0"
22
+ VERSION_DATE = "28.10.2025"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.4.1
3
+ Version: 1.5.0
4
4
  Summary: Imports robot result(s) to IBM Rational Quality Manager (RQM)
5
5
  Home-page: https://github.com/test-fullautomation/robotframework-robotlog2rqm
6
6
  Author: Tran Duy Ngoan
@@ -21,8 +21,7 @@ Requires-Dist: requests
21
21
  Requires-Dist: colorama
22
22
  Requires-Dist: robotframework
23
23
 
24
- RobotLog2RQM Description
25
- ========================
24
+ # RobotLog2RQM Description
26
25
 
27
26
  The Python package **RobotLog2RQM** provides ability to import [Robot
28
27
  Framework test
@@ -42,14 +41,13 @@ Manager](https://www.ibm.com/support/knowledgecenter/SSYMRC_6.0.2/com.ibm.ration
42
41
  **RobotLog2RQM** tool is operating system independent and only works
43
42
  with Python 3.
44
43
 
45
- How to install
46
- --------------
44
+ ## How to install
47
45
 
48
46
  **RobotLog2RQM** can be installed in two different ways.
49
47
 
50
48
  1. Installation via PyPi (recommended for users)
51
49
 
52
- ``` {.}
50
+ ```
53
51
  pip install RobotLog2RQM
54
52
  ```
55
53
 
@@ -61,7 +59,7 @@ How to install
61
59
  - Clone the **robotframework-robotlog2rqm** repository to your
62
60
  machine.
63
61
 
64
- ``` {.}
62
+ ```
65
63
  git clone https://github.com/test-fullautomation/robotframework-robotlog2rqm.git
66
64
  ```
67
65
 
@@ -76,7 +74,7 @@ How to install
76
74
  packages you can find in the file `requirements.txt` in the
77
75
  repository root folder. Use pip to install them:
78
76
 
79
- ``` {.}
77
+ ```
80
78
  pip install -r ./requirements.txt
81
79
  ```
82
80
 
@@ -95,7 +93,7 @@ How to install
95
93
  to find **LaTeX**. This is defined in the **GenPackageDoc**
96
94
  configuration file
97
95
 
98
- ``` {.}
96
+ ```
99
97
  packagedoc\packagedoc_config.json
100
98
  ```
101
99
 
@@ -107,7 +105,7 @@ How to install
107
105
 
108
106
  - Use the following command to install **RobotLog2RQM**:
109
107
 
110
- ``` {.}
108
+ ```
111
109
  python setup.py install
112
110
  ```
113
111
 
@@ -118,8 +116,7 @@ be available (under *Scripts* folder of Python on Windows and
118
116
  In case above location is added to **PATH** environment variable then
119
117
  you can run it directly as operation system\'s command.
120
118
 
121
- How to use
122
- ----------
119
+ ## How to use
123
120
 
124
121
  **RobotLog2RQM** tool requires the Robot Framework `output.xml` result
125
122
  file(s) which will be imported, RQM information(e.g. host url, project,
@@ -132,15 +129,15 @@ Use below command to get tools\'s usage:
132
129
 
133
130
  The usage should be showed as below:
134
131
 
135
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
136
- [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
132
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
133
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
137
134
  resultxmlfile host project user password testplan
138
135
 
139
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
136
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
140
137
  Robot Framework into an IBM Rational Quality Manager.
141
138
 
142
139
  positional arguments:
143
- resultxmlfile absolute or relative path to the xml result file
140
+ resultxmlfile absolute or relative path to the xml result file
144
141
  or directory of result files to be imported.
145
142
  host RQM host url.
146
143
  project project on RQM.
@@ -152,19 +149,19 @@ The usage should be showed as below:
152
149
  -h, --help show this help message and exit
153
150
  -v, --version Version of the RobotLog2RQM importer.
154
151
  --testsuite TESTSUITE
155
- testsuite ID for this execution. If 'new', then create a new
152
+ testsuite ID for this execution. If 'new', then create a new
156
153
  testsuite for this execution.
157
- --recursive if set, then the path is searched recursively for
154
+ --recursive if set, then the path is searched recursively for
158
155
  log files to be imported.
159
- --createmissing if set, then all testcases without tcid are created
156
+ --createmissing if set, then all testcases without tcid are created
160
157
  when importing.
161
- --updatetestcase if set, then testcase information on RQM will be updated
158
+ --updatetestcase if set, then testcase information on RQM will be updated
162
159
  bases on robot testfile.
163
- --dryrun if set, then verify all input arguments
160
+ --dryrun if set, then verify all input arguments
164
161
  (includes RQM authentication) and show what would be done.
165
- --stream STREAM project stream. Note, requires Configuration Management (CM)
162
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
166
163
  to be enabled for the project area.
167
- --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
164
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
168
165
  or Baselines Only to be enabled for the project area.
169
166
 
170
167
  The below command is simple usage witth all required arguments to import
@@ -216,6 +213,58 @@ Then, open RQM with your favourite browser and you will see that the
216
213
  test case execution records and their results are imported in the given
217
214
  testplan ID.
218
215
 
216
+ # RQMTool Submodule
217
+
218
+ The package now includes a submodule **RQMTool** which provides a
219
+ standalone CLI to fetch test cases and test suites from IBM RQM test
220
+ plans.
221
+
222
+ RQMTool is accessible as a Python module:
223
+
224
+ python -m RobotLog2RQM.rqmtool --host <RQM_SERVER_URL> --project <PROJECT_AREA> \
225
+ --user <USERNAME> --password <PASSWORD> \
226
+ --testplan <TESTPLAN_ID> [--types <artifact_types>] \
227
+ [--format <csv|json>] [--output-dir <DIR>] \
228
+ [--basename <BASENAME>] [--dryrun]
229
+
230
+ Main features:
231
+
232
+ - Fetches selected artifact types ([testcase]{.title-ref} or
233
+ [testsuite]{.title-ref}) from a given test plan.
234
+ - Supports CSV or JSON export, with automatic filenames including the
235
+ test plan ID.
236
+
237
+ ## How to use RQMTool
238
+
239
+ RQMTool requires RQM information (host URL, project, credentials) and a
240
+ test plan ID. Use the [-h]{.title-ref} argument to see full usage:
241
+
242
+ python -m RobotLog2RQM.rqmtool -h
243
+
244
+ This will display all available command line options for RQMTool, such
245
+ as:
246
+
247
+ - [\--types]{.title-ref} : artifact types to fetch
248
+ ([testcase]{.title-ref}, [testsuite]{.title-ref}). Default: both.
249
+ - \`\--format\`: output format, either [csv]{.title-ref} or
250
+ [json]{.title-ref}.
251
+ - \`\--output-dir\`: directory to save exported files.
252
+ - \`\--basename\`: base name for output files.
253
+ - \`\--dryrun\`: validate inputs and RQM login without fetching data.
254
+
255
+ ## Example
256
+
257
+ Fetch all test cases and test suites from test plan ID 720 and export to
258
+ CSV:
259
+
260
+ python -m RobotLog2RQM.rqmtool --host https://sample-rqm-host.com \
261
+ --project ROBFW-AIO \
262
+ --user test_user \
263
+ --password test_pw \
264
+ --testplan 720 \
265
+ --types testcase,testsuite \
266
+ --format csv
267
+
219
268
  ### Sourcecode Documentation
220
269
 
221
270
  To understand more detail about the tool\'s features and how Robot test
@@ -223,8 +272,7 @@ cases and their results are reflected on RQM, please refer to
223
272
  [RobotLog2RQM tool's
224
273
  Documentation](https://github.com/test-fullautomation/robotframework-robotlog2rqm/blob/develop/RobotLog2RQM/RobotLog2RQM.pdf).
225
274
 
226
- Feedback
227
- --------
275
+ ## Feedback
228
276
 
229
277
  To give us a feedback, you can send an email to [Thomas
230
278
  Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com).
@@ -232,15 +280,13 @@ Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com).
232
280
  In case you want to report a bug or request any interesting feature,
233
281
  please don\'t hesitate to raise a ticket.
234
282
 
235
- Maintainers
236
- -----------
283
+ ## Maintainers
237
284
 
238
285
  [Thomas Pollerspöck](mailto:Thomas.Pollerspoeck@de.bosch.com)
239
286
 
240
287
  [Tran Duy Ngoan](mailto:Ngoan.TranDuy@vn.bosch.com)
241
288
 
242
- Contributors
243
- ------------
289
+ ## Contributors
244
290
 
245
291
  [Nguyen Huynh Tri Cuong](mailto:Cuong.NguyenHuynhTri@vn.bosch.com)
246
292
 
@@ -250,8 +296,7 @@ Contributors
250
296
 
251
297
  [Holger Queckenstedt](mailto:Holger.Queckenstedt@de.bosch.com)
252
298
 
253
- License
254
- -------
299
+ ## License
255
300
 
256
301
  Copyright 2020-2024 Robert Bosch GmbH
257
302
 
@@ -1,9 +1,11 @@
1
- RobotLog2RQM/CRQM.py,sha256=fyuU9F2e4RCZzkxOb3bShKrWEjPcXDwzi1HGbUmWjxQ,64123
2
- RobotLog2RQM/RobotLog2RQM.pdf,sha256=sRhG1vvBX00YwBR2V9pKhMBpJH5AMniLHtPf9rQdK6Q,336238
1
+ RobotLog2RQM/CRQM.py,sha256=TsgEXYu608vb-rjQ9qTbxzql_EPTgVHqnrdkDs3rAus,67139
2
+ RobotLog2RQM/RobotLog2RQM.pdf,sha256=Ujot_99PiYu2s9hS_sQu3fP1occXcbs2WO9qiHSVx6M,349449
3
3
  RobotLog2RQM/__init__.py,sha256=YKDTJjDsnQkr5X-gjjO8opwKUVKm6kc8sIUpURYMk48,596
4
4
  RobotLog2RQM/__main__.py,sha256=JabttEncy80antJWeGVmjoXyiF1DyXxkxdW4xLuHzT0,681
5
- RobotLog2RQM/robotlog2rqm.py,sha256=bhPaj43LazYO58mhchMt7ammaf6Qerxq7z2_aq8qesY,33437
6
- RobotLog2RQM/version.py,sha256=lTcM8gNMLdiczaZBwoctKVZ7UBQ1h8jjocuoi3Qoc9A,917
5
+ RobotLog2RQM/logger.py,sha256=FnZZqssOcrF5yK6wxkgifScoQcFGrqrvzLvXWY3V-9A,4252
6
+ RobotLog2RQM/robotlog2rqm.py,sha256=FeMJsBuAu1CLBjDJsQQs7CjQifYgqyhaPMNabnEeSc0,30160
7
+ RobotLog2RQM/rqmtool.py,sha256=wDagUGptbPwRYyn5jo1RA8HJmwHoDX_iBew40WFtWJ0,7957
8
+ RobotLog2RQM/version.py,sha256=4VudYtnU16wL-FZAKungXo19Q5QacjNSAHMEWv-53JE,916
7
9
  RobotLog2RQM/RQM_templates/buildrecord.xml,sha256=uGot7pNOjPR8do0JsJi0Lz3OCU9NMhODRd428QgvHh4,1498
8
10
  RobotLog2RQM/RQM_templates/configuration.xml,sha256=NrFDv51fuGhgeMiZuRhQ5q_UJ0u_pWzdxisIF5AJs74,1378
9
11
  RobotLog2RQM/RQM_templates/executionresult.xml,sha256=WTp4qDk29peBc0ll6GHVXX_kF_YBsOVjy9vBzoz7_2k,2160
@@ -12,9 +14,9 @@ RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml,sha256=9GAs2WqZMkFJSNEZULm9B
12
14
  RobotLog2RQM/RQM_templates/testcase.xml,sha256=zovFKj-37QHn2S8mMA_9RnAJ3zBmDJkJj31yelsnFFI,2167
13
15
  RobotLog2RQM/RQM_templates/testsuite.xml,sha256=r2ijEsyPoE7qzCtUxgIHDOEcqUveDN4SMf9HSE9b0ZU,1326
14
16
  RobotLog2RQM/RQM_templates/testsuitelog.xml,sha256=l-NlaCyk6Ben76PElXKOHfMlEvyQ-e9MOZ6-F9HvwDQ,1920
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,,
17
+ robotframework_robotlog2rqm-1.5.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
+ robotframework_robotlog2rqm-1.5.0.dist-info/METADATA,sha256=mBHsyTik_HMPC-SsoTjqph_S_3MQc_CLj3VViXfgXuI,11846
19
+ robotframework_robotlog2rqm-1.5.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
20
+ robotframework_robotlog2rqm-1.5.0.dist-info/entry_points.txt,sha256=94N661T4lHzLSey2WQ18OVduw9-Mf6Kh8HK7cBL2YPY,112
21
+ robotframework_robotlog2rqm-1.5.0.dist-info/top_level.txt,sha256=jb_Gt6W44FoOLtGfBe7RzqCLaquhihkEWvSI1zjXDHc,13
22
+ robotframework_robotlog2rqm-1.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.44.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
+ RQMTool = RobotLog2RQM.rqmtool:RQMTool
2
3
  RobotLog2RQM = RobotLog2RQM.robotlog2rqm:RobotLog2RQM
3
4