""" 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)