robotframework-robotlog2rqm 1.4.2__py3-none-any.whl → 1.6.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
@@ -308,9 +308,9 @@ Disconnect from RQM.
308
308
  """
309
309
  self.session.close()
310
310
 
311
- def config(self, plan_id, build_name=None, config_name=None,
311
+ def config(self, plan_id=None, build_name=None, config_name=None,
312
312
  createmissing=False, updatetestcase=False, suite_id=None,
313
- stream=None, baseline=None, naming_convention=None):
313
+ stream=None, baseline=None, naming_convention=None, fetch_team_areas=True):
314
314
  """
315
315
  Configure RQMClient with testplan ID, build, configuration, createmissing, ...
316
316
 
@@ -322,7 +322,7 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
322
322
 
323
323
  * ``plan_id``
324
324
 
325
- / *Condition*: required / *Type*: str /
325
+ / *Condition*: optional / *Type*: str /
326
326
 
327
327
  Testplan ID of RQM project for importing result(s).
328
328
 
@@ -352,12 +352,36 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
352
352
 
353
353
  If `True`, the information of testcase on RQM will be updated bases on robot testfile.
354
354
 
355
- * ``suite_id (optional)``
355
+ * ``suite_id``
356
356
 
357
357
  / *Condition*: optional / *Type*: str / *Default*: None /
358
358
 
359
359
  Testsuite ID of RQM project for importing result(s).
360
360
 
361
+ * ``stream``
362
+
363
+ / *Condition*: optional / *Type*: str / *Default*: None /
364
+
365
+ Project stream which is enabled in Configuration Management (CM).
366
+
367
+ * ``baseline``
368
+
369
+ / *Condition*: optional / *Type*: str / *Default*: None /
370
+
371
+ Project baseline which is enabled in Configuration Management (CM).
372
+
373
+ * ``naming_convention``
374
+
375
+ / *Condition*: optional / *Type*: dict / *Default*: None /
376
+
377
+ Naming convention for the creating new resources (testcase, TCER, ...).
378
+
379
+ * ``fetch_team_areas``
380
+
381
+ / *Condition*: optional / *Type*: bool / *Default*: True /
382
+
383
+ If `True`, fetch team-areas information for further process.
384
+
361
385
  **Returns:**
362
386
 
363
387
  (*no returns*)
@@ -411,12 +435,13 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
411
435
  raise Exception("Get all baselines failed. Reason: %s"%res['message'])
412
436
 
413
437
  # Verify testplan ID
414
- res_plan = self.getResourceByID('testplan', plan_id)
415
- if res_plan.status_code != 200:
416
- raise Exception('Testplan with ID %s is not existing!'%str(plan_id))
438
+ if plan_id != None:
439
+ res_plan = self.getResourceByID('testplan', plan_id)
440
+ if res_plan.status_code != 200:
441
+ raise Exception('Testplan with ID %s is not existing!'%str(plan_id))
417
442
 
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
443
+ oTestplan = get_xml_tree(BytesIO(str(res_plan.text).encode()), bdtd_validation=False)
444
+ self.testplan.name = oTestplan.find('ns4:title', oTestplan.getroot().nsmap).text
420
445
 
421
446
  # Verify and create build version if required
422
447
  if build_name != None:
@@ -453,7 +478,8 @@ Configure RQMClient with testplan ID, build, configuration, createmissing, ...
453
478
  self.testsuite.name = oTestsuite.find('ns4:title', oTestsuite.getroot().nsmap).text
454
479
 
455
480
  # get all team-areas for testcase template
456
- self.getAllTeamAreas()
481
+ if fetch_team_areas:
482
+ self.getAllTeamAreas()
457
483
 
458
484
 
459
485
  except Exception as error:
@@ -680,6 +706,54 @@ Return the name for given resource bases on the naming convention
680
706
  except:
681
707
  raise Exception(f"Failed to generate name for {resource} '{name}'")
682
708
 
709
+ def __parse_test_artifact(self, xml_data):
710
+ """
711
+ Parse XML test case response and extract its data.
712
+
713
+ **Arguments:**
714
+
715
+ * ``xml_data``
716
+
717
+ / *Condition*: required / *Type*: str /
718
+
719
+ The response text from request to get test case as xml string.
720
+
721
+ **Returns:**
722
+
723
+ * / *Type*: dict /
724
+
725
+ The dictionary which contains the parsed data from given xml.
726
+
727
+ .. code:: python
728
+
729
+ {
730
+ 'id': ...,
731
+ 'name': ...,
732
+ 'url': ...,
733
+ ...
734
+ }
735
+
736
+ """
737
+ test_tree = get_xml_tree(BytesIO(str(xml_data).encode()), bdtd_validation=False)
738
+ nsmap = test_tree.getroot().nsmap
739
+
740
+ test_name = test_tree.find('ns4:title', test_tree.getroot().nsmap)
741
+ test_web_id = test_tree.find('ns2:webId', test_tree.getroot().nsmap)
742
+ test_url = test_tree.find('ns4:identifier', test_tree.getroot().nsmap)
743
+ test_category = test_tree.findall('ns2:category', test_tree.getroot().nsmap)
744
+ test_custom_attr = test_tree.findall('.//ns2:customAttribute', test_tree.getroot().nsmap)
745
+ test_data = {'id': test_web_id.text,
746
+ 'name': test_name.text if test_name is not None else '',
747
+ 'url': test_url.text}
748
+ for item in test_category:
749
+ test_data[item.attrib.get('term')] = item.attrib.get('value')
750
+ for attr in test_custom_attr:
751
+ try:
752
+ test_data[attr.find("ns2:name", nsmap).text] = attr.find("ns2:value", nsmap).text
753
+ except:
754
+ pass
755
+
756
+ return test_data
683
757
  #
684
758
  # Methods to get resources
685
759
  #
@@ -849,6 +923,91 @@ Example:
849
923
  else:
850
924
  raise Exception(f"Could not get 'team-areas' of project '{self.projectname}'.")
851
925
 
926
+ def getTestArtifactsFromResource(self, resource_type, resource_id, artifact_types):
927
+ """
928
+ Get all test cases and test suites associated with a given test plan.
929
+
930
+ **Arguments:**
931
+
932
+ * ``resource_type``
933
+
934
+ / *Condition*: required / *Type*: str /
935
+
936
+ The RQM test resource (`testplan` or `testsuite`) to get test artifact(s).
937
+
938
+ * ``resource_id``
939
+
940
+ / *Condition*: required / *Type*: str /
941
+
942
+ The RQM test plan/suite to get test artifact(s).
943
+
944
+ * ``artifact_types``
945
+
946
+ / *Condition*: required / *Type*: list /
947
+
948
+ List of artifact types (`testcase`, `testsuite`) for fetching.
949
+
950
+ **Returns:**
951
+
952
+ * / *Type*: dict /
953
+
954
+ A dictionary containing fetched artifacts:
955
+
956
+ .. code:: python
957
+
958
+ {
959
+ 'testcase': [{'id': ..., 'name': ..., 'url': ...}, ...],
960
+ 'testsuite': [{'id': ..., 'name': ..., 'url': ...}, ...]
961
+ }
962
+ """
963
+ ALLOW_RESOURCE_TYPES = ['testplan', 'testsuite']
964
+ ALLOW_ARTIFACT_TYPES = ['testcase', 'testsuite']
965
+ result = {}
966
+
967
+ if resource_type not in ALLOW_RESOURCE_TYPES:
968
+ raise ValueError(
969
+ f"Unsupported resource type '{resource_type}'. "
970
+ f"Please use one of the following: {', '.join(ALLOW_RESOURCE_TYPES)}."
971
+ )
972
+
973
+ if isinstance(artifact_types, str):
974
+ artifact_types = [artifact_types]
975
+ elif not isinstance(artifact_types, (list, tuple)):
976
+ raise TypeError(
977
+ f"Invalid type for 'artifact_types': expected str or list, got {type(artifact_types).__name__}."
978
+ )
979
+
980
+ for item in artifact_types:
981
+ artifact_type = item.lower()
982
+ if artifact_type not in ALLOW_ARTIFACT_TYPES:
983
+ raise ValueError(
984
+ f"Unsupported artifact type '{artifact_type}'. "
985
+ f"Please use one of the following: {', '.join(ALLOW_ARTIFACT_TYPES)}."
986
+ )
987
+ result[artifact_type] = []
988
+
989
+ res = self.getResourceByID(resource_type, resource_id)
990
+ if res.status_code != 200:
991
+ raise Exception(f"Failed to get {resource_type} {resource_id}: {res.reason}")
992
+
993
+ oTree = get_xml_tree(BytesIO(str(res.text).encode()), bdtd_validation=False)
994
+ root = oTree.getroot()
995
+ nsmap = root.nsmap
996
+
997
+ for item in artifact_types:
998
+ artifact_type = item.lower()
999
+ # Find all linked test artifact
1000
+ for oTest in root.findall(f'.//ns2:{artifact_type}', nsmap):
1001
+ href = oTest.attrib.get('href')
1002
+ if href:
1003
+ test_id = href.split('/')[-1]
1004
+ # Get testcase/testsuite by ID
1005
+ test_res = self.getResourceByID(artifact_type, test_id)
1006
+ if test_res.status_code == 200:
1007
+ test_data = self.__parse_test_artifact(test_res.text)
1008
+ result[artifact_type].append(test_data)
1009
+
1010
+ return result
852
1011
 
853
1012
  #
854
1013
  # Methods to create XML template for resources
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
  """
@@ -380,7 +225,7 @@ Default schema supports below information:
380
225
  # TESTCASE_NAME is not available for non-testcase relevant resources
381
226
  # TESTSUITE_NAME is not available for `buildrecord` and `configuration` resources
382
227
  # Warning user for using wrong place holders
383
- oMatch = re.search(".*\{(.*)\}.*", dConfig[key])
228
+ oMatch = re.search(r".*\{(.*)\}.*", dConfig[key])
384
229
  if oMatch:
385
230
  if oMatch.group(1) not in CRQMClient.SUPPORTED_PLACEHOLDER:
386
231
  Logger.log_warning(f"Place holder '{{{oMatch.group(1)}}}' is not supported, it will not be replaced when generating {key} resource")
@@ -0,0 +1,392 @@
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
+ # exclusive group to provdve only --testsuite or --testplan
87
+ group = parser.add_mutually_exclusive_group(required=True)
88
+ group.add_argument(
89
+ "--testplan",
90
+ help="RQM testplan ID."
91
+ )
92
+ group.add_argument(
93
+ "--testsuite",
94
+ help="RQM testsuite ID."
95
+ )
96
+ parser.add_argument(
97
+ "--stream",
98
+ type=str,
99
+ help="project stream. Note, requires Configuration Management (CM) to be enabled for the project area."
100
+ )
101
+ parser.add_argument(
102
+ '--baseline',
103
+ type=str,
104
+ help='project baseline. Note, requires Configuration Management (CM), or Baselines Only to be enabled for the project area.'
105
+ )
106
+ parser.add_argument(
107
+ "--dryrun",
108
+ action="store_true",
109
+ help='if set, then verify all input arguments (includes RQM authentication) and show what would be done.')
110
+ parser.add_argument(
111
+ "--format",
112
+ default="csv",
113
+ choices=OUTPUT_FORMATS,
114
+ help="Output format (csv or json). Default is csv.")
115
+ parser.add_argument(
116
+ "--types",
117
+ default="testcase,testsuite",
118
+ help="Comma-separated list of artifact types to fetch. Allowed: testcase, testsuite.")
119
+ parser.add_argument(
120
+ "--output-dir",
121
+ default=".",
122
+ help="Directory to save output files.")
123
+ parser.add_argument(
124
+ "--basename",
125
+ default="testplan_export",
126
+ help="Base name for output files.")
127
+ return parser.parse_args()
128
+
129
+ def __validate_arguments(arguments):
130
+ """
131
+ Validate and normalize command line arguments.
132
+
133
+ **Arguments:**
134
+
135
+ * ``arguments``
136
+
137
+ / *Condition*: required / *Type*: `ArgumentParser` object /
138
+
139
+ Parsed arguments from __process_commandline().
140
+
141
+ **Returns:**
142
+
143
+ * ``arguments``
144
+
145
+ / *Type*: `ArgumentParser` object /
146
+
147
+ ArgumentParser object.
148
+ """
149
+ artifact_types = arguments.types
150
+ if isinstance(artifact_types, str):
151
+ artifact_types = [x.strip().lower() for x in artifact_types.split(',')]
152
+ elif isinstance(artifact_types, (list, tuple)):
153
+ artifact_types = [x.lower() for x in artifact_types]
154
+ else:
155
+ raise TypeError(f"Invalid type for '--types': {type(artifact_types).__name__}, expected str or list.")
156
+
157
+ for t in artifact_types:
158
+ if t not in ARTIFACT_TYPES:
159
+ raise ValueError(
160
+ f"Invalid artifact type '{t}'. Allowed: {', '.join(ARTIFACT_TYPES)}."
161
+ )
162
+ arguments.types = artifact_types
163
+
164
+ return arguments
165
+
166
+ def write_json_file(file_name, data):
167
+ """
168
+ Write data to a JSON file.
169
+
170
+ **Arguments:**
171
+
172
+ * ``file_name``
173
+
174
+ / *Condition*: required / *Type*: str /
175
+
176
+ Path of the JSON file to write.
177
+
178
+ * ``data``
179
+
180
+ / *Condition*: required / *Type*: dict /
181
+
182
+ Data to export.
183
+
184
+ **Returns:**
185
+
186
+ (*no returns*)
187
+ """
188
+ with open(file_name, 'w', encoding='utf-8') as f:
189
+ json.dump(data, f, indent=2, ensure_ascii=False)
190
+ Logger.log(f"Exported data to: {file_name}")
191
+
192
+ def write_csv_file(file_name, data, artifact_type):
193
+ """
194
+ Write data to a CSV file for a specific artifact type.
195
+
196
+ **Arguments:**
197
+
198
+ * ``file_name``
199
+
200
+ / *Condition*: required / *Type*: str /
201
+
202
+ Path of the CSV file to write.
203
+
204
+ * ``data``
205
+
206
+ / *Condition*: required / *Type*: dict /
207
+
208
+ Data dictionary containing artifacts.
209
+
210
+ * ``artifact_type``
211
+
212
+ / *Condition*: required / *Type*: str /
213
+
214
+ Artifact type (`testcase` or `testsuite`) to export.
215
+
216
+ **Returns:**
217
+
218
+ (*no returns*)
219
+ """
220
+ artifact_data = data.get(artifact_type, [])
221
+ if not artifact_data:
222
+ Logger.log_warning(f"No data for '{artifact_type}', skipping CSV export.")
223
+ return
224
+
225
+ fieldnames = list(data[artifact_type][0].keys()) if data[artifact_type] else ["id", "name"]
226
+ with open(file_name, mode="w", newline='', encoding="utf-8") as f:
227
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
228
+ writer.writeheader()
229
+ for row in data[artifact_type]:
230
+ writer.writerow(row)
231
+ Logger.log(f"Exported {artifact_type} to: {file_name}")
232
+
233
+ def write_output_file(data, output_dir=".", basename="testplan_export", extension="csv", artifact_types=None):
234
+ """
235
+ Write data to output files (JSON or CSV) according to specified options.
236
+
237
+ **Arguments:**
238
+
239
+ * ``data``
240
+
241
+ / *Condition*: required / *Type*: dict /
242
+
243
+ Data dictionary containing artifacts.
244
+
245
+ * ``output_dir``
246
+
247
+ / *Condition*: optional / *Type*: str /
248
+
249
+ Directory to save output files. Default is current directory.
250
+
251
+ * ``basename``
252
+
253
+ / *Condition*: optional / *Type*: str /
254
+
255
+ Base name for output files. Default is "testplan_export".
256
+
257
+ * ``extension``
258
+
259
+ / *Condition*: optional / *Type*: str /
260
+
261
+ Output format: `json` or `csv`. Default is `csv`.
262
+
263
+ * ``artifact_types``
264
+
265
+ / *Condition*: optional / *Type*: list /
266
+
267
+ Artifact types to export. Default is all supported types.
268
+
269
+ **Returns:**
270
+
271
+ (*no returns*)
272
+ """
273
+ if extension == 'json':
274
+ file_name = os.path.join(output_dir, f"{basename}.json")
275
+ write_json_file(file_name, data)
276
+ else:
277
+ # CSV export: one file per artifact_type
278
+ for artifact_type in (artifact_types or ARTIFACT_TYPES):
279
+ file_name = os.path.join(output_dir, f"{basename}_{artifact_type}s.csv")
280
+ write_csv_file(file_name, data, artifact_type)
281
+
282
+ def normalize_custom_attributes(data, artifact_types):
283
+ """
284
+ Ensure all artifacts across projects have consistent keys.
285
+ Missing custom attributes will be filled with empty strings.
286
+
287
+ **Arguments:**
288
+
289
+ * ``data``
290
+
291
+ / *Condition*: required / *Type*: dict /
292
+
293
+ Dictionary containing artifact data fetched from RQM, where each key represents
294
+ an artifact type (e.g., ``testcase``, ``testsuite``) and the value is a list
295
+ of dictionaries holding artifact details.
296
+
297
+ * ``artifact_types``
298
+
299
+ / *Condition*: required / *Type*: list /
300
+
301
+ List of artifact types to process (e.g., ``['testcase', 'testsuite']``).
302
+ Each type key in ``data`` will be normalized to ensure all items have the same set of attributes.
303
+
304
+ **Returns:**
305
+
306
+ * ``data``
307
+
308
+ / *Type*: dict /
309
+
310
+ Normalized dictionary with consistent keys across all artifacts.
311
+ Any missing custom attributes are added with an empty string ("") as value.
312
+ """
313
+ for artifact_type in artifact_types:
314
+ artifacts = data.get(artifact_type, [])
315
+ if not artifacts:
316
+ continue
317
+
318
+ # Collect all keys (standard + custom attributes)
319
+ all_keys = set()
320
+ for item in artifacts:
321
+ all_keys.update(item.keys())
322
+
323
+ # Fill missing attributes with empty string
324
+ for item in artifacts:
325
+ for key in all_keys:
326
+ if key not in item:
327
+ item[key] = ""
328
+
329
+ return data
330
+
331
+
332
+ def RQMTool():
333
+ """
334
+ Main entry point for RQMTool CLI.
335
+
336
+ **Arguments:**
337
+
338
+ (*no arguments*)
339
+
340
+ **Returns:**
341
+
342
+ (*no returns*)
343
+ """
344
+ args = __process_commandline()
345
+ __validate_arguments(args)
346
+ Logger.config(dryrun=args.dryrun)
347
+
348
+ RQMClient = CRQMClient(args.user, args.password, args.project, args.host)
349
+ try:
350
+ bSuccess = RQMClient.login()
351
+ RQMClient.config(stream=args.stream, baseline=args.baseline)
352
+ if bSuccess:
353
+ Logger.log()
354
+ Logger.log(f"Login RQM as user '{args.user}' successfully!")
355
+ else:
356
+ Logger.log_error("Could not login to RQM: 'Unknown reason'.")
357
+ except Exception as reason:
358
+ Logger.log_error(f"Could not login to RQM: '{str(reason)}'.")
359
+
360
+ if not args.dryrun:
361
+ if args.testplan:
362
+ artifact_types = args.types
363
+ basename_with_id = f"{args.basename}_{args.testplan}"
364
+ test_data = RQMClient.getTestArtifactsFromResource('testplan', args.testplan, args.types)
365
+ test_data = normalize_custom_attributes(test_data, artifact_types)
366
+ elif args.testsuite:
367
+ artifact_types = ['testcase']
368
+ if args.basename == "testplan_export":
369
+ basename_with_id = f"testsuite_export_{args.testsuite}"
370
+ else:
371
+ basename_with_id = f"{args.basename}_{args.testsuite}"
372
+ test_data = RQMClient.getTestArtifactsFromResource('testsuite', args.testsuite, artifact_types)
373
+ test_data = normalize_custom_attributes(test_data, artifact_types)
374
+
375
+ write_output_file(
376
+ test_data,
377
+ output_dir=args.output_dir,
378
+ basename=basename_with_id,
379
+ extension=args.format,
380
+ artifact_types=artifact_types
381
+ )
382
+
383
+ for artifact_type in artifact_types:
384
+ items = test_data.get(artifact_type, [])
385
+ Logger.log(f"Found {len(items)} {artifact_type}(s)")
386
+ cnt = 1
387
+ for item in items:
388
+ Logger.log(f"{cnt:>3}. {item['id']} - {item['name']}", indent=2)
389
+ cnt += 1
390
+
391
+ if __name__ == "__main__":
392
+ RQMTool()
RobotLog2RQM/version.py CHANGED
@@ -18,5 +18,5 @@
18
18
  #
19
19
  # Version and date of RobotLog2RQM
20
20
  #
21
- VERSION = "1.4.2"
22
- VERSION_DATE = "18.02.2025"
21
+ VERSION = "1.6.0"
22
+ VERSION_DATE = "10.11.2025"
@@ -1,12 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: robotframework-robotlog2rqm
3
- Version: 1.4.2
3
+ Version: 1.6.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
7
7
  Author-email: Ngoan.TranDuy@vn.bosch.com
8
- License: UNKNOWN
9
- Platform: UNKNOWN
10
8
  Classifier: Programming Language :: Python :: 3
11
9
  Classifier: License :: OSI Approved :: Apache Software License
12
10
  Classifier: Operating System :: OS Independent
@@ -20,6 +18,16 @@ Requires-Dist: lxml
20
18
  Requires-Dist: requests
21
19
  Requires-Dist: colorama
22
20
  Requires-Dist: robotframework
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license-file
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
23
31
 
24
32
  # RobotLog2RQM Description
25
33
 
@@ -129,15 +137,15 @@ Use below command to get tools\'s usage:
129
137
 
130
138
  The usage should be showed as below:
131
139
 
132
- usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
133
- [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
140
+ usage: RobotLog2RQM (RobotXMLResult to RQM importer) [-h] [-v] [--testsuite TESTSUITE] [--recursive]
141
+ [--createmissing] [--updatetestcase] [--dryrun] [--stream STREAM] [--baseline BASELINE]
134
142
  resultxmlfile host project user password testplan
135
143
 
136
- RobotLog2RQM imports XML result files (default: output.xml) generated by the
144
+ RobotLog2RQM imports XML result files (default: output.xml) generated by the
137
145
  Robot Framework into an IBM Rational Quality Manager.
138
146
 
139
147
  positional arguments:
140
- resultxmlfile absolute or relative path to the xml result file
148
+ resultxmlfile absolute or relative path to the xml result file
141
149
  or directory of result files to be imported.
142
150
  host RQM host url.
143
151
  project project on RQM.
@@ -149,19 +157,19 @@ The usage should be showed as below:
149
157
  -h, --help show this help message and exit
150
158
  -v, --version Version of the RobotLog2RQM importer.
151
159
  --testsuite TESTSUITE
152
- testsuite ID for this execution. If 'new', then create a new
160
+ testsuite ID for this execution. If 'new', then create a new
153
161
  testsuite for this execution.
154
- --recursive if set, then the path is searched recursively for
162
+ --recursive if set, then the path is searched recursively for
155
163
  log files to be imported.
156
- --createmissing if set, then all testcases without tcid are created
164
+ --createmissing if set, then all testcases without tcid are created
157
165
  when importing.
158
- --updatetestcase if set, then testcase information on RQM will be updated
166
+ --updatetestcase if set, then testcase information on RQM will be updated
159
167
  bases on robot testfile.
160
- --dryrun if set, then verify all input arguments
168
+ --dryrun if set, then verify all input arguments
161
169
  (includes RQM authentication) and show what would be done.
162
- --stream STREAM project stream. Note, requires Configuration Management (CM)
170
+ --stream STREAM project stream. Note, requires Configuration Management (CM)
163
171
  to be enabled for the project area.
164
- --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
172
+ --baseline BASELINE project baseline. Note, requires Configuration Management (CM),
165
173
  or Baselines Only to be enabled for the project area.
166
174
 
167
175
  The below command is simple usage witth all required arguments to import
@@ -213,6 +221,58 @@ Then, open RQM with your favourite browser and you will see that the
213
221
  test case execution records and their results are imported in the given
214
222
  testplan ID.
215
223
 
224
+ # RQMTool Submodule
225
+
226
+ The package now includes a submodule **RQMTool** which provides a
227
+ standalone CLI to fetch test cases and test suites from IBM RQM test
228
+ plans.
229
+
230
+ RQMTool is accessible as a Python module:
231
+
232
+ python -m RobotLog2RQM.rqmtool --host <RQM_SERVER_URL> --project <PROJECT_AREA> \
233
+ --user <USERNAME> --password <PASSWORD> \
234
+ --testplan <TESTPLAN_ID> [--types <artifact_types>] \
235
+ [--format <csv|json>] [--output-dir <DIR>] \
236
+ [--basename <BASENAME>] [--dryrun]
237
+
238
+ Main features:
239
+
240
+ - Fetches selected artifact types ([testcase]{.title-ref} or
241
+ [testsuite]{.title-ref}) from a given test plan.
242
+ - Supports CSV or JSON export, with automatic filenames including the
243
+ test plan ID.
244
+
245
+ ## How to use RQMTool
246
+
247
+ RQMTool requires RQM information (host URL, project, credentials) and a
248
+ test plan ID. Use the [-h]{.title-ref} argument to see full usage:
249
+
250
+ python -m RobotLog2RQM.rqmtool -h
251
+
252
+ This will display all available command line options for RQMTool, such
253
+ as:
254
+
255
+ - [\--types]{.title-ref} : artifact types to fetch
256
+ ([testcase]{.title-ref}, [testsuite]{.title-ref}). Default: both.
257
+ - \`\--format\`: output format, either [csv]{.title-ref} or
258
+ [json]{.title-ref}.
259
+ - \`\--output-dir\`: directory to save exported files.
260
+ - \`\--basename\`: base name for output files.
261
+ - \`\--dryrun\`: validate inputs and RQM login without fetching data.
262
+
263
+ ## Example
264
+
265
+ Fetch all test cases and test suites from test plan ID 720 and export to
266
+ CSV:
267
+
268
+ python -m RobotLog2RQM.rqmtool --host https://sample-rqm-host.com \
269
+ --project ROBFW-AIO \
270
+ --user test_user \
271
+ --password test_pw \
272
+ --testplan 720 \
273
+ --types testcase,testsuite \
274
+ --format csv
275
+
216
276
  ### Sourcecode Documentation
217
277
 
218
278
  To understand more detail about the tool\'s features and how Robot test
@@ -260,5 +320,3 @@ distributed under the License is distributed on an \"AS IS\" BASIS,
260
320
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
261
321
  See the License for the specific language governing permissions and
262
322
  limitations under the License.
263
-
264
-
@@ -0,0 +1,22 @@
1
+ RobotLog2RQM/CRQM.py,sha256=NxFbIKvTOepkdNmv0V9aQCaU2jSGhuHO707XTglMF_A,69357
2
+ RobotLog2RQM/RobotLog2RQM.pdf,sha256=--VWUBLqZOtfQXy9O8N28MvOT7VMRPg9fSAQXl8coK0,358016
3
+ RobotLog2RQM/__init__.py,sha256=YKDTJjDsnQkr5X-gjjO8opwKUVKm6kc8sIUpURYMk48,596
4
+ RobotLog2RQM/__main__.py,sha256=JabttEncy80antJWeGVmjoXyiF1DyXxkxdW4xLuHzT0,681
5
+ RobotLog2RQM/logger.py,sha256=FnZZqssOcrF5yK6wxkgifScoQcFGrqrvzLvXWY3V-9A,4252
6
+ RobotLog2RQM/robotlog2rqm.py,sha256=zX8ibJqeQTPwtRF6zX-qHtT1d6jj1EI90vpaUL77FEM,30161
7
+ RobotLog2RQM/rqmtool.py,sha256=s-rTI1MBby0SfQIBP-WRx5_frdtQBss86I9i04MBuy4,10564
8
+ RobotLog2RQM/version.py,sha256=3E-s2UEJ2s5J5UVh1TPTW-T21mrSzBfY_KjCv-v7VQk,916
9
+ RobotLog2RQM/RQM_templates/buildrecord.xml,sha256=uGot7pNOjPR8do0JsJi0Lz3OCU9NMhODRd428QgvHh4,1498
10
+ RobotLog2RQM/RQM_templates/configuration.xml,sha256=NrFDv51fuGhgeMiZuRhQ5q_UJ0u_pWzdxisIF5AJs74,1378
11
+ RobotLog2RQM/RQM_templates/executionresult.xml,sha256=WTp4qDk29peBc0ll6GHVXX_kF_YBsOVjy9vBzoz7_2k,2160
12
+ RobotLog2RQM/RQM_templates/executionworkitem.xml,sha256=3GYO-nvXcG-HDQZnDzGwYZSQOWAUNckT3_GUa8Ze2Eo,1511
13
+ RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml,sha256=9GAs2WqZMkFJSNEZULm9BJvQy02dl_2JMecpQPHGTPA,1389
14
+ RobotLog2RQM/RQM_templates/testcase.xml,sha256=zovFKj-37QHn2S8mMA_9RnAJ3zBmDJkJj31yelsnFFI,2167
15
+ RobotLog2RQM/RQM_templates/testsuite.xml,sha256=r2ijEsyPoE7qzCtUxgIHDOEcqUveDN4SMf9HSE9b0ZU,1326
16
+ RobotLog2RQM/RQM_templates/testsuitelog.xml,sha256=l-NlaCyk6Ben76PElXKOHfMlEvyQ-e9MOZ6-F9HvwDQ,1920
17
+ robotframework_robotlog2rqm-1.6.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
18
+ robotframework_robotlog2rqm-1.6.0.dist-info/METADATA,sha256=k834p4eH88hGgfxhwifUu7ms7TXY83tJCty1uIS8WCA,12028
19
+ robotframework_robotlog2rqm-1.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ robotframework_robotlog2rqm-1.6.0.dist-info/entry_points.txt,sha256=yLoNhxm2lfj3FxMx3Orct8db4qkPLII8u241GAH6BB0,111
21
+ robotframework_robotlog2rqm-1.6.0.dist-info/top_level.txt,sha256=jb_Gt6W44FoOLtGfBe7RzqCLaquhihkEWvSI1zjXDHc,13
22
+ robotframework_robotlog2rqm-1.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
+ RQMTool = RobotLog2RQM.rqmtool:RQMTool
2
3
  RobotLog2RQM = RobotLog2RQM.robotlog2rqm:RobotLog2RQM
3
-
@@ -1,20 +0,0 @@
1
- RobotLog2RQM/CRQM.py,sha256=sL7IsFOhzRVvV2n_g2PFDjL3Ej8YChUl3PDTv3LCIN0,64363
2
- RobotLog2RQM/RobotLog2RQM.pdf,sha256=utfSCdhRtw6mtljc-WMaG4GqRn7yggyZNcOeCota40Y,337295
3
- RobotLog2RQM/__init__.py,sha256=YKDTJjDsnQkr5X-gjjO8opwKUVKm6kc8sIUpURYMk48,596
4
- RobotLog2RQM/__main__.py,sha256=JabttEncy80antJWeGVmjoXyiF1DyXxkxdW4xLuHzT0,681
5
- RobotLog2RQM/robotlog2rqm.py,sha256=Q35lrrDDv3skXJO6IH9hRRoU0Ua0kSKOuBdpNeF_v5s,33531
6
- RobotLog2RQM/version.py,sha256=XS3UlCLUKUcaTFjKhJ6Pj4bN04WACmWEz7cmWTadZ1A,916
7
- RobotLog2RQM/RQM_templates/buildrecord.xml,sha256=uGot7pNOjPR8do0JsJi0Lz3OCU9NMhODRd428QgvHh4,1498
8
- RobotLog2RQM/RQM_templates/configuration.xml,sha256=NrFDv51fuGhgeMiZuRhQ5q_UJ0u_pWzdxisIF5AJs74,1378
9
- RobotLog2RQM/RQM_templates/executionresult.xml,sha256=WTp4qDk29peBc0ll6GHVXX_kF_YBsOVjy9vBzoz7_2k,2160
10
- RobotLog2RQM/RQM_templates/executionworkitem.xml,sha256=3GYO-nvXcG-HDQZnDzGwYZSQOWAUNckT3_GUa8Ze2Eo,1511
11
- RobotLog2RQM/RQM_templates/suiteexecutionrecord.xml,sha256=9GAs2WqZMkFJSNEZULm9BJvQy02dl_2JMecpQPHGTPA,1389
12
- RobotLog2RQM/RQM_templates/testcase.xml,sha256=zovFKj-37QHn2S8mMA_9RnAJ3zBmDJkJj31yelsnFFI,2167
13
- RobotLog2RQM/RQM_templates/testsuite.xml,sha256=r2ijEsyPoE7qzCtUxgIHDOEcqUveDN4SMf9HSE9b0ZU,1326
14
- RobotLog2RQM/RQM_templates/testsuitelog.xml,sha256=l-NlaCyk6Ben76PElXKOHfMlEvyQ-e9MOZ6-F9HvwDQ,1920
15
- robotframework_robotlog2rqm-1.4.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
- robotframework_robotlog2rqm-1.4.2.dist-info/METADATA,sha256=JEeig_C3ceOdSoP9UauelzVEHm6l6S7gvd8S6Us8zO4,9863
17
- robotframework_robotlog2rqm-1.4.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
18
- robotframework_robotlog2rqm-1.4.2.dist-info/entry_points.txt,sha256=-Xug2kDJW2LtcSADEVPtCwa337twCy2iGh5aK7xApHA,73
19
- robotframework_robotlog2rqm-1.4.2.dist-info/top_level.txt,sha256=jb_Gt6W44FoOLtGfBe7RzqCLaquhihkEWvSI1zjXDHc,13
20
- robotframework_robotlog2rqm-1.4.2.dist-info/RECORD,,