|
#!/usr/bin/env python3
from flask import Flask, request, jsonify
from playwright.sync_api import sync_playwright
import time
import re
import os
import base64
from datetime import datetime
app = Flask(__name__)
SCREENSHOT_DIR = "/opt/screenshots"
REPORT_DIR = "/opt/reports"
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
os.makedirs(REPORT_DIR, exist_ok=True)
def image_to_base64(image_path):
try:
with open(image_path, "rb") as f:
image_data = f.read()
return base64.b64encode(image_data).decode('utf-8')
except Exception as e:
print(f"Failed to encode image: {e}")
return None
def generate_html_report(status, message, screenshots):
status_text = "PASSED" if status == "success" else "FAILED"
status_color = "#10b981" if status == "success" else "#ef4444"
status_bg = "#d1fae5" if status == "success" else "#fee2e2"
status_icon = "✓" if status == "success" else "✗"
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
html = '<!DOCTYPE html>\n'
html += '<html>\n<head>\n'
html += '<meta charset="UTF-8">\n'
html += '<title>Test Report</title>\n'
html += '<style>\n'
html += '*{margin:0;padding:0;box-sizing:border-box;}\n'
html += 'body{font-family:Arial,sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);min-height:100vh;padding:40px 20px;}\n'
html += '.container{max-width:1200px;margin:0 auto;}\n'
html += '.card{background:white;border-radius:24px;box-shadow:0 20px 60px rgba(0,0,0,0.3);overflow:hidden;}\n'
html += '.header{background:linear-gradient(135deg,#667eea,#764ba2);color:white;padding:40px;text-align:center;}\n'
html += '.header h1{font-size:32px;margin-bottom:10px;}\n'
html += '.time{opacity:0.9;font-size:14px;}\n'
html += '.badge{display:inline-flex;align-items:center;gap:8px;padding:8px 20px;border-radius:40px;font-size:18px;font-weight:bold;margin-top:20px;background:' + status_bg + ';color:' + status_color + ';}\n'
html += '.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;padding:30px 40px;background:#f8f9fa;border-bottom:1px solid #e5e7eb;}\n'
html += '.stat{text-align:center;padding:20px;background:white;border-radius:16px;}\n'
html += '.stat-val{font-size:36px;font-weight:bold;color:#667eea;}\n'
html += '.stat-label{color:#6b7280;font-size:14px;margin-top:8px;}\n'
html += '.content{padding:40px;}\n'
html += '.title{font-size:20px;font-weight:bold;color:#1f2937;margin-bottom:20px;padding-bottom:10px;border-bottom:3px solid #667eea;display:inline-block;}\n'
html += '.info{background:#f8f9fa;border-radius:12px;padding:20px;margin:20px 0;}\n'
html += '.info-row{display:flex;padding:10px 0;border-bottom:1px solid #e5e7eb;}\n'
html += '.info-label{width:100px;font-weight:bold;color:#4b5563;}\n'
html += '.info-value{flex:1;color:#1f2937;}\n'
html += '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:24px;margin-top:24px;}\n'
html += '.item{background:#f8f9fa;border-radius:16px;overflow:hidden;box-shadow:0 4px 12px rgba(0,0,0,0.1);transition:transform 0.3s;}\n'
html += '.item:hover{transform:translateY(-5px);}\n'
html += '.item img{width:100%;height:auto;display:block;cursor:pointer;}\n'
html += '.caption{padding:12px 16px;background:white;font-weight:bold;display:flex;align-items:center;gap:8px;}\n'
html += '.num{width:28px;height:28px;background:#667eea;color:white;border-radius:50%;text-align:center;line-height:28px;font-size:12px;}\n'
html += '.footer{text-align:center;padding:30px;background:#f8f9fa;color:#9ca3af;font-size:12px;border-top:1px solid #e5e7eb;}\n'
html += '.modal{display:none;position:fixed;z-index:1000;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.9);cursor:pointer;}\n'
html += '.modal-img{margin:auto;display:block;max-width:90%;max-height:90%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}\n'
html += '@media(max-width:768px){.header{padding:24px;}.stats{padding:20px;gap:12px;}.content{padding:24px;}.grid{grid-template-columns:1fr;}}\n'
html += '</style>\n</head>\n<body>\n'
html += '<div class="container"><div class="card">\n'
html += '<div class="header"><h1>Auto Test Report</h1>\n'
html += '<div class="time">Time: ' + now + '</div>\n'
html += '<div class="badge"><span>' + status_icon + '</span><span>' + status_text + '</span></div>\n</div>\n'
html += '<div class="stats">\n'
html += '<div class="stat"><div class="stat-val">' + str(len(screenshots)) + '</div><div class="stat-label">Screenshots</div></div>\n'
html += '<div class="stat"><div class="stat-val">' + str(len(screenshots)) + '</div><div class="stat-label">Steps</div></div>\n'
html += '<div class="stat"><div class="stat-val">' + datetime.now().strftime('%H:%M:%S') + '</div><div class="stat-label">Duration</div></div>\n'
html += '</div>\n'
html += '<div class="content">\n'
html += '<div class="title">Test Details</div>\n'
html += '<div class="info">\n'
html += '<div class="info-row"><div class="info-label">Status</div><div class="info-value" style="color:' + status_color + ';font-weight:bold;">' + status_text + '</div></div>\n'
html += '<div class="info-row"><div class="info-label">Message</div><div class="info-value">' + message + '</div></div>\n'
html += '</div>\n'
html += '<div class="title" style="margin-top:40px;">Screenshots</div>\n'
html += '<div class="grid">\n'
for i, ss in enumerate(screenshots, 1):
filename = ss.split("/")[-1]
step_name = filename.replace(".png", "").replace("step", "Step").replace("_", " ").title()
img_base64 = image_to_base64(ss)
if img_base64:
html += '<div class="item">\n'
html += '<img src="data:image/png;base64,' + img_base64 + '" onclick="openModal(this.src)">\n'
html += '<div class="caption"><span class="num">' + str(i) + '</span><span>' + step_name + '</span></div>\n'
html += '</div>\n'
html += '</div>\n</div>\n'
html += '<div class="footer"><p>Powered by <strong>Dify + Skyvern</strong></p></div>\n'
html += '</div></div>\n'
html += '<div id="modal" class="modal" onclick="closeModal()"><img class="modal-img" id="modalImg"></div>\n'
html += '<script>\n'
html += 'function openModal(src){document.getElementById("modal").style.display="block";document.getElementById("modalImg").src=src;}\n'
html += 'function closeModal(){document.getElementById("modal").style.display="none";}\n'
html += '</script>\n</body>\n</html> |
所有评论(0)