riazmo's picture
Upload 17 files
cfec14d verified
"""
UI Regression Testing Tool v2 - Phase 1
Main Gradio interface with single action button and blue theme.
"""
import gradio as gr
import os
import sys
from datetime import datetime
from pathlib import Path
# Add current directory to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from workflow import run_workflow_step_1, resume_workflow
# Log file
LOG_FILE = "app.log"
# Custom CSS for blue theme
CUSTOM_CSS = """
/* Main background */
.gradio-container {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%) !important;
}
/* Headers */
h1, h2, h3, .markdown-text h1, .markdown-text h2, .markdown-text h3 {
color: #00d4ff !important;
}
/* Labels */
label, .label-wrap {
color: #e0e0e0 !important;
background: #1e3a5f !important;
padding: 4px 8px !important;
border-radius: 4px !important;
}
/* Input fields */
input, textarea, .textbox {
background: #0d1b2a !important;
border: 1px solid #1e3a5f !important;
color: #ffffff !important;
}
/* Primary button - Blue */
.primary {
background: linear-gradient(135deg, #0066ff 0%, #0052cc 100%) !important;
border: none !important;
color: white !important;
}
.primary:hover {
background: linear-gradient(135deg, #0052cc 0%, #003d99 100%) !important;
}
/* Secondary elements */
.secondary {
background: #1e3a5f !important;
border: 1px solid #2d5a87 !important;
color: #e0e0e0 !important;
}
/* Code blocks / Logs */
.code-wrap, pre, code {
background: #0d1b2a !important;
border: 1px solid #1e3a5f !important;
color: #00ff88 !important;
}
/* Status boxes */
.output-class, .input-class {
background: #0d1b2a !important;
border: 1px solid #1e3a5f !important;
}
/* Panels */
.panel {
background: rgba(30, 58, 95, 0.5) !important;
border: 1px solid #2d5a87 !important;
border-radius: 8px !important;
}
/* Success text */
.success {
color: #00ff88 !important;
}
/* Error text */
.error {
color: #ff4757 !important;
}
/* Warning text */
.warning {
color: #ffa502 !important;
}
"""
def clear_logs():
"""Clear log file for fresh start."""
with open(LOG_FILE, "w") as f:
f.write(f"===== New Session: {datetime.now()} =====\n\n")
def get_logs():
"""Get current log contents."""
try:
with open(LOG_FILE, "r") as f:
return f.read()
except:
return "Logs will appear here..."
def run_full_analysis(figma_key, figma_id, url, hf_token, progress=gr.Progress()):
"""
Combined action: Capture screenshots AND run analysis in one step.
"""
clear_logs()
print(f"\n{'='*60}")
print(f"๐Ÿš€ STARTING UI REGRESSION TEST")
print(f"{'='*60}")
print(f"โฐ Time: {datetime.now()}")
print(f"๐ŸŒ Website: {url}")
print(f"๐Ÿ“ Figma File: {figma_id}")
print(f"๐Ÿ”‘ Figma Key: {'โœ… Provided' if figma_key else 'โŒ Missing'}")
print(f"๐Ÿค— HF Token: {'โœ… Provided' if hf_token else 'โšช Not provided'}")
print(f"{'='*60}\n")
# Validation
if not figma_key or not figma_id or not url:
return (
"โŒ Please fill in all required fields:\nโ€ข Figma API Key\nโ€ข Figma File ID\nโ€ข Website URL",
None, None, None, None, get_logs()
)
execution_id = f"exec_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
thread_id = f"thread_{execution_id}"
try:
# ===== STEP 1: Capture Screenshots =====
progress(0.1, desc="๐Ÿ“ธ Capturing Figma screenshots...")
print(f"๐Ÿงต Thread ID: {thread_id}")
print(f"\n๐Ÿ“‹ STEP 1: Capturing Screenshots...")
print("-" * 40)
state = run_workflow_step_1(figma_id, figma_key, url, execution_id, thread_id, hf_token)
if not state or not state.values:
return "โŒ Workflow failed to start", None, None, None, None, get_logs()
values = state.values
status = values.get("status", "unknown")
if "failed" in status:
error = values.get("error_message", "Unknown error")
return f"โŒ Capture Failed: {error}", None, None, None, None, get_logs()
# Get captured screenshots
figma_shots = values.get("figma_screenshots", {})
website_shots = values.get("website_screenshots", {})
print(f"\nโœ… Screenshots captured successfully!")
progress(0.5, desc="๐Ÿ” Running visual analysis...")
# ===== STEP 2: Run Analysis =====
print(f"\n{'='*60}")
print(f"๐Ÿ” STEP 2: RUNNING VISUAL ANALYSIS")
print(f"{'='*60}")
state = resume_workflow(thread_id, user_approval=True)
if not state or not state.values:
return "โŒ Analysis failed - no state returned", None, None, None, None, get_logs()
values = state.values
status = values.get("status", "unknown")
if "failed" in status:
error = values.get("error_message", "Unknown error")
return f"โŒ Analysis Failed: {error}", None, None, None, None, get_logs()
progress(0.9, desc="๐Ÿ“Š Generating results...")
# Get results
overall_score = values.get("overall_score", 0)
viewport_scores = values.get("similarity_scores", {})
differences = values.get("visual_differences", [])
comparison_images = values.get("comparison_images", {})
figma_dims = values.get("figma_dimensions", {})
website_dims = values.get("website_dimensions", {})
print(f"\n{'='*60}")
print(f"โœ… ANALYSIS COMPLETE!")
print(f"{'='*60}")
# ===== Build Results =====
# Status message
status_msg = f"โœ… Analysis Complete!\n\n"
score_emoji = "๐ŸŸข" if overall_score >= 90 else "๐ŸŸก" if overall_score >= 70 else "๐Ÿ”ด"
status_msg += f"{score_emoji} Overall Similarity: {overall_score:.1f}%\n\n"
status_msg += "๐Ÿ“ฑ Viewport Scores:\n"
for vp, score in viewport_scores.items():
vp_emoji = "โœ…" if score >= 90 else "โš ๏ธ" if score >= 70 else "โŒ"
status_msg += f" {vp_emoji} {vp.capitalize()}: {score:.1f}%\n"
status_msg += f"\n๐Ÿ” Differences: {len(differences)} found\n"
# Detailed results
result_msg = f"{'='*50}\n"
result_msg += f"๐Ÿ“Š DETAILED ANALYSIS REPORT\n"
result_msg += f"{'='*50}\n\n"
result_msg += f"๐Ÿ“ธ Screenshots Captured:\n"
result_msg += f" Figma: {len(figma_shots)} viewports\n"
result_msg += f" Website: {len(website_shots)} viewports\n\n"
if differences:
result_msg += f"๐Ÿ” DIFFERENCES FOUND: {len(differences)}\n"
result_msg += "-" * 40 + "\n"
for i, diff in enumerate(differences, 1):
sev = diff.get("severity", "Medium")
sev_emoji = "๐Ÿ”ด HIGH" if sev == "High" else "๐ŸŸก MEDIUM" if sev == "Medium" else "๐ŸŸข LOW"
result_msg += f"\n[{i}] {diff.get('title', 'Unknown')}\n"
result_msg += f" Severity: {sev_emoji}\n"
result_msg += f" Category: {diff.get('category', 'N/A')}\n"
result_msg += f" Viewport: {diff.get('viewport', 'N/A')}\n"
if diff.get("description"):
desc = diff["description"][:150]
result_msg += f" Details: {desc}\n"
else:
result_msg += "\nโœ… No significant differences detected!\n"
result_msg += "The website matches the Figma design closely.\n"
# Get images
figma_preview = figma_shots.get("desktop") or figma_shots.get("mobile")
website_preview = website_shots.get("desktop") or website_shots.get("mobile")
comparison_img = comparison_images.get("desktop") or comparison_images.get("mobile")
gallery_images = list(comparison_images.values()) if comparison_images else None
progress(1.0, desc="โœ… Complete!")
return status_msg, result_msg, figma_preview, website_preview, comparison_img, get_logs()
except Exception as e:
import traceback
error_msg = f"โŒ Error: {str(e)}"
print(error_msg)
traceback.print_exc()
return error_msg, None, None, None, None, get_logs()
def create_interface():
"""Create the Gradio interface with blue theme."""
with gr.Blocks(
title="UI Regression Testing v2",
theme=gr.themes.Base(
primary_hue="blue",
secondary_hue="slate",
neutral_hue="slate",
),
css=CUSTOM_CSS
) as demo:
gr.Markdown("# ๐ŸŽจ UI Regression Testing Tool")
gr.Markdown("*Compare Figma designs with live websites to detect visual differences*")
with gr.Row():
# LEFT: Configuration
with gr.Column(scale=1):
gr.Markdown("### ๐Ÿ“ Configuration")
figma_key = gr.Textbox(
label="Figma API Key *",
type="password",
placeholder="figd_xxxxx..."
)
figma_id = gr.Textbox(
label="Figma File ID *",
placeholder="e.g., ENieX2p3Gy3TAtxaB36cZA"
)
website_url = gr.Textbox(
label="Website URL *",
placeholder="https://your-site.com"
)
hf_token = gr.Textbox(
label="HF Token (Optional)",
type="password",
placeholder="For future AI enhancements"
)
gr.Markdown("### ๐ŸŽฎ Action")
btn_run = gr.Button(
"๐Ÿš€ Run Full Analysis",
variant="primary",
size="lg"
)
gr.Markdown("""
*This will:*
1. Capture Figma design screenshots
2. Capture website screenshots
3. Compare and highlight differences
4. Generate similarity scores
""")
# RIGHT: Status & Results
with gr.Column(scale=2):
gr.Markdown("### ๐Ÿ“Š Results Summary")
status_box = gr.Textbox(
label="Status",
lines=8,
interactive=False
)
gr.Markdown("### ๐Ÿ“‹ Detailed Report")
results_box = gr.Textbox(
label="Analysis Details",
lines=12,
interactive=False
)
# Preview Section
gr.Markdown("---")
gr.Markdown("### ๐Ÿ–ผ๏ธ Screenshot Preview")
with gr.Row():
figma_preview = gr.Image(label="๐Ÿ“ Figma Design", height=250)
website_preview = gr.Image(label="๐ŸŒ Live Website", height=250)
# Comparison Section
gr.Markdown("---")
gr.Markdown("### ๐Ÿ” Visual Comparison")
gr.Markdown("*Red areas indicate differences between design and website*")
comparison_image = gr.Image(
label="Figma | Website | Differences",
height=500
)
# Logs Section
gr.Markdown("---")
with gr.Accordion("๐Ÿ“œ Execution Logs", open=False):
log_box = gr.Code(
label="Live Logs",
language="python",
lines=20,
interactive=False
)
log_timer = gr.Timer(value=2)
log_timer.tick(get_logs, outputs=[log_box])
# Button handler
btn_run.click(
run_full_analysis,
inputs=[figma_key, figma_id, website_url, hf_token],
outputs=[status_box, results_box, figma_preview, website_preview, comparison_image, log_box]
)
return demo
# Main entry point
if __name__ == "__main__":
# Setup logging
class Logger:
def __init__(self):
self.terminal = sys.stdout
Path(LOG_FILE).parent.mkdir(parents=True, exist_ok=True)
with open(LOG_FILE, "w") as f:
f.write(f"===== Application Started: {datetime.now()} =====\n\n")
self.log = open(LOG_FILE, "a")
def write(self, message):
self.terminal.write(message)
self.log.write(message)
self.log.flush()
def flush(self):
self.terminal.flush()
self.log.flush()
def isatty(self):
return hasattr(self.terminal, 'isatty') and self.terminal.isatty()
sys.stdout = Logger()
sys.stderr = Logger()
print(f"๐Ÿš€ Starting UI Regression Testing Tool v2")
print(f"๐Ÿ“… Date: {datetime.now()}")
print(f"๐Ÿ“ฆ Phase: 1 (Visual Comparison)")
# Create and launch
demo = create_interface()
demo.launch(server_name="0.0.0.0", server_port=7860)