""" Web Testing Suite for Hugging Face Spaces Interactive UI with Gradio for Performance, SEO, Security, Accessibility Testing """ import gradio as gr import time import ssl import socket import json import requests from urllib.parse import urljoin, urlparse from bs4 import BeautifulSoup from datetime import datetime import re from typing import Dict, List, Tuple # ============================================================================ # PERFORMANCE TESTING # ============================================================================ class PerformanceTester: def __init__(self, url: str): self.url = url def measure_ttfb(self) -> float: try: start = time.time() response = requests.get(self.url, stream=True, timeout=10) ttfb = time.time() - start return round(ttfb * 1000, 2) except Exception as e: return f"Error: {e}" def get_payload_size(self) -> Dict: try: response = requests.get(self.url, timeout=10) html_size = len(response.content) soup = BeautifulSoup(response.content, 'html.parser') scripts = len(soup.find_all('script', src=True)) stylesheets = len(soup.find_all('link', rel='stylesheet')) images = len(soup.find_all('img')) return { 'html_size_kb': round(html_size / 1024, 2), 'script_count': scripts, 'stylesheet_count': stylesheets, 'image_count': images, 'compression': response.headers.get('Content-Encoding', 'none') } except Exception as e: return {'error': str(e)} def test_all(self) -> str: result = f"⚑ **PERFORMANCE TEST RESULTS**\n\n" result += f"🌐 URL: {self.url}\n\n" recommendations = [] # TTFB ttfb = self.measure_ttfb() if isinstance(ttfb, float): if ttfb < 200: status = "βœ… Excellent" elif ttfb < 500: status = "⚠️ Needs Improvement" recommendations.append("πŸ”§ **TTFB Issue:** Reduce server response time by:\n β€’ Using a CDN\n β€’ Optimizing database queries\n β€’ Enabling caching\n β€’ Upgrading server resources") else: status = "❌ Poor" recommendations.append("🚨 **Critical TTFB Issue:** Immediate action required:\n β€’ Check server health and resources\n β€’ Implement Redis/Memcached caching\n β€’ Use a CDN (Cloudflare, AWS CloudFront)\n β€’ Optimize backend code\n β€’ Consider server upgrade") result += f"⏱️ **Time To First Byte:** {ttfb} ms {status}\n" else: result += f"⏱️ **Time To First Byte:** {ttfb}\n" # Payload payload = self.get_payload_size() if 'error' not in payload: result += f"\nπŸ“¦ **Page Size:** {payload['html_size_kb']} KB" if payload['html_size_kb'] > 1000: result += " ❌" recommendations.append("🚨 **Large Page Size:** Reduce HTML size:\n β€’ Minify HTML, CSS, and JavaScript\n β€’ Remove unused code\n β€’ Compress images\n β€’ Implement lazy loading") elif payload['html_size_kb'] > 500: result += " ⚠️" recommendations.append("πŸ”§ **Page Size Warning:** Consider:\n β€’ Enabling Gzip/Brotli compression\n β€’ Minifying resources\n β€’ Optimizing images") else: result += " βœ…" result += f"\nπŸ“œ **Scripts:** {payload['script_count']}" if payload['script_count'] > 20: result += " ⚠️" recommendations.append("πŸ”§ **Too Many Scripts:** Reduce JavaScript files:\n β€’ Bundle scripts together\n β€’ Remove unused libraries\n β€’ Use async/defer attributes\n β€’ Implement code splitting") result += f"\n🎨 **Stylesheets:** {payload['stylesheet_count']}" if payload['stylesheet_count'] > 10: result += " ⚠️" recommendations.append("πŸ”§ **Too Many Stylesheets:** Optimize CSS:\n β€’ Combine CSS files\n β€’ Remove unused styles\n β€’ Use CSS minification\n β€’ Consider critical CSS approach") result += f"\nπŸ–ΌοΈ **Images:** {payload['image_count']}" result += f"\nπŸ—œοΈ **Compression:** {payload['compression']}" if payload['compression'] == 'none': result += " ❌" recommendations.append("🚨 **No Compression:** Enable compression:\n β€’ Add Gzip/Brotli to server config\n β€’ For Apache: Enable mod_deflate\n β€’ For Nginx: Add gzip on;\n β€’ For Node.js: Use compression middleware") # Add recommendations section if recommendations: result += f"\n\n{'='*50}\n" result += f"πŸ’‘ **RECOMMENDATIONS**\n\n" result += "\n\n".join(recommendations) return result # ============================================================================ # SEO AUDITOR # ============================================================================ class SEOAuditor: def __init__(self, url: str): self.url = url self.soup = None def fetch_page(self): try: response = requests.get(self.url, timeout=10) self.soup = BeautifulSoup(response.content, 'html.parser') except Exception as e: return False return True def check_meta_tags(self) -> Dict: title = self.soup.find('title') description = self.soup.find('meta', attrs={'name': 'description'}) og_title = self.soup.find('meta', property='og:title') og_description = self.soup.find('meta', property='og:description') og_image = self.soup.find('meta', property='og:image') canonical = self.soup.find('link', rel='canonical') return { 'title': title.text if title else '❌ Missing', 'title_length': len(title.text) if title else 0, 'description': description.get('content')[:100] + '...' if description else '❌ Missing', 'og_title': 'βœ… Present' if og_title else '❌ Missing', 'og_description': 'βœ… Present' if og_description else '❌ Missing', 'og_image': 'βœ… Present' if og_image else '❌ Missing', 'canonical': 'βœ… Present' if canonical else '❌ Missing' } def check_headers(self) -> Dict: h1 = self.soup.find_all('h1') h2 = self.soup.find_all('h2') h3 = self.soup.find_all('h3') return { 'h1_count': len(h1), 'h1_text': [h.text.strip()[:50] for h in h1][:3], 'h2_count': len(h2), 'h3_count': len(h3), 'valid': len(h1) == 1 } def check_sitemap(self) -> Dict: sitemap_url = urljoin(self.url, '/sitemap.xml') try: resp = requests.get(sitemap_url, timeout=5) return {'exists': resp.status_code == 200} except: return {'exists': False} def audit(self) -> str: result = f"πŸ” **SEO AUDIT RESULTS**\n\n" result += f"🌐 URL: {self.url}\n\n" recommendations = [] if not self.fetch_page(): return result + "❌ Failed to fetch page" # Meta Tags meta = self.check_meta_tags() result += f"πŸ“ **Meta Tags**\n" # Title result += f"β€’ Title: {meta['title']}\n" result += f"β€’ Title Length: {meta['title_length']} chars " if 50 <= meta['title_length'] <= 60: result += f"βœ…\n" else: result += f"⚠️\n" if meta['title'] == '❌ Missing': recommendations.append("🚨 **Missing Title Tag:**\n β€’ Add Your Page Title in \n β€’ Keep it 50-60 characters\n β€’ Include primary keyword\n β€’ Make it unique for each page") elif meta['title_length'] < 50: recommendations.append("πŸ”§ **Title Too Short:**\n β€’ Expand to 50-60 characters\n β€’ Add descriptive keywords\n β€’ Include brand name") elif meta['title_length'] > 60: recommendations.append("πŸ”§ **Title Too Long:**\n β€’ Shorten to 50-60 characters\n β€’ Google truncates at ~60 chars\n β€’ Keep most important words first") # Description result += f"β€’ Description: {meta['description']}\n" if meta['description'] == '❌ Missing': recommendations.append("🚨 **Missing Meta Description:**\n β€’ Add \n β€’ Keep it 150-160 characters\n β€’ Include target keywords naturally\n β€’ Make it compelling for click-through") # Open Graph result += f"β€’ OG Title: {meta['og_title']}\n" result += f"β€’ OG Description: {meta['og_description']}\n" result += f"β€’ OG Image: {meta['og_image']}\n" if meta['og_title'] == '❌ Missing' or meta['og_description'] == '❌ Missing' or meta['og_image'] == '❌ Missing': recommendations.append("⚠️ **Missing Open Graph Tags:**\n β€’ Add \n β€’ Add \n β€’ Add \n β€’ Improves social media sharing appearance") # Canonical result += f"β€’ Canonical: {meta['canonical']}\n\n" if meta['canonical'] == '❌ Missing': recommendations.append("⚠️ **Missing Canonical Tag:**\n β€’ Add \n β€’ Prevents duplicate content issues\n β€’ Points to preferred URL version") # Headers headers = self.check_headers() result += f"πŸ“‹ **Header Structure**\n" result += f"β€’ H1 Count: {headers['h1_count']} {'βœ…' if headers['valid'] else '❌'}\n" if not headers['valid']: if headers['h1_count'] == 0: recommendations.append("🚨 **Missing H1 Tag:**\n β€’ Add exactly ONE

tag per page\n β€’ Should describe main page content\n β€’ Include primary keyword\n β€’ Must be unique on page") elif headers['h1_count'] > 1: recommendations.append("❌ **Multiple H1 Tags:**\n β€’ Use only ONE

per page\n β€’ Convert extra H1s to

or

\n β€’ Maintains proper heading hierarchy\n β€’ Better for SEO and accessibility") if headers['h1_text']: result += f"β€’ H1 Text: {headers['h1_text'][0]}\n" result += f"β€’ H2 Count: {headers['h2_count']}\n" result += f"β€’ H3 Count: {headers['h3_count']}\n\n" # Sitemap sitemap = self.check_sitemap() result += f"πŸ—ΊοΈ **Sitemap:** {'βœ… Found' if sitemap['exists'] else '❌ Not Found'}\n" if not sitemap['exists']: recommendations.append("❌ **Missing Sitemap:**\n β€’ Create sitemap.xml file\n β€’ List all important pages\n β€’ Submit to Google Search Console\n β€’ Update when adding new pages\n β€’ Use sitemap generator tools") # Add recommendations if recommendations: result += f"\n{'='*50}\n" result += f"πŸ’‘ **SEO RECOMMENDATIONS**\n\n" result += "\n\n".join(recommendations) return result # ============================================================================ # SECURITY TESTER # ============================================================================ class SecurityTester: def __init__(self, url: str): self.url = url self.domain = urlparse(url).netloc def check_ssl(self) -> Dict: try: context = ssl.create_default_context() with socket.create_connection((self.domain, 443), timeout=5) as sock: with context.wrap_socket(sock, server_hostname=self.domain) as ssock: cert = ssock.getpeercert() return { 'valid': True, 'expires': cert['notAfter'] } except Exception as e: return {'valid': False, 'error': str(e)} def check_security_headers(self) -> Dict: try: response = requests.get(self.url, timeout=10) headers = response.headers return { 'hsts': headers.get('Strict-Transport-Security', '❌'), 'x_content_type': headers.get('X-Content-Type-Options', '❌'), 'x_frame': headers.get('X-Frame-Options', '❌'), 'csp': headers.get('Content-Security-Policy', '❌'), 'referrer': headers.get('Referrer-Policy', '❌') } except Exception as e: return {'error': str(e)} def check_mixed_content(self) -> List[str]: if not self.url.startswith('https'): return ['⚠️ Page is not HTTPS'] try: response = requests.get(self.url, timeout=10) soup = BeautifulSoup(response.content, 'html.parser') mixed = [] for tag in soup.find_all(['img', 'script', 'link']): src = tag.get('src') or tag.get('href', '') if src.startswith('http://'): mixed.append(src[:50]) return mixed[:5] if mixed else ['βœ… No mixed content'] except: return ['Error checking'] def test_all(self) -> str: result = f"πŸ”’ **SECURITY TEST RESULTS**\n\n" result += f"🌐 URL: {self.url}\n\n" recommendations = [] # SSL ssl_info = self.check_ssl() if ssl_info['valid']: result += f"πŸ” **SSL Certificate:** βœ… Valid\n" result += f"β€’ Expires: {ssl_info['expires']}\n\n" else: result += f"πŸ” **SSL Certificate:** ❌ Invalid\n\n" recommendations.append("🚨 **SSL Certificate Issue:**\n β€’ Install valid SSL certificate\n β€’ Use Let's Encrypt (free)\n β€’ Or purchase from trusted CA\n β€’ Configure HTTPS redirect\n β€’ Update all internal links to HTTPS") # Security Headers headers = self.check_security_headers() if 'error' not in headers: result += f"πŸ›‘οΈ **Security Headers**\n" # HSTS hsts_present = headers['hsts'] != '❌' result += f"β€’ HSTS: {'βœ…' if hsts_present else '❌'}\n" if not hsts_present: recommendations.append("❌ **Missing HSTS Header:**\n β€’ Add: Strict-Transport-Security: max-age=31536000\n β€’ Forces HTTPS connections\n β€’ Prevents protocol downgrade attacks\n β€’ Apache: Header always set Strict-Transport-Security \"max-age=31536000\"\n β€’ Nginx: add_header Strict-Transport-Security \"max-age=31536000\";") # X-Content-Type-Options xct_present = headers['x_content_type'] != '❌' result += f"β€’ X-Content-Type-Options: {'βœ…' if xct_present else '❌'}\n" if not xct_present: recommendations.append("❌ **Missing X-Content-Type-Options:**\n β€’ Add: X-Content-Type-Options: nosniff\n β€’ Prevents MIME type sniffing\n β€’ Reduces XSS attack vectors\n β€’ Apache: Header set X-Content-Type-Options \"nosniff\"\n β€’ Nginx: add_header X-Content-Type-Options \"nosniff\";") # X-Frame-Options xfo_present = headers['x_frame'] != '❌' result += f"β€’ X-Frame-Options: {'βœ…' if xfo_present else '❌'}\n" if not xfo_present: recommendations.append("❌ **Missing X-Frame-Options:**\n β€’ Add: X-Frame-Options: SAMEORIGIN\n β€’ Prevents clickjacking attacks\n β€’ Blocks iframe embedding by other sites\n β€’ Apache: Header always set X-Frame-Options \"SAMEORIGIN\"\n β€’ Nginx: add_header X-Frame-Options \"SAMEORIGIN\";") # CSP csp_present = headers['csp'] != '❌' result += f"β€’ Content-Security-Policy: {'βœ…' if csp_present else '❌'}\n" if not csp_present: recommendations.append("⚠️ **Missing Content-Security-Policy:**\n β€’ Add CSP header to prevent XSS\n β€’ Example: Content-Security-Policy: default-src 'self'\n β€’ Restricts resource loading sources\n β€’ Start with report-only mode\n β€’ Use CSP evaluator tools to test") # Referrer Policy ref_present = headers['referrer'] != '❌' result += f"β€’ Referrer-Policy: {'βœ…' if ref_present else '❌'}\n\n" if not ref_present: recommendations.append("⚠️ **Missing Referrer-Policy:**\n β€’ Add: Referrer-Policy: strict-origin-when-cross-origin\n β€’ Controls referrer information\n β€’ Protects user privacy\n β€’ Apache: Header set Referrer-Policy \"strict-origin-when-cross-origin\"\n β€’ Nginx: add_header Referrer-Policy \"strict-origin-when-cross-origin\";") # Mixed Content mixed = self.check_mixed_content() result += f"πŸ“¦ **Mixed Content Check**\n" has_mixed = False for item in mixed: result += f"β€’ {item}\n" if item.startswith('http://'): has_mixed = True if has_mixed: recommendations.append("❌ **Mixed Content Detected:**\n β€’ Change all HTTP resources to HTTPS\n β€’ Update image sources\n β€’ Update script/stylesheet URLs\n β€’ Check third-party integrations\n β€’ Use protocol-relative URLs: //example.com") # Add recommendations if recommendations: result += f"\n{'='*50}\n" result += f"πŸ’‘ **SECURITY RECOMMENDATIONS**\n\n" result += "\n\n".join(recommendations) return result # ============================================================================ # ACCESSIBILITY TESTER # ============================================================================ class AccessibilityTester: def __init__(self, url: str): self.url = url self.soup = None def fetch_page(self): try: response = requests.get(self.url, timeout=10) self.soup = BeautifulSoup(response.content, 'html.parser') return True except: return False def check_alt_attributes(self) -> Dict: images = self.soup.find_all('img') missing_alt = [] for img in images: if not img.get('alt'): src = img.get('src', 'No src')[:50] missing_alt.append(src) return { 'total': len(images), 'missing': len(missing_alt), 'samples': missing_alt[:5] } def check_form_labels(self) -> Dict: inputs = self.soup.find_all(['input', 'textarea', 'select']) missing_labels = 0 for inp in inputs: input_id = inp.get('id') aria_label = inp.get('aria-label') if input_id: label = self.soup.find('label', attrs={'for': input_id}) if not label and not aria_label: missing_labels += 1 elif not aria_label: missing_labels += 1 return { 'total': len(inputs), 'missing': missing_labels } def test_all(self) -> str: result = f"β™Ώ **ACCESSIBILITY TEST RESULTS**\n\n" result += f"🌐 URL: {self.url}\n\n" recommendations = [] if not self.fetch_page(): return result + "❌ Failed to fetch page" # Alt Attributes alt_check = self.check_alt_attributes() result += f"πŸ–ΌοΈ **Image Alt Attributes**\n" result += f"β€’ Total Images: {alt_check['total']}\n" result += f"β€’ Missing Alt: {alt_check['missing']} " if alt_check['missing'] == 0: result += f"βœ…\n" else: result += f"❌\n" recommendations.append(f"❌ **Missing Alt Attributes ({alt_check['missing']} images):**\n β€’ Add descriptive alt text to all images\n β€’ Example: \"Person\n β€’ Describe image content clearly\n β€’ Use empty alt=\"\" for decorative images\n β€’ Improves screen reader experience\n β€’ Benefits SEO") if alt_check['samples']: result += f"β€’ Sample Missing Alt: {', '.join(alt_check['samples'][:2])}\n" result += f"\n" # Form Labels label_check = self.check_form_labels() result += f"πŸ“ **Form Labels**\n" result += f"β€’ Total Inputs: {label_check['total']}\n" result += f"β€’ Missing Labels: {label_check['missing']} " if label_check['missing'] == 0: result += f"βœ…\n" else: result += f"❌\n" recommendations.append(f"❌ **Missing Form Labels ({label_check['missing']} inputs):**\n β€’ Add