+"""
+
+ # Response Headers
+ if response.get('headers'):
+ html += """
+
+ ๐ Response Headers
+
"""
+ for key, value in response['headers'].items():
+ html += f"{self.escape_html(key)}: {self.escape_html(value)}\n"
+ html += """
+
+"""
+
+ # Response Body
+ if response.get('body'):
+ html += f"""
+
+ ๐ฆ Response Body
+
{self.escape_html(response['body'][:3000])}
+
+"""
+
+ html += """
+
+"""
+ html += """
+
+"""
+
+ # Context Information (collapsible, compact)
+ if structured.get('context'):
+ html += """
+
+ โน๏ธ Test Context
+
+
+"""
+ for ctx in structured['context']:
+ html += f"""
+
+
{self.escape_html(ctx['key'])}
+
{self.escape_html(str(ctx['value']))}
+
+"""
+ html += """
+
+
+
+"""
+
+ return html
+
+ def escape_html(self, text):
+ """Escape HTML special characters"""
+ if text is None:
+ return ""
+ text = str(text)
+ return (text
+ .replace('&', '&')
+ .replace('<', '<')
+ .replace('>', '>')
+ .replace('"', '"')
+ .replace("'", '''))
+
+ def generate_html(self, output_file='test-report-enhanced.html'):
+ """Generate enhanced HTML report"""
+
+ # Calculate pass rate
+ pass_rate = (self.results['passed'] / self.results['total'] * 100) if self.results['total'] > 0 else 0
+
+ # Group tests by category
+ tests_by_category = {}
+ for test in self.results['tests']:
+ category = test['category']
+ if category not in tests_by_category:
+ tests_by_category[category] = []
+ tests_by_category[category].append(test)
+
+ # Sort categories
+ sorted_categories = sorted(tests_by_category.keys())
+
+ html = f"""
+
+
+
+
+ .NET CDA SDK - Enhanced Test Report
+
+
+
+
+
+
.NET CDA SDK Test Report
+
Enhanced Integration Test Results - {datetime.now().strftime('%B %d, %Y at %I:%M %p')}
+
+
+
+
+
{self.results['total']}
+
Total Tests
+
+
+
{self.results['passed']}
+
Passed
+
+
+
{self.results['failed']}
+
Failed
+
+
+
{self.results['skipped']}
+
Skipped
+
+
+
+
+
Pass Rate
+
+
+ {pass_rate:.1f}%
+
+
+
+
+
+
Test Results by Category
+"""
+
+ # Generate category sections
+ for category in sorted_categories:
+ tests = tests_by_category[category]
+ passed = sum(1 for t in tests if t['outcome'] == 'Passed')
+ failed = sum(1 for t in tests if t['outcome'] == 'Failed')
+ skipped = sum(1 for t in tests if t['outcome'] == 'NotExecuted')
+
+ html += f"""
+
+
+
+"""
+
+ for test_idx, test in enumerate(tests):
+ status_class = 'status-passed' if test['outcome'] == 'Passed' else 'status-failed' if test['outcome'] == 'Failed' else 'status-skipped'
+ test_id = f"test-{category}-{test_idx}"
+
+ html += f"""
+
+
+
{test['name']}
+"""
+
+ # Add enhanced test details
+ details_html = self.generate_test_details_html(test)
+ if details_html or test.get('error_message') or test.get('error_stacktrace'):
+ html += f"""
+
+"""
+
+ # Add error details if failed
+ if test['outcome'] == 'Failed' and (test['error_message'] or test['error_stacktrace']):
+ html += """
+
+"""
+ if test['error_message']:
+ html += f"""
+
Error: {self.escape_html(test['error_message'])}
+"""
+ if test['error_stacktrace']:
+ html += f"""
+
+ Stack Trace
+
{self.escape_html(test['error_stacktrace'])}
+
+"""
+ html += """
+
+"""
+
+ # Add enhanced details
+ html += details_html
+
+ html += """
+
+"""
+
+ html += f"""
+
+
{test['outcome']}
+
{test['duration']}
+
+"""
+
+ html += """
+
+
+
+
+"""
+
+ html += f"""
+
+
+
+
+
+
+
+
+"""
+
+ # Write HTML file
+ with open(output_file, 'w', encoding='utf-8') as f:
+ f.write(html)
+
+ print(f"โ Enhanced HTML report generated: {output_file}")
+ return output_file
+
+
+def main():
+ """Main entry point"""
+ print("="*80)
+ print("๐งช .NET Enhanced Test Report Generator")
+ print("="*80)
+ print("Features: Expected/Actual, HTTP Requests, cURL, Responses")
+ print("="*80)
+
+ # Check for .trx file
+ trx_file = None
+
+ if len(sys.argv) > 1:
+ trx_file = sys.argv[1]
+ else:
+ # Look for .trx files in TestResults directory
+ test_results_dir = './TestResults'
+ if os.path.exists(test_results_dir):
+ trx_files = [f for f in os.listdir(test_results_dir) if f.endswith('.trx')]
+ if trx_files:
+ trx_file = os.path.join(test_results_dir, trx_files[0])
+
+ if not trx_file or not os.path.exists(trx_file):
+ print("\nโ Error: No .trx file found!")
+ print("\nUsage:")
+ print(" python3 generate_enhanced_html_report.py ")
+ print("\nOr run tests first to generate .trx file:")
+ print(" dotnet test --logger 'trx;LogFileName=test-results.trx' --results-directory ./TestResults")
+ sys.exit(1)
+
+ print(f"\n๐ Input file: {trx_file}")
+
+ # Generate report
+ generator = EnhancedTestReportGenerator(trx_file)
+
+ print("\nโณ Parsing test results...")
+ if not generator.parse_trx():
+ print("โ Failed to parse TRX file")
+ sys.exit(1)
+
+ print(f"โ Found {generator.results['total']} tests")
+ print(f" โข Passed: {generator.results['passed']}")
+ print(f" โข Failed: {generator.results['failed']}")
+ print(f" โข Skipped: {generator.results['skipped']}")
+
+ print("\nโณ Generating enhanced HTML report...")
+ from datetime import datetime
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+ output_file = generator.generate_html(f'test-report-enhanced_{timestamp}.html')
+
+ print("\n" + "="*80)
+ print("โ SUCCESS! Enhanced HTML report generated")
+ print("="*80)
+ print(f"\n๐ Open the report: {os.path.abspath(output_file)}")
+ print("\nIn your browser:")
+ print(f" file://{os.path.abspath(output_file)}")
+
+ # Summary
+ print("\n๐ Summary:")
+ print(f" Total Tests: {generator.results['total']}")
+ print(f" Passed: {generator.results['passed']} ({generator.results['passed']/generator.results['total']*100:.1f}%)")
+ print(f" Failed: {generator.results['failed']}")
+ print(f" Skipped: {generator.results['skipped']}")
+
+ print("\n๐ Done!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/Scripts/generate_html_report.py b/Scripts/generate_html_report.py
new file mode 100644
index 0000000..ca84a43
--- /dev/null
+++ b/Scripts/generate_html_report.py
@@ -0,0 +1,660 @@
+#!/usr/bin/env python3
+"""
+HTML Test Report Generator for .NET Test Results
+Converts .trx files to beautiful HTML reports
+No external dependencies - uses only Python standard library
+"""
+
+import xml.etree.ElementTree as ET
+import os
+import sys
+from datetime import datetime
+import json
+
+class TestReportGenerator:
+ def __init__(self, trx_file_path):
+ self.trx_file = trx_file_path
+ self.results = {
+ 'total': 0,
+ 'passed': 0,
+ 'failed': 0,
+ 'skipped': 0,
+ 'duration': '0s',
+ 'tests': []
+ }
+
+ def parse_trx(self):
+ """Parse .trx XML file and extract test results"""
+ try:
+ tree = ET.parse(self.trx_file)
+ root = tree.getroot()
+
+ # Get namespace
+ ns = {'': 'http://microsoft.com/schemas/VisualStudio/TeamTest/2010'}
+
+ # Get summary
+ result_summary = root.find('.//ResultSummary', ns)
+ counters = result_summary.find('Counters', ns) if result_summary else None
+
+ if counters is not None:
+ self.results['total'] = int(counters.get('total', 0))
+ self.results['passed'] = int(counters.get('passed', 0))
+ self.results['failed'] = int(counters.get('failed', 0))
+ self.results['skipped'] = int(counters.get('notExecuted', 0))
+
+ # Get test results
+ test_results = root.findall('.//UnitTestResult', ns)
+
+ for test_result in test_results:
+ test_name = test_result.get('testName', 'Unknown')
+ outcome = test_result.get('outcome', 'Unknown')
+ duration = test_result.get('duration', '0')
+
+ # Parse duration (format: HH:MM:SS.mmmmmmm)
+ try:
+ parts = duration.split(':')
+ if len(parts) == 3:
+ hours = int(parts[0])
+ minutes = int(parts[1])
+ seconds = float(parts[2])
+ total_seconds = hours * 3600 + minutes * 60 + seconds
+ duration_str = f"{total_seconds:.2f}s"
+ else:
+ duration_str = duration
+ except:
+ duration_str = duration
+
+ # Get error message if failed
+ error_message = None
+ error_stacktrace = None
+ output_elem = test_result.find('Output', ns)
+ if output_elem is not None:
+ error_info = output_elem.find('ErrorInfo', ns)
+ if error_info is not None:
+ message_elem = error_info.find('Message', ns)
+ stacktrace_elem = error_info.find('StackTrace', ns)
+ if message_elem is not None:
+ error_message = message_elem.text
+ if stacktrace_elem is not None:
+ error_stacktrace = stacktrace_elem.text
+
+ # Get test category
+ test_def_id = test_result.get('testId', '')
+ test_def = root.find(f".//UnitTest[@id='{test_def_id}']", ns)
+ category = 'General'
+ if test_def is not None:
+ test_method = test_def.find('.//TestMethod', ns)
+ if test_method is not None:
+ class_name = test_method.get('className', '')
+ # Extract category from namespace
+ if 'Integration' in class_name:
+ parts = class_name.split('.')
+ if len(parts) >= 5:
+ category = parts[4] # e.g., "QueryTests", "EntryTests"
+
+ self.results['tests'].append({
+ 'name': test_name,
+ 'outcome': outcome,
+ 'duration': duration_str,
+ 'category': category,
+ 'error_message': error_message,
+ 'error_stacktrace': error_stacktrace
+ })
+
+ return True
+
+ except Exception as e:
+ print(f"Error parsing TRX file: {e}")
+ return False
+
+ def generate_html(self, output_file='test-report.html'):
+ """Generate beautiful HTML report"""
+
+ # Calculate pass rate
+ pass_rate = (self.results['passed'] / self.results['total'] * 100) if self.results['total'] > 0 else 0
+
+ # Group tests by category
+ tests_by_category = {}
+ for test in self.results['tests']:
+ category = test['category']
+ if category not in tests_by_category:
+ tests_by_category[category] = []
+ tests_by_category[category].append(test)
+
+ # Sort categories
+ sorted_categories = sorted(tests_by_category.keys())
+
+ html = f"""
+
+
+
+
+ .NET CDA SDK - Test Report
+
+
+
+
+
+
.NET CDA SDK Test Report
+
Integration Test Results - {datetime.now().strftime('%B %d, %Y at %I:%M %p')}
+
+
+
+
+
{self.results['total']}
+
Total Tests
+
+
+
{self.results['passed']}
+
Passed
+
+
+
{self.results['failed']}
+
Failed
+
+
+
{self.results['skipped']}
+
Skipped
+
+
+
+
+
Pass Rate
+
+
+ {pass_rate:.1f}%
+
+
+
+
+
+
Test Results by Category
+"""
+
+ # Generate category sections
+ for category in sorted_categories:
+ tests = tests_by_category[category]
+ passed = sum(1 for t in tests if t['outcome'] == 'Passed')
+ failed = sum(1 for t in tests if t['outcome'] == 'Failed')
+ skipped = sum(1 for t in tests if t['outcome'] == 'NotExecuted')
+
+ html += f"""
+