robotframework-logxml2chunks 1.1.0__tar.gz → 1.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/LogXML2Chunks/LogXML2Chunks.py +193 -21
  2. {robotframework_logxml2chunks-1.1.0/robotframework_logxml2chunks.egg-info → robotframework_logxml2chunks-1.1.3}/PKG-INFO +1 -1
  3. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3/robotframework_logxml2chunks.egg-info}/PKG-INFO +1 -1
  4. robotframework_logxml2chunks-1.1.3/robotframework_logxml2chunks.egg-info/SOURCES.txt +14 -0
  5. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/setup.py +1 -1
  6. robotframework_logxml2chunks-1.1.0/requirements.txt +0 -4
  7. robotframework_logxml2chunks-1.1.0/robotframework_logxml2chunks.egg-info/SOURCES.txt +0 -25
  8. robotframework_logxml2chunks-1.1.0/tests/__init__.py +0 -3
  9. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/1_Example_Test_1_s1-t1.xml +0 -45
  10. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/1_Example_Test_1_s1-t1_log.html +0 -2457
  11. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/2_Example_Test_2_s1-t2.xml +0 -45
  12. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/2_Example_Test_2_s1-t2_log.html +0 -2457
  13. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/3_Example_Test_3_s1-t3.xml +0 -45
  14. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/3_Example_Test_3_s1-t3_log.html +0 -2457
  15. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/4_Example_Test_4_s1-t4.xml +0 -45
  16. robotframework_logxml2chunks-1.1.0/tests/example_logs/chunks/4_Example_Test_4_s1-t4_log.html +0 -2457
  17. robotframework_logxml2chunks-1.1.0/tests/example_logs/example.robot +0 -57
  18. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/LICENSE +0 -0
  19. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/LogXML2Chunks/__init__.py +0 -0
  20. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/LogXML2Chunks/cli.py +0 -0
  21. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/README.md +0 -0
  22. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/robotframework_logxml2chunks.egg-info/dependency_links.txt +0 -0
  23. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/robotframework_logxml2chunks.egg-info/entry_points.txt +0 -0
  24. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/robotframework_logxml2chunks.egg-info/not-zip-safe +0 -0
  25. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/robotframework_logxml2chunks.egg-info/requires.txt +0 -0
  26. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/robotframework_logxml2chunks.egg-info/top_level.txt +0 -0
  27. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/setup.cfg +0 -0
  28. {robotframework_logxml2chunks-1.1.0 → robotframework_logxml2chunks-1.1.3}/tests/test_logxml2chunks.py +0 -0
@@ -18,6 +18,28 @@ from pathlib import Path
18
18
 
19
19
  class LogXML2Chunks:
20
20
 
21
+ def __init__(self, debug=True, filename_prefix_pattern=None):
22
+ """
23
+ Initialize LogXML2Chunks instance.
24
+
25
+ Args:
26
+ debug: Enable debug output (default: True)
27
+ filename_prefix_pattern: Regex pattern to extract a custom prefix for chunk filenames.
28
+ Must contain one capture group that captures the prefix value.
29
+ The captured value will be added to the beginning of XML and HTML filenames.
30
+ Example: r"open_session\('(\w+)'" would extract 'DB' from
31
+ "open_session('DB', '10.0.0.1')" and create files like:
32
+ "1_DB_TestName_s1-t1.xml"
33
+ Default: None (no prefix extraction)
34
+ """
35
+ self.debug = debug
36
+ self.filename_prefix_pattern = re.compile(filename_prefix_pattern) if filename_prefix_pattern else None
37
+
38
+ def _debug_print(self, *args, **kwargs):
39
+ """Print only if debug mode is enabled."""
40
+ if self.debug:
41
+ print(*args, **kwargs)
42
+
21
43
  def _extract_steps_from_documentation(self, doc_text):
22
44
  """
23
45
  Extract steps from test documentation.
@@ -106,6 +128,83 @@ class LogXML2Chunks:
106
128
 
107
129
  return steps
108
130
 
131
+ def _extract_requirements_from_documentation(self, doc_text):
132
+ """
133
+ Extract requirements from test documentation.
134
+
135
+ Looks for sections marked as *Requirements*, *Requirements:* or Requirements:
136
+ and extracts the list items that follow (numbered or bulleted).
137
+
138
+ Args:
139
+ doc_text: The documentation text to parse
140
+
141
+ Returns:
142
+ List of requirement strings extracted from the documentation.
143
+ Returns empty list if no requirements found.
144
+ """
145
+ if not doc_text:
146
+ return []
147
+
148
+ requirements = []
149
+
150
+ # Pattern to find the Requirements section (case-insensitive, with or without asterisks/colon)
151
+ requirements_pattern = r'(?:\*)?Requirements(?:\*)?:?'
152
+
153
+ # Find the Requirements section
154
+ match = re.search(requirements_pattern, doc_text, re.IGNORECASE | re.MULTILINE)
155
+ if not match:
156
+ return []
157
+
158
+ # Get text after the Requirements marker
159
+ text_after_requirements = doc_text[match.end():]
160
+
161
+ # Split into lines and process
162
+ lines = text_after_requirements.split('\n')
163
+
164
+ for line in lines:
165
+ line = line.strip()
166
+
167
+ # Stop if we hit another section (starts with * and ends with *, or starts with * and contains :)
168
+ if line.startswith('*'):
169
+ if line.endswith('*') or ':' in line:
170
+ break
171
+
172
+ # Skip empty lines
173
+ if not line or line == '...':
174
+ continue
175
+
176
+ # Remove leading "..." from Robot Framework documentation
177
+ if line.startswith('...'):
178
+ line = line[3:].strip()
179
+
180
+ req_text = None
181
+
182
+ # Check for numbered list items (1. , 2. , etc.)
183
+ numbered_match = re.match(r'^\d+\.\s+(.+)$', line)
184
+ if numbered_match:
185
+ req_text = numbered_match.group(1).strip()
186
+
187
+ # Check for bulleted list items (- , * , etc.)
188
+ if not req_text:
189
+ bullet_match = re.match(r'^[-*]\s+(.+)$', line)
190
+ if bullet_match:
191
+ req_text = bullet_match.group(1).strip()
192
+
193
+ # If no list marker, treat as plain text requirement
194
+ if not req_text and line:
195
+ req_text = line
196
+
197
+ if req_text:
198
+ requirements.append(req_text)
199
+ continue
200
+
201
+ # If we already have requirements and this line doesn't match any pattern,
202
+ # it might be the end of the requirements section
203
+ if requirements and not line.startswith(('1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '*')):
204
+ break
205
+
206
+ return requirements
207
+
109
208
  def get_data_from_chunk(self, xml_filepath):
110
209
  """
111
210
  Extract and structure data from a test chunk XML file.
@@ -117,6 +216,7 @@ class LogXML2Chunks:
117
216
  Dictionary with test case data, or None if parsing fails
118
217
  """
119
218
  try:
219
+
120
220
  # Parse the XML file
121
221
  tree = ET.parse(xml_filepath)
122
222
  root = tree.getroot()
@@ -153,6 +253,9 @@ class LogXML2Chunks:
153
253
  # Extract steps from documentation
154
254
  test_steps = self._extract_steps_from_documentation(test_doc)
155
255
 
256
+ # Extract requirements from documentation
257
+ test_requirements = self._extract_requirements_from_documentation(test_doc)
258
+
156
259
  # Get test status
157
260
  status_element = test.find('status')
158
261
  test_status = status_element.get('status', 'UNKNOWN') if status_element is not None else 'UNKNOWN'
@@ -173,7 +276,7 @@ class LogXML2Chunks:
173
276
  # Calculate checksum based on test_name and documentation
174
277
  checksum_data = f"{test_name}{test_doc}".encode('utf-8')
175
278
  checksum = hashlib.md5(checksum_data).hexdigest()
176
-
279
+
177
280
  # Build result dictionary
178
281
  result = {
179
282
  'index': idx,
@@ -182,6 +285,7 @@ class LogXML2Chunks:
182
285
  'status': test_status,
183
286
  'documentation': test_doc,
184
287
  'steps': test_steps,
288
+ 'requirements': test_requirements,
185
289
  'source': test_source,
186
290
  'xml_file': str(xml_filepath),
187
291
  'checksum': checksum,
@@ -228,36 +332,94 @@ class LogXML2Chunks:
228
332
 
229
333
  # Check if folder exists
230
334
  if not folder.exists():
231
- print(f"Error: Folder does not exist: {folder_path}")
335
+ self._debug_print(f"Error: Folder does not exist: {folder_path}")
232
336
  return results
233
337
 
234
338
  if not folder.is_dir():
235
- print(f"Error: Path is not a directory: {folder_path}")
339
+ self._debug_print(f"Error: Path is not a directory: {folder_path}")
236
340
  return results
237
341
 
238
342
  # Find all XML files in the folder
239
343
  xml_files = sorted(folder.glob('*.xml'))
240
344
 
241
345
  if not xml_files:
242
- print(f"Warning: No XML files found in {folder_path}")
346
+ self._debug_print(f"Warning: No XML files found in {folder_path}")
243
347
  return results
244
348
 
245
- print(f"Found {len(xml_files)} XML files in {folder_path}")
349
+ self._debug_print(f"Found {len(xml_files)} XML files in {folder_path}")
246
350
 
247
351
  # Process each XML file
248
352
  for xml_file in xml_files:
249
- print(f" Processing: {xml_file.name}")
353
+ self._debug_print(f" Processing: {xml_file.name}")
250
354
  chunk_data = self.get_data_from_chunk(str(xml_file))
251
355
  results.append(chunk_data)
252
356
 
253
357
  # Print status
254
358
  if chunk_data.get('success', False):
255
- print(f" ✓ {chunk_data.get('test_name', 'Unknown')} - {chunk_data.get('status', 'Unknown')}")
359
+ self._debug_print(f" ✓ {chunk_data.get('test_name', 'Unknown')} - {chunk_data.get('status', 'Unknown')}")
256
360
  else:
257
- print(f" ✗ Error: {chunk_data.get('error', 'Unknown error')}")
361
+ self._debug_print(f" ✗ Error: {chunk_data.get('error', 'Unknown error')}")
258
362
 
259
363
  return results
260
364
 
365
+ def _extract_filename_prefix(self, test, suite, root=None):
366
+ """
367
+ Extract a custom prefix for filenames from test case or suite setup messages.
368
+
369
+ Uses the regex pattern provided in constructor to search XML messages
370
+ and extract a prefix value for chunk filenames.
371
+
372
+ Searches in this order:
373
+ 1. Current suite's SETUP keywords
374
+ 2. All parent suites' SETUP keywords (if root is provided)
375
+ 3. Test case keywords
376
+
377
+ Args:
378
+ test: Test case XML element
379
+ suite: Suite XML element containing the test
380
+ root: Root XML element (optional, for searching parent suites)
381
+
382
+ Returns:
383
+ Extracted prefix string (uppercase) or None if not found or pattern not set
384
+ """
385
+ if not self.filename_prefix_pattern:
386
+ return None
387
+
388
+ # Search in suite setup keywords first (current suite)
389
+ for msg in suite.findall('.//kw[@type="SETUP"]//msg'):
390
+ if msg.text:
391
+ match = self.filename_prefix_pattern.search(msg.text)
392
+ if match:
393
+ return match.group(1).upper()
394
+
395
+ # Search in parent suites' SETUP keywords (if root is provided)
396
+ if root is not None:
397
+ suite_id = suite.get('id', '')
398
+ # Build list of parent suite IDs: s1-s1-s1-s1 -> [s1-s1-s1, s1-s1, s1]
399
+ parent_ids = []
400
+ parts = suite_id.split('-')
401
+ for i in range(len(parts) - 1, 0, -1):
402
+ parent_ids.append('-'.join(parts[:i]))
403
+
404
+ # Search in each parent suite
405
+ for parent_id in parent_ids:
406
+ parent_suite = root.find(f".//suite[@id='{parent_id}']")
407
+ if parent_suite is not None:
408
+ for msg in parent_suite.findall('.//kw[@type="SETUP"]//msg'):
409
+ if msg.text:
410
+ match = self.filename_prefix_pattern.search(msg.text)
411
+ if match:
412
+ return match.group(1).upper()
413
+
414
+ # Search in test case keywords
415
+ for msg in test.findall('.//msg'):
416
+ if msg.text:
417
+ match = self.filename_prefix_pattern.search(msg.text)
418
+ if match:
419
+ return match.group(1).upper()
420
+
421
+ return None
422
+
261
423
  def split_to_chunks(self, output_xml_path, output_dir="chunked_results"):
262
424
  """
263
425
  Extract each test case from output.xml into separate XML files
@@ -281,19 +443,28 @@ class LogXML2Chunks:
281
443
  for test in suite.findall('test'):
282
444
  test_cases.append((suite, test))
283
445
 
284
- print(f"Found {len(test_cases)} test cases")
446
+ self._debug_print(f"Found {len(test_cases)} test cases")
285
447
 
286
448
  # Extract each test case
287
449
  for idx, (suite, test) in enumerate(test_cases, 1):
288
450
  test_name = test.get('name')
289
451
  test_id = test.get('id')
452
+
453
+ # Extract filename prefix (if pattern is configured)
454
+ prefix = self._extract_filename_prefix(test, suite, root)
290
455
 
291
- # Create a safe filename
456
+ # Create a safe filename (with prefix if available)
292
457
  safe_name = test_name.replace(' ', '_').replace('/', '_').replace('\\', '_')
293
- xml_filename = f"{idx}_{safe_name}_{test_id}.xml"
458
+ if prefix:
459
+ xml_filename = f"{idx}_{prefix}_{safe_name}_{test_id}.xml"
460
+ else:
461
+ xml_filename = f"{idx}_{safe_name}_{test_id}.xml"
294
462
  xml_filepath = output_path / xml_filename
295
463
 
296
- print(f"\n[{idx}/{len(test_cases)}] Processing: {test_name}")
464
+ if prefix:
465
+ self._debug_print(f"\n[{idx}/{len(test_cases)}] Processing: {test_name} (Prefix: {prefix})")
466
+ else:
467
+ self._debug_print(f"\n[{idx}/{len(test_cases)}] Processing: {test_name}")
297
468
 
298
469
  # Create a new XML document with only this test case
299
470
  new_root = ET.Element('robot', root.attrib)
@@ -366,12 +537,13 @@ class LogXML2Chunks:
366
537
  ET.indent(new_tree, space=' ')
367
538
  new_tree.write(xml_filepath, encoding='UTF-8', xml_declaration=True)
368
539
 
369
- print(f" ✓ Created XML: {xml_filepath}")
540
+ self._debug_print(f" ✓ Created XML: {xml_filepath}")
370
541
 
371
542
  # Generate HTML report using rebot
372
- # html_filename = f"{safe_name}_{test_id}.html"
373
- log_filename = f"{idx}_{safe_name}_{test_id}_log.html"
374
- # html_filepath = output_path / html_filename
543
+ if prefix:
544
+ log_filename = f"{idx}_{prefix}_{safe_name}_{test_id}_log.html"
545
+ else:
546
+ log_filename = f"{idx}_{safe_name}_{test_id}_log.html"
375
547
  log_filepath = output_path / log_filename
376
548
 
377
549
  try:
@@ -394,13 +566,13 @@ class LogXML2Chunks:
394
566
  # With --NoStatusRC, rebot returns 0 on success regardless of test results
395
567
  # Only non-zero codes indicate actual errors (invalid data, missing files, etc.)
396
568
  if result.returncode == 0:
397
- print(f" ✓ Generated log: {log_filepath}")
569
+ self._debug_print(f" ✓ Generated log: {log_filepath}")
398
570
  else:
399
- print(f" ✗ Failed to generate report (exit code: {result.returncode})")
571
+ self._debug_print(f" ✗ Failed to generate report (exit code: {result.returncode})")
400
572
  if result.stderr:
401
- print(f" Error: {result.stderr}")
573
+ self._debug_print(f" Error: {result.stderr}")
402
574
 
403
575
  except FileNotFoundError:
404
- print(f" ✗ Error: rebot command not found. Please install robotframework.")
576
+ self._debug_print(f" ✗ Error: rebot command not found. Please install robotframework.")
405
577
  except Exception as e:
406
- print(f" ✗ Error generating report: {str(e)}, index number = {idx}")
578
+ self._debug_print(f" ✗ Error generating report: {str(e)}, index number = {idx}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-logxml2chunks
3
- Version: 1.1.0
3
+ Version: 1.1.3
4
4
  Summary: Extract individual test cases from Robot Framework output.xml into separate chunks
5
5
  Home-page: https://github.com/ajadach/robotframework-LogXML2Chunks
6
6
  Author: Artur Jadach
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-logxml2chunks
3
- Version: 1.1.0
3
+ Version: 1.1.3
4
4
  Summary: Extract individual test cases from Robot Framework output.xml into separate chunks
5
5
  Home-page: https://github.com/ajadach/robotframework-LogXML2Chunks
6
6
  Author: Artur Jadach
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ LogXML2Chunks/LogXML2Chunks.py
5
+ LogXML2Chunks/__init__.py
6
+ LogXML2Chunks/cli.py
7
+ robotframework_logxml2chunks.egg-info/PKG-INFO
8
+ robotframework_logxml2chunks.egg-info/SOURCES.txt
9
+ robotframework_logxml2chunks.egg-info/dependency_links.txt
10
+ robotframework_logxml2chunks.egg-info/entry_points.txt
11
+ robotframework_logxml2chunks.egg-info/not-zip-safe
12
+ robotframework_logxml2chunks.egg-info/requires.txt
13
+ robotframework_logxml2chunks.egg-info/top_level.txt
14
+ tests/test_logxml2chunks.py
@@ -10,7 +10,7 @@ long_description = (this_directory / "README.md").read_text(encoding='utf-8')
10
10
 
11
11
  setup(
12
12
  name='robotframework-logxml2chunks',
13
- version='1.1.0',
13
+ version='1.1.3',
14
14
  author='Artur Jadach',
15
15
  author_email='artur.k.ziolkowski@example.com',
16
16
  description='Extract individual test cases from Robot Framework output.xml into separate chunks',
@@ -1,4 +0,0 @@
1
- # Production dependencies
2
- robotframework>=4.0
3
- pytest>=7.0
4
- pytest-cov>=4.0
@@ -1,25 +0,0 @@
1
- LICENSE
2
- README.md
3
- requirements.txt
4
- setup.py
5
- LogXML2Chunks/LogXML2Chunks.py
6
- LogXML2Chunks/__init__.py
7
- LogXML2Chunks/cli.py
8
- robotframework_logxml2chunks.egg-info/PKG-INFO
9
- robotframework_logxml2chunks.egg-info/SOURCES.txt
10
- robotframework_logxml2chunks.egg-info/dependency_links.txt
11
- robotframework_logxml2chunks.egg-info/entry_points.txt
12
- robotframework_logxml2chunks.egg-info/not-zip-safe
13
- robotframework_logxml2chunks.egg-info/requires.txt
14
- robotframework_logxml2chunks.egg-info/top_level.txt
15
- tests/__init__.py
16
- tests/test_logxml2chunks.py
17
- tests/example_logs/example.robot
18
- tests/example_logs/chunks/1_Example_Test_1_s1-t1.xml
19
- tests/example_logs/chunks/1_Example_Test_1_s1-t1_log.html
20
- tests/example_logs/chunks/2_Example_Test_2_s1-t2.xml
21
- tests/example_logs/chunks/2_Example_Test_2_s1-t2_log.html
22
- tests/example_logs/chunks/3_Example_Test_3_s1-t3.xml
23
- tests/example_logs/chunks/3_Example_Test_3_s1-t3_log.html
24
- tests/example_logs/chunks/4_Example_Test_4_s1-t4.xml
25
- tests/example_logs/chunks/4_Example_Test_4_s1-t4_log.html
@@ -1,3 +0,0 @@
1
- """
2
- Tests initialization file.
3
- """
@@ -1,45 +0,0 @@
1
- <?xml version='1.0' encoding='UTF-8'?>
2
- <robot generator="Robot 7.3.2 (Python 3.10.12 on linux)" generated="2025-11-13T17:21:50.004523" rpa="false" schemaversion="5">
3
- <suite id="s1" name="Example" source="/home/ajadach/GIT_LAB/robotframework-LogXML2Chunks/tests/example_logs/example.robot">
4
- <test id="s1-t1" name="Example Test 1" line="15">
5
- <kw name="Log" owner="BuiltIn">
6
- <msg time="2025-11-13T17:21:50.036281" level="INFO">Executing Example Test 1</msg>
7
- <arg>Executing Example Test 1</arg>
8
- <doc>Logs the given message with the given level.</doc>
9
- <status status="PASS" start="2025-11-13T17:21:50.035959" elapsed="0.000421" />
10
- </kw>
11
- <doc>Example Test 1 Documentation
12
-
13
- *Steps / Expected*
14
- - Log to HTML / pass
15
-
16
- *Expected*
17
- - Log should be successful.</doc>
18
- <tag>test_1</tag>
19
- <status status="PASS" start="2025-11-13T17:21:50.034956" elapsed="0.001694" />
20
- </test>
21
- <doc>Example suite level documentation.
22
-
23
-
24
- *Steps*
25
- - Example Test 1
26
- - Example Test 2
27
- - Example Test 3
28
- - Example Test 4
29
-
30
- *Expected*
31
- - All test cases should be successful.</doc>
32
- </suite>
33
- <statistics>
34
- <total>
35
- <stat pass="1" fail="0" skip="0">All Tests</stat>
36
- </total>
37
- <tag>
38
- <stat pass="1" fail="0" skip="0">test_1</stat>
39
- </tag>
40
- <suite>
41
- <stat name="Example" id="s1" pass="1" fail="0" skip="0" />
42
- </suite>
43
- </statistics>
44
- <errors />
45
- </robot>