Spaces:
Sleeping
Sleeping
| """ | |
| 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) | |