Amaanali01 commited on
Commit
ebdc441
Β·
verified Β·
1 Parent(s): 3085ae9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +455 -0
app.py CHANGED
@@ -0,0 +1,455 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Web Testing Suite for Hugging Face Spaces
3
+ Interactive UI with Gradio for Performance, SEO, Security, Accessibility Testing
4
+ """
5
+
6
+ import gradio as gr
7
+ import time
8
+ import ssl
9
+ import socket
10
+ import json
11
+ import requests
12
+ from urllib.parse import urljoin, urlparse
13
+ from bs4 import BeautifulSoup
14
+ from datetime import datetime
15
+ import re
16
+ from typing import Dict, List, Tuple
17
+
18
+ # ============================================================================
19
+ # PERFORMANCE TESTING
20
+ # ============================================================================
21
+
22
+ class PerformanceTester:
23
+ def __init__(self, url: str):
24
+ self.url = url
25
+
26
+ def measure_ttfb(self) -> float:
27
+ try:
28
+ start = time.time()
29
+ response = requests.get(self.url, stream=True, timeout=10)
30
+ ttfb = time.time() - start
31
+ return round(ttfb * 1000, 2)
32
+ except Exception as e:
33
+ return f"Error: {e}"
34
+
35
+ def get_payload_size(self) -> Dict:
36
+ try:
37
+ response = requests.get(self.url, timeout=10)
38
+ html_size = len(response.content)
39
+
40
+ soup = BeautifulSoup(response.content, 'html.parser')
41
+
42
+ scripts = len(soup.find_all('script', src=True))
43
+ stylesheets = len(soup.find_all('link', rel='stylesheet'))
44
+ images = len(soup.find_all('img'))
45
+
46
+ return {
47
+ 'html_size_kb': round(html_size / 1024, 2),
48
+ 'script_count': scripts,
49
+ 'stylesheet_count': stylesheets,
50
+ 'image_count': images,
51
+ 'compression': response.headers.get('Content-Encoding', 'none')
52
+ }
53
+ except Exception as e:
54
+ return {'error': str(e)}
55
+
56
+ def test_all(self) -> str:
57
+ result = f"⚑ **PERFORMANCE TEST RESULTS**\n\n"
58
+ result += f"🌐 URL: {self.url}\n\n"
59
+
60
+ # TTFB
61
+ ttfb = self.measure_ttfb()
62
+ if isinstance(ttfb, float):
63
+ status = "βœ… Excellent" if ttfb < 200 else "⚠️ Needs Improvement" if ttfb < 500 else "❌ Poor"
64
+ result += f"⏱️ **Time To First Byte:** {ttfb} ms {status}\n"
65
+ else:
66
+ result += f"⏱️ **Time To First Byte:** {ttfb}\n"
67
+
68
+ # Payload
69
+ payload = self.get_payload_size()
70
+ if 'error' not in payload:
71
+ result += f"\nπŸ“¦ **Page Size:** {payload['html_size_kb']} KB\n"
72
+ result += f"πŸ“œ **Scripts:** {payload['script_count']}\n"
73
+ result += f"🎨 **Stylesheets:** {payload['stylesheet_count']}\n"
74
+ result += f"πŸ–ΌοΈ **Images:** {payload['image_count']}\n"
75
+ result += f"πŸ—œοΈ **Compression:** {payload['compression']}\n"
76
+
77
+ return result
78
+
79
+
80
+ # ============================================================================
81
+ # SEO AUDITOR
82
+ # ============================================================================
83
+
84
+ class SEOAuditor:
85
+ def __init__(self, url: str):
86
+ self.url = url
87
+ self.soup = None
88
+
89
+ def fetch_page(self):
90
+ try:
91
+ response = requests.get(self.url, timeout=10)
92
+ self.soup = BeautifulSoup(response.content, 'html.parser')
93
+ except Exception as e:
94
+ return False
95
+ return True
96
+
97
+ def check_meta_tags(self) -> Dict:
98
+ title = self.soup.find('title')
99
+ description = self.soup.find('meta', attrs={'name': 'description'})
100
+ og_title = self.soup.find('meta', property='og:title')
101
+ og_description = self.soup.find('meta', property='og:description')
102
+ og_image = self.soup.find('meta', property='og:image')
103
+ canonical = self.soup.find('link', rel='canonical')
104
+
105
+ return {
106
+ 'title': title.text if title else '❌ Missing',
107
+ 'title_length': len(title.text) if title else 0,
108
+ 'description': description.get('content')[:100] + '...' if description else '❌ Missing',
109
+ 'og_title': 'βœ… Present' if og_title else '❌ Missing',
110
+ 'og_description': 'βœ… Present' if og_description else '❌ Missing',
111
+ 'og_image': 'βœ… Present' if og_image else '❌ Missing',
112
+ 'canonical': 'βœ… Present' if canonical else '❌ Missing'
113
+ }
114
+
115
+ def check_headers(self) -> Dict:
116
+ h1 = self.soup.find_all('h1')
117
+ h2 = self.soup.find_all('h2')
118
+ h3 = self.soup.find_all('h3')
119
+
120
+ return {
121
+ 'h1_count': len(h1),
122
+ 'h1_text': [h.text.strip()[:50] for h in h1][:3],
123
+ 'h2_count': len(h2),
124
+ 'h3_count': len(h3),
125
+ 'valid': len(h1) == 1
126
+ }
127
+
128
+ def check_sitemap(self) -> Dict:
129
+ sitemap_url = urljoin(self.url, '/sitemap.xml')
130
+ try:
131
+ resp = requests.get(sitemap_url, timeout=5)
132
+ return {'exists': resp.status_code == 200}
133
+ except:
134
+ return {'exists': False}
135
+
136
+ def audit(self) -> str:
137
+ result = f"πŸ” **SEO AUDIT RESULTS**\n\n"
138
+ result += f"🌐 URL: {self.url}\n\n"
139
+
140
+ if not self.fetch_page():
141
+ return result + "❌ Failed to fetch page"
142
+
143
+ # Meta Tags
144
+ meta = self.check_meta_tags()
145
+ result += f"πŸ“ **Meta Tags**\n"
146
+ result += f"β€’ Title: {meta['title']}\n"
147
+ result += f"β€’ Title Length: {meta['title_length']} chars "
148
+ result += f"{'βœ…' if 50 <= meta['title_length'] <= 60 else '⚠️'}\n"
149
+ result += f"β€’ Description: {meta['description']}\n"
150
+ result += f"β€’ OG Title: {meta['og_title']}\n"
151
+ result += f"β€’ OG Description: {meta['og_description']}\n"
152
+ result += f"β€’ OG Image: {meta['og_image']}\n"
153
+ result += f"β€’ Canonical: {meta['canonical']}\n\n"
154
+
155
+ # Headers
156
+ headers = self.check_headers()
157
+ result += f"πŸ“‹ **Header Structure**\n"
158
+ result += f"β€’ H1 Count: {headers['h1_count']} {'βœ…' if headers['valid'] else '❌'}\n"
159
+ if headers['h1_text']:
160
+ result += f"β€’ H1 Text: {headers['h1_text'][0]}\n"
161
+ result += f"β€’ H2 Count: {headers['h2_count']}\n"
162
+ result += f"β€’ H3 Count: {headers['h3_count']}\n\n"
163
+
164
+ # Sitemap
165
+ sitemap = self.check_sitemap()
166
+ result += f"πŸ—ΊοΈ **Sitemap:** {'βœ… Found' if sitemap['exists'] else '❌ Not Found'}\n"
167
+
168
+ return result
169
+
170
+
171
+ # ============================================================================
172
+ # SECURITY TESTER
173
+ # ============================================================================
174
+
175
+ class SecurityTester:
176
+ def __init__(self, url: str):
177
+ self.url = url
178
+ self.domain = urlparse(url).netloc
179
+
180
+ def check_ssl(self) -> Dict:
181
+ try:
182
+ context = ssl.create_default_context()
183
+ with socket.create_connection((self.domain, 443), timeout=5) as sock:
184
+ with context.wrap_socket(sock, server_hostname=self.domain) as ssock:
185
+ cert = ssock.getpeercert()
186
+ return {
187
+ 'valid': True,
188
+ 'expires': cert['notAfter']
189
+ }
190
+ except Exception as e:
191
+ return {'valid': False, 'error': str(e)}
192
+
193
+ def check_security_headers(self) -> Dict:
194
+ try:
195
+ response = requests.get(self.url, timeout=10)
196
+ headers = response.headers
197
+
198
+ return {
199
+ 'hsts': headers.get('Strict-Transport-Security', '❌'),
200
+ 'x_content_type': headers.get('X-Content-Type-Options', '❌'),
201
+ 'x_frame': headers.get('X-Frame-Options', '❌'),
202
+ 'csp': headers.get('Content-Security-Policy', '❌'),
203
+ 'referrer': headers.get('Referrer-Policy', '❌')
204
+ }
205
+ except Exception as e:
206
+ return {'error': str(e)}
207
+
208
+ def check_mixed_content(self) -> List[str]:
209
+ if not self.url.startswith('https'):
210
+ return ['⚠️ Page is not HTTPS']
211
+
212
+ try:
213
+ response = requests.get(self.url, timeout=10)
214
+ soup = BeautifulSoup(response.content, 'html.parser')
215
+ mixed = []
216
+
217
+ for tag in soup.find_all(['img', 'script', 'link']):
218
+ src = tag.get('src') or tag.get('href', '')
219
+ if src.startswith('http://'):
220
+ mixed.append(src[:50])
221
+
222
+ return mixed[:5] if mixed else ['βœ… No mixed content']
223
+ except:
224
+ return ['Error checking']
225
+
226
+ def test_all(self) -> str:
227
+ result = f"πŸ”’ **SECURITY TEST RESULTS**\n\n"
228
+ result += f"🌐 URL: {self.url}\n\n"
229
+
230
+ # SSL
231
+ ssl_info = self.check_ssl()
232
+ if ssl_info['valid']:
233
+ result += f"πŸ” **SSL Certificate:** βœ… Valid\n"
234
+ result += f"β€’ Expires: {ssl_info['expires']}\n\n"
235
+ else:
236
+ result += f"πŸ” **SSL Certificate:** ❌ Invalid\n\n"
237
+
238
+ # Security Headers
239
+ headers = self.check_security_headers()
240
+ if 'error' not in headers:
241
+ result += f"πŸ›‘οΈ **Security Headers**\n"
242
+ result += f"β€’ HSTS: {'βœ…' if headers['hsts'] != '❌' else '❌'}\n"
243
+ result += f"β€’ X-Content-Type-Options: {'βœ…' if headers['x_content_type'] != '❌' else '❌'}\n"
244
+ result += f"β€’ X-Frame-Options: {'βœ…' if headers['x_frame'] != '❌' else '❌'}\n"
245
+ result += f"β€’ Content-Security-Policy: {'βœ…' if headers['csp'] != '❌' else '❌'}\n"
246
+ result += f"β€’ Referrer-Policy: {'βœ…' if headers['referrer'] != '❌' else '❌'}\n\n"
247
+
248
+ # Mixed Content
249
+ mixed = self.check_mixed_content()
250
+ result += f"πŸ“¦ **Mixed Content Check**\n"
251
+ for item in mixed:
252
+ result += f"β€’ {item}\n"
253
+
254
+ return result
255
+
256
+
257
+ # ============================================================================
258
+ # ACCESSIBILITY TESTER
259
+ # ============================================================================
260
+
261
+ class AccessibilityTester:
262
+ def __init__(self, url: str):
263
+ self.url = url
264
+ self.soup = None
265
+
266
+ def fetch_page(self):
267
+ try:
268
+ response = requests.get(self.url, timeout=10)
269
+ self.soup = BeautifulSoup(response.content, 'html.parser')
270
+ return True
271
+ except:
272
+ return False
273
+
274
+ def check_alt_attributes(self) -> Dict:
275
+ images = self.soup.find_all('img')
276
+ missing_alt = []
277
+
278
+ for img in images:
279
+ if not img.get('alt'):
280
+ src = img.get('src', 'No src')[:50]
281
+ missing_alt.append(src)
282
+
283
+ return {
284
+ 'total': len(images),
285
+ 'missing': len(missing_alt),
286
+ 'samples': missing_alt[:5]
287
+ }
288
+
289
+ def check_form_labels(self) -> Dict:
290
+ inputs = self.soup.find_all(['input', 'textarea', 'select'])
291
+ missing_labels = 0
292
+
293
+ for inp in inputs:
294
+ input_id = inp.get('id')
295
+ aria_label = inp.get('aria-label')
296
+
297
+ if input_id:
298
+ label = self.soup.find('label', attrs={'for': input_id})
299
+ if not label and not aria_label:
300
+ missing_labels += 1
301
+ elif not aria_label:
302
+ missing_labels += 1
303
+
304
+ return {
305
+ 'total': len(inputs),
306
+ 'missing': missing_labels
307
+ }
308
+
309
+ def test_all(self) -> str:
310
+ result = f"β™Ώ **ACCESSIBILITY TEST RESULTS**\n\n"
311
+ result += f"🌐 URL: {self.url}\n\n"
312
+
313
+ if not self.fetch_page():
314
+ return result + "❌ Failed to fetch page"
315
+
316
+ # Alt Attributes
317
+ alt_check = self.check_alt_attributes()
318
+ result += f"πŸ–ΌοΈ **Image Alt Attributes**\n"
319
+ result += f"β€’ Total Images: {alt_check['total']}\n"
320
+ result += f"β€’ Missing Alt: {alt_check['missing']} "
321
+ result += f"{'βœ…' if alt_check['missing'] == 0 else '❌'}\n"
322
+ if alt_check['samples']:
323
+ result += f"β€’ Samples: {', '.join(alt_check['samples'][:2])}\n"
324
+ result += f"\n"
325
+
326
+ # Form Labels
327
+ label_check = self.check_form_labels()
328
+ result += f"πŸ“ **Form Labels**\n"
329
+ result += f"β€’ Total Inputs: {label_check['total']}\n"
330
+ result += f"β€’ Missing Labels: {label_check['missing']} "
331
+ result += f"{'βœ…' if label_check['missing'] == 0 else '❌'}\n"
332
+
333
+ return result
334
+
335
+
336
+ # ============================================================================
337
+ # GRADIO INTERFACE
338
+ # ============================================================================
339
+
340
+ def run_performance_test(url):
341
+ if not url:
342
+ return "⚠️ Please enter a URL"
343
+ if not url.startswith(('http://', 'https://')):
344
+ url = 'https://' + url
345
+ try:
346
+ tester = PerformanceTester(url)
347
+ return tester.test_all()
348
+ except Exception as e:
349
+ return f"❌ Error: {str(e)}"
350
+
351
+ def run_seo_audit(url):
352
+ if not url:
353
+ return "⚠️ Please enter a URL"
354
+ if not url.startswith(('http://', 'https://')):
355
+ url = 'https://' + url
356
+ try:
357
+ auditor = SEOAuditor(url)
358
+ return auditor.audit()
359
+ except Exception as e:
360
+ return f"❌ Error: {str(e)}"
361
+
362
+ def run_security_test(url):
363
+ if not url:
364
+ return "⚠️ Please enter a URL"
365
+ if not url.startswith(('http://', 'https://')):
366
+ url = 'https://' + url
367
+ try:
368
+ tester = SecurityTester(url)
369
+ return tester.test_all()
370
+ except Exception as e:
371
+ return f"❌ Error: {str(e)}"
372
+
373
+ def run_accessibility_test(url):
374
+ if not url:
375
+ return "⚠️ Please enter a URL"
376
+ if not url.startswith(('http://', 'https://')):
377
+ url = 'https://' + url
378
+ try:
379
+ tester = AccessibilityTester(url)
380
+ return tester.test_all()
381
+ except Exception as e:
382
+ return f"❌ Error: {str(e)}"
383
+
384
+ def run_all_tests(url):
385
+ if not url:
386
+ return "⚠️ Please enter a URL", "", "", ""
387
+ if not url.startswith(('http://', 'https://')):
388
+ url = 'https://' + url
389
+
390
+ perf = run_performance_test(url)
391
+ seo = run_seo_audit(url)
392
+ sec = run_security_test(url)
393
+ a11y = run_accessibility_test(url)
394
+
395
+ return perf, seo, sec, a11y
396
+
397
+ # Create Gradio Interface
398
+ with gr.Blocks(title="Web Testing Suite", theme=gr.themes.Soft()) as demo:
399
+ gr.Markdown("""
400
+ # πŸš€ Comprehensive Web Testing Suite
401
+ Test any website for **Performance**, **SEO**, **Security**, and **Accessibility**
402
+ """)
403
+
404
+ with gr.Row():
405
+ url_input = gr.Textbox(
406
+ label="🌐 Website URL",
407
+ placeholder="https://example.com",
408
+ scale=3
409
+ )
410
+
411
+ with gr.Row():
412
+ test_all_btn = gr.Button("πŸ” Run All Tests", variant="primary", scale=1)
413
+
414
+ with gr.Row():
415
+ perf_btn = gr.Button("⚑ Performance", scale=1)
416
+ seo_btn = gr.Button("πŸ” SEO", scale=1)
417
+ sec_btn = gr.Button("πŸ”’ Security", scale=1)
418
+ a11y_btn = gr.Button("β™Ώ Accessibility", scale=1)
419
+
420
+ with gr.Tabs():
421
+ with gr.Tab("⚑ Performance"):
422
+ perf_output = gr.Textbox(label="Results", lines=15)
423
+
424
+ with gr.Tab("πŸ” SEO"):
425
+ seo_output = gr.Textbox(label="Results", lines=15)
426
+
427
+ with gr.Tab("πŸ”’ Security"):
428
+ sec_output = gr.Textbox(label="Results", lines=15)
429
+
430
+ with gr.Tab("β™Ώ Accessibility"):
431
+ a11y_output = gr.Textbox(label="Results", lines=15)
432
+
433
+ # Button actions
434
+ test_all_btn.click(
435
+ run_all_tests,
436
+ inputs=[url_input],
437
+ outputs=[perf_output, seo_output, sec_output, a11y_output]
438
+ )
439
+
440
+ perf_btn.click(run_performance_test, inputs=[url_input], outputs=[perf_output])
441
+ seo_btn.click(run_seo_audit, inputs=[url_input], outputs=[seo_output])
442
+ sec_btn.click(run_security_test, inputs=[url_input], outputs=[sec_output])
443
+ a11y_btn.click(run_accessibility_test, inputs=[url_input], outputs=[a11y_output])
444
+
445
+ gr.Markdown("""
446
+ ---
447
+ ### πŸ“‹ What Each Test Checks:
448
+ - **⚑ Performance**: TTFB, Page Size, Resource Count, Compression
449
+ - **πŸ” SEO**: Meta Tags, Headers, Sitemap, Title/Description Length
450
+ - **πŸ”’ Security**: SSL, Security Headers, Mixed Content
451
+ - **β™Ώ Accessibility**: Alt Text, Form Labels, ARIA Roles
452
+ """)
453
+
454
+ if __name__ == "__main__":
455
+ demo.launch()