Files
plugins/openedx-tenant-api/test_e2e/report_generator.py
DamarKusumo 2b7027e37d Add openedx-tenant-api plugin
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 08:20:57 +07:00

241 lines
8.5 KiB
Python

"""Markdown report generator for E2E test results."""
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
logger = logging.getLogger(__name__)
class ReportGenerator:
"""Generate markdown reports for E2E test execution."""
def __init__(self, report_dir: Path, test_name: str = "E2E Tenant Test"):
"""
Initialize the report generator.
Args:
report_dir: Directory to save reports
test_name: Name of the test suite
"""
self.report_dir = Path(report_dir)
self.report_dir.mkdir(parents=True, exist_ok=True)
self.test_name = test_name
self.steps: List[Dict[str, Any]] = []
self.cors_errors: List[Dict[str, Any]] = []
self.start_time: Optional[datetime] = None
self.end_time: Optional[datetime] = None
def start_test(self) -> None:
"""Mark the start of the test."""
self.start_time = datetime.now()
logger.info(f"Starting test: {self.test_name}")
def end_test(self) -> None:
"""Mark the end of the test."""
self.end_time = datetime.now()
logger.info(f"Test completed: {self.test_name}")
def start_step(self, step_name: str) -> None:
"""
Mark the start of a test step.
Args:
step_name: Name of the step
"""
step = {
"name": step_name,
"status": "in_progress",
"start_time": datetime.now(),
"end_time": None,
"message": "",
"screenshot": None,
}
self.steps.append(step)
logger.info(f"Starting step: {step_name}")
def end_step(
self,
status: str,
message: str = "",
screenshot_path: Optional[str] = None,
) -> None:
"""
Mark the end of the current test step.
Args:
status: "PASS", "FAIL", or "SKIP"
message: Additional message about the step
screenshot_path: Path to screenshot if captured
"""
if not self.steps:
logger.warning("No step to end")
return
step = self.steps[-1]
step["status"] = status
step["end_time"] = datetime.now()
step["message"] = message
step["screenshot"] = screenshot_path
duration = (step["end_time"] - step["start_time"]).total_seconds()
logger.info(f"Step '{step['name']}' completed: {status} ({duration:.2f}s)")
def add_cors_errors(self, errors: List[Dict[str, Any]]) -> None:
"""
Add CORS errors to the report.
Args:
errors: List of CORS error dictionaries
"""
self.cors_errors.extend(errors)
if errors:
logger.warning(f"Added {len(errors)} CORS errors to report")
def generate_report(self) -> Path:
"""
Generate the markdown report.
Returns:
Path to the generated report file
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_file = self.report_dir / f"e2e_report_{timestamp}.md"
content = self._build_report_content()
report_file.write_text(content, encoding="utf-8")
logger.info(f"Report generated: {report_file}")
return report_file
def _build_report_content(self) -> str:
"""Build the markdown report content."""
lines = []
# Header
lines.append(f"# {self.test_name} Report")
lines.append("")
lines.append(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
if self.start_time and self.end_time:
duration = (self.end_time - self.start_time).total_seconds()
lines.append(f"**Duration:** {duration:.2f} seconds")
lines.append("")
# Summary
lines.append("## Summary")
lines.append("")
total_steps = len(self.steps)
passed = sum(1 for s in self.steps if s["status"] == "PASS")
failed = sum(1 for s in self.steps if s["status"] == "FAIL")
skipped = sum(1 for s in self.steps if s["status"] == "SKIP")
lines.append(f"- **Total Steps:** {total_steps}")
lines.append(f"- **Passed:** {passed}")
lines.append(f"- **Failed:** {failed}")
lines.append(f"- **Skipped:** {skipped} ⏭️")
lines.append(f"- **CORS Errors:** {len(self.cors_errors)}")
lines.append("")
# Overall status
if failed > 0:
lines.append("**Overall Status:** ❌ FAILED")
else:
lines.append("**Overall Status:** ✅ PASSED")
lines.append("")
# Steps detail
lines.append("## Test Steps")
lines.append("")
for i, step in enumerate(self.steps, 1):
status_icon = "" if step["status"] == "PASS" else "" if step["status"] == "FAIL" else "⏭️"
lines.append(f"### {i}. {step['name']} {status_icon}")
lines.append("")
if step["start_time"] and step["end_time"]:
duration = (step["end_time"] - step["start_time"]).total_seconds()
lines.append(f"**Duration:** {duration:.2f}s")
lines.append("")
if step["message"]:
lines.append(f"**Message:** {step['message']}")
lines.append("")
if step["screenshot"]:
screenshot_rel = Path(step["screenshot"]).name
lines.append(f"**Screenshot:** `{screenshot_rel}`")
lines.append("")
lines.append(f"![{step['name']}]({screenshot_rel})")
lines.append("")
lines.append("---")
lines.append("")
# CORS Errors section
lines.append("## CORS Issues")
lines.append("")
if self.cors_errors:
lines.append(f"**{len(self.cors_errors)} CORS error(s) detected:**")
lines.append("")
for i, error in enumerate(self.cors_errors, 1):
lines.append(f"### Error {i}")
lines.append("")
lines.append(f"- **Type:** {error.get('type', 'unknown')}")
lines.append(f"- **Message:** ```{error.get('text', 'N/A')}```")
if error.get("location"):
lines.append(f"- **Location:** {error['location']}")
lines.append("")
else:
lines.append("✅ No CORS errors detected during test execution.")
lines.append("")
# Recommendations
lines.append("## Recommendations")
lines.append("")
if failed > 0:
lines.append("### Failed Steps")
lines.append("")
for step in self.steps:
if step["status"] == "FAIL":
lines.append(f"- **{step['name']}:** {step['message']}")
# Check for DNS/hosts file issues
if "DNS/Hostname issue" in step["message"]:
lines.append("")
lines.append("**Hosts File Fix Required:**")
lines.append("```")
lines.append("127.0.0.1 mondaytest.local.openedx.io")
lines.append("127.0.0.1 studio.mondaytest.local.openedx.io")
lines.append("127.0.0.1 mondaytest.apps.local.openedx.io")
lines.append("```")
lines.append("")
if self.cors_errors:
lines.append("### CORS Issues")
lines.append("")
lines.append("CORS errors were detected. Possible solutions:")
lines.append("")
lines.append("1. Verify `CORS_ORIGIN_WHITELIST` includes both `lms_domain` and `apps_domain` origins")
lines.append("2. Check that the tenant config has correct CORS settings")
lines.append("3. Enable the `cors_fix.py` plugin for global CORS settings")
lines.append("4. Verify the MFE is using the correct LMS_BASE_URL for the tenant")
lines.append("")
lines.append("### Next Steps")
lines.append("")
if failed == 0 and not self.cors_errors:
lines.append("- ✅ All tests passed. The tenant workflow is functioning correctly.")
else:
lines.append("- Review failed steps and error messages above")
lines.append("- Check screenshots for visual context")
lines.append("- Verify tenant configuration in Django admin")
lines.append("- Run tests again after fixes")
lines.append("")
return "\n".join(lines)