Spaces:
Running
Running
Upload 2 files
Browse files- utils/backup.py +354 -0
- utils/error_checker.py +1106 -0
utils/backup.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import json
|
| 4 |
+
import zipfile
|
| 5 |
+
import requests
|
| 6 |
+
import hashlib
|
| 7 |
+
import time
|
| 8 |
+
from io import BytesIO
|
| 9 |
+
from urllib.parse import urljoin, urlparse, unquote
|
| 10 |
+
from bs4 import BeautifulSoup
|
| 11 |
+
from selenium.webdriver.common.by import By
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class SiteBackup:
|
| 15 |
+
"""Classe responsΓ‘vel por fazer backup completo de um site aberto no Selenium."""
|
| 16 |
+
|
| 17 |
+
def __init__(self, driver, url):
|
| 18 |
+
self.driver = driver
|
| 19 |
+
self.url = url
|
| 20 |
+
self.base_url = self._get_base_url(url)
|
| 21 |
+
self.domain = urlparse(url).netloc
|
| 22 |
+
self.downloaded_assets = {}
|
| 23 |
+
self.asset_counter = 0
|
| 24 |
+
self.errors = []
|
| 25 |
+
|
| 26 |
+
def _get_base_url(self, url):
|
| 27 |
+
parsed = urlparse(url)
|
| 28 |
+
return f"{parsed.scheme}://{parsed.netloc}"
|
| 29 |
+
|
| 30 |
+
def _safe_filename(self, url, extension=None):
|
| 31 |
+
"""Gera um nome de arquivo seguro a partir de uma URL."""
|
| 32 |
+
self.asset_counter += 1
|
| 33 |
+
parsed = urlparse(url)
|
| 34 |
+
path = unquote(parsed.path).strip("/")
|
| 35 |
+
|
| 36 |
+
if path:
|
| 37 |
+
name = path.replace("/", "_").replace("\\", "_")
|
| 38 |
+
name = re.sub(r'[<>:"|?*]', '_', name)
|
| 39 |
+
else:
|
| 40 |
+
name = f"asset_{self.asset_counter}"
|
| 41 |
+
|
| 42 |
+
if extension and not name.endswith(extension):
|
| 43 |
+
name = f"{name}{extension}"
|
| 44 |
+
|
| 45 |
+
if len(name) > 150:
|
| 46 |
+
hash_str = hashlib.md5(url.encode()).hexdigest()[:8]
|
| 47 |
+
ext = os.path.splitext(name)[1] or (extension or "")
|
| 48 |
+
name = f"{hash_str}{ext}"
|
| 49 |
+
|
| 50 |
+
return name
|
| 51 |
+
|
| 52 |
+
def _download_asset(self, url):
|
| 53 |
+
"""Baixa um asset e retorna seus bytes."""
|
| 54 |
+
if url in self.downloaded_assets:
|
| 55 |
+
return self.downloaded_assets[url]
|
| 56 |
+
|
| 57 |
+
try:
|
| 58 |
+
headers = {
|
| 59 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
| 60 |
+
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
| 61 |
+
"Chrome/120.0.0.0 Safari/537.36"
|
| 62 |
+
}
|
| 63 |
+
response = requests.get(url, headers=headers, timeout=15, verify=False)
|
| 64 |
+
if response.status_code == 200:
|
| 65 |
+
self.downloaded_assets[url] = response.content
|
| 66 |
+
return response.content
|
| 67 |
+
except Exception as e:
|
| 68 |
+
self.errors.append(f"Erro ao baixar {url}: {str(e)}")
|
| 69 |
+
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
def _classify_asset(self, url, content_type=None):
|
| 73 |
+
"""Classifica o asset em uma pasta baseado no tipo."""
|
| 74 |
+
url_lower = url.lower()
|
| 75 |
+
|
| 76 |
+
if any(ext in url_lower for ext in ['.css']):
|
| 77 |
+
return "css"
|
| 78 |
+
elif any(ext in url_lower for ext in ['.js', '.mjs']):
|
| 79 |
+
return "js"
|
| 80 |
+
elif any(ext in url_lower for ext in ['.png', '.jpg', '.jpeg', '.gif',
|
| 81 |
+
'.svg', '.webp', '.ico', '.bmp',
|
| 82 |
+
'.avif']):
|
| 83 |
+
return "images"
|
| 84 |
+
elif any(ext in url_lower for ext in ['.woff', '.woff2', '.ttf',
|
| 85 |
+
'.eot', '.otf']):
|
| 86 |
+
return "fonts"
|
| 87 |
+
elif any(ext in url_lower for ext in ['.mp4', '.webm', '.ogg',
|
| 88 |
+
'.mp3', '.wav']):
|
| 89 |
+
return "media"
|
| 90 |
+
elif any(ext in url_lower for ext in ['.json', '.xml', '.csv']):
|
| 91 |
+
return "data"
|
| 92 |
+
else:
|
| 93 |
+
return "assets"
|
| 94 |
+
|
| 95 |
+
def capture_full_html(self):
|
| 96 |
+
"""Captura o HTML completo renderizado pelo navegador."""
|
| 97 |
+
try:
|
| 98 |
+
html = self.driver.execute_script("return document.documentElement.outerHTML;")
|
| 99 |
+
return f"<!DOCTYPE html>\n{html}"
|
| 100 |
+
except Exception as e:
|
| 101 |
+
self.errors.append(f"Erro ao capturar HTML: {str(e)}")
|
| 102 |
+
return self.driver.page_source
|
| 103 |
+
|
| 104 |
+
def capture_all_stylesheets(self):
|
| 105 |
+
"""Captura todos os CSS externos e inline."""
|
| 106 |
+
stylesheets = {}
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
# CSS externos via <link>
|
| 110 |
+
css_links = self.driver.execute_script("""
|
| 111 |
+
var links = document.querySelectorAll('link[rel="stylesheet"]');
|
| 112 |
+
var urls = [];
|
| 113 |
+
links.forEach(function(link) {
|
| 114 |
+
if (link.href) urls.push(link.href);
|
| 115 |
+
});
|
| 116 |
+
return urls;
|
| 117 |
+
""")
|
| 118 |
+
|
| 119 |
+
for css_url in css_links:
|
| 120 |
+
content = self._download_asset(css_url)
|
| 121 |
+
if content:
|
| 122 |
+
filename = self._safe_filename(css_url, ".css")
|
| 123 |
+
stylesheets[f"css/{filename}"] = content
|
| 124 |
+
|
| 125 |
+
# CSS inline via <style>
|
| 126 |
+
inline_styles = self.driver.execute_script("""
|
| 127 |
+
var styles = document.querySelectorAll('style');
|
| 128 |
+
var contents = [];
|
| 129 |
+
styles.forEach(function(s, i) {
|
| 130 |
+
contents.push(s.textContent || s.innerText || '');
|
| 131 |
+
});
|
| 132 |
+
return contents;
|
| 133 |
+
""")
|
| 134 |
+
|
| 135 |
+
for i, style_content in enumerate(inline_styles):
|
| 136 |
+
if style_content.strip():
|
| 137 |
+
stylesheets[f"css/inline_style_{i+1}.css"] = style_content.encode('utf-8')
|
| 138 |
+
|
| 139 |
+
except Exception as e:
|
| 140 |
+
self.errors.append(f"Erro ao capturar CSS: {str(e)}")
|
| 141 |
+
|
| 142 |
+
return stylesheets
|
| 143 |
+
|
| 144 |
+
def capture_all_scripts(self):
|
| 145 |
+
"""Captura todos os JS externos e inline."""
|
| 146 |
+
scripts = {}
|
| 147 |
+
|
| 148 |
+
try:
|
| 149 |
+
# JS externos
|
| 150 |
+
js_urls = self.driver.execute_script("""
|
| 151 |
+
var scripts = document.querySelectorAll('script[src]');
|
| 152 |
+
var urls = [];
|
| 153 |
+
scripts.forEach(function(s) {
|
| 154 |
+
if (s.src) urls.push(s.src);
|
| 155 |
+
});
|
| 156 |
+
return urls;
|
| 157 |
+
""")
|
| 158 |
+
|
| 159 |
+
for js_url in js_urls:
|
| 160 |
+
content = self._download_asset(js_url)
|
| 161 |
+
if content:
|
| 162 |
+
filename = self._safe_filename(js_url, ".js")
|
| 163 |
+
scripts[f"js/{filename}"] = content
|
| 164 |
+
|
| 165 |
+
# JS inline
|
| 166 |
+
inline_scripts = self.driver.execute_script("""
|
| 167 |
+
var scripts = document.querySelectorAll('script:not([src])');
|
| 168 |
+
var contents = [];
|
| 169 |
+
scripts.forEach(function(s, i) {
|
| 170 |
+
var text = s.textContent || s.innerText || '';
|
| 171 |
+
if (text.trim().length > 0) contents.push(text);
|
| 172 |
+
});
|
| 173 |
+
return contents;
|
| 174 |
+
""")
|
| 175 |
+
|
| 176 |
+
for i, script_content in enumerate(inline_scripts):
|
| 177 |
+
if script_content.strip():
|
| 178 |
+
scripts[f"js/inline_script_{i+1}.js"] = script_content.encode('utf-8')
|
| 179 |
+
|
| 180 |
+
except Exception as e:
|
| 181 |
+
self.errors.append(f"Erro ao capturar JS: {str(e)}")
|
| 182 |
+
|
| 183 |
+
return scripts
|
| 184 |
+
|
| 185 |
+
def capture_all_images(self):
|
| 186 |
+
"""Captura todas as imagens do site."""
|
| 187 |
+
images = {}
|
| 188 |
+
|
| 189 |
+
try:
|
| 190 |
+
img_urls = self.driver.execute_script("""
|
| 191 |
+
var images = document.querySelectorAll('img');
|
| 192 |
+
var urls = [];
|
| 193 |
+
images.forEach(function(img) {
|
| 194 |
+
if (img.src && !img.src.startsWith('data:')) urls.push(img.src);
|
| 195 |
+
if (img.dataset && img.dataset.src) urls.push(img.dataset.src);
|
| 196 |
+
});
|
| 197 |
+
// Background images via CSS
|
| 198 |
+
var allElements = document.querySelectorAll('*');
|
| 199 |
+
allElements.forEach(function(el) {
|
| 200 |
+
var bg = window.getComputedStyle(el).backgroundImage;
|
| 201 |
+
if (bg && bg !== 'none') {
|
| 202 |
+
var match = bg.match(/url\\(['"]?(.+?)['"]?\\)/);
|
| 203 |
+
if (match && !match[1].startsWith('data:')) {
|
| 204 |
+
urls.push(match[1]);
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
});
|
| 208 |
+
return [...new Set(urls)];
|
| 209 |
+
""")
|
| 210 |
+
|
| 211 |
+
for img_url in img_urls:
|
| 212 |
+
absolute_url = urljoin(self.url, img_url)
|
| 213 |
+
content = self._download_asset(absolute_url)
|
| 214 |
+
if content:
|
| 215 |
+
filename = self._safe_filename(absolute_url)
|
| 216 |
+
images[f"images/{filename}"] = content
|
| 217 |
+
|
| 218 |
+
except Exception as e:
|
| 219 |
+
self.errors.append(f"Erro ao capturar imagens: {str(e)}")
|
| 220 |
+
|
| 221 |
+
return images
|
| 222 |
+
|
| 223 |
+
def capture_fonts(self):
|
| 224 |
+
"""Captura todas as fontes usadas no site."""
|
| 225 |
+
fonts = {}
|
| 226 |
+
|
| 227 |
+
try:
|
| 228 |
+
font_urls = self.driver.execute_script("""
|
| 229 |
+
var urls = [];
|
| 230 |
+
for (var i = 0; i < document.styleSheets.length; i++) {
|
| 231 |
+
try {
|
| 232 |
+
var rules = document.styleSheets[i].cssRules ||
|
| 233 |
+
document.styleSheets[i].rules;
|
| 234 |
+
if (!rules) continue;
|
| 235 |
+
for (var j = 0; j < rules.length; j++) {
|
| 236 |
+
if (rules[j].type === CSSRule.FONT_FACE_RULE) {
|
| 237 |
+
var src = rules[j].style.getPropertyValue('src');
|
| 238 |
+
var matches = src.match(/url\\(['"]?(.+?)['"]?\\)/g);
|
| 239 |
+
if (matches) {
|
| 240 |
+
matches.forEach(function(m) {
|
| 241 |
+
var url = m.replace(/url\\(['"]?/, '')
|
| 242 |
+
.replace(/['"]?\\)/, '');
|
| 243 |
+
if (!url.startsWith('data:')) urls.push(url);
|
| 244 |
+
});
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
} catch(e) {}
|
| 249 |
+
}
|
| 250 |
+
return [...new Set(urls)];
|
| 251 |
+
""")
|
| 252 |
+
|
| 253 |
+
for font_url in font_urls:
|
| 254 |
+
absolute_url = urljoin(self.url, font_url)
|
| 255 |
+
content = self._download_asset(absolute_url)
|
| 256 |
+
if content:
|
| 257 |
+
filename = self._safe_filename(absolute_url)
|
| 258 |
+
fonts[f"fonts/{filename}"] = content
|
| 259 |
+
|
| 260 |
+
except Exception as e:
|
| 261 |
+
self.errors.append(f"Erro ao capturar fontes: {str(e)}")
|
| 262 |
+
|
| 263 |
+
return fonts
|
| 264 |
+
|
| 265 |
+
def capture_screenshot(self):
|
| 266 |
+
"""Captura um screenshot da pΓ‘gina."""
|
| 267 |
+
try:
|
| 268 |
+
return self.driver.get_screenshot_as_png()
|
| 269 |
+
except Exception as e:
|
| 270 |
+
self.errors.append(f"Erro ao capturar screenshot: {str(e)}")
|
| 271 |
+
return None
|
| 272 |
+
|
| 273 |
+
def generate_backup_zip(self):
|
| 274 |
+
"""Gera o ZIP completo do backup."""
|
| 275 |
+
zip_buffer = BytesIO()
|
| 276 |
+
|
| 277 |
+
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
|
| 278 |
+
# 1. HTML principal
|
| 279 |
+
html = self.capture_full_html()
|
| 280 |
+
zf.writestr("index.html", html)
|
| 281 |
+
|
| 282 |
+
# 2. CSS
|
| 283 |
+
stylesheets = self.capture_all_stylesheets()
|
| 284 |
+
for path, content in stylesheets.items():
|
| 285 |
+
zf.writestr(path, content)
|
| 286 |
+
|
| 287 |
+
# 3. JavaScript
|
| 288 |
+
scripts = self.capture_all_scripts()
|
| 289 |
+
for path, content in scripts.items():
|
| 290 |
+
zf.writestr(path, content)
|
| 291 |
+
|
| 292 |
+
# 4. Imagens
|
| 293 |
+
images = self.capture_all_images()
|
| 294 |
+
for path, content in images.items():
|
| 295 |
+
zf.writestr(path, content)
|
| 296 |
+
|
| 297 |
+
# 5. Fontes
|
| 298 |
+
fonts = self.capture_fonts()
|
| 299 |
+
for path, content in fonts.items():
|
| 300 |
+
zf.writestr(path, content)
|
| 301 |
+
|
| 302 |
+
# 6. Screenshot
|
| 303 |
+
screenshot = self.capture_screenshot()
|
| 304 |
+
if screenshot:
|
| 305 |
+
zf.writestr("screenshot.png", screenshot)
|
| 306 |
+
|
| 307 |
+
# 7. RelatΓ³rio do backup
|
| 308 |
+
report = self._generate_report(
|
| 309 |
+
html, stylesheets, scripts, images, fonts
|
| 310 |
+
)
|
| 311 |
+
zf.writestr("backup_report.txt", report)
|
| 312 |
+
|
| 313 |
+
# 8. Erros durante o backup
|
| 314 |
+
if self.errors:
|
| 315 |
+
error_log = "ERROS DURANTE O BACKUP\n"
|
| 316 |
+
error_log += "=" * 50 + "\n\n"
|
| 317 |
+
for err in self.errors:
|
| 318 |
+
error_log += f"- {err}\n"
|
| 319 |
+
zf.writestr("backup_errors.txt", error_log)
|
| 320 |
+
|
| 321 |
+
zip_buffer.seek(0)
|
| 322 |
+
return zip_buffer
|
| 323 |
+
|
| 324 |
+
def _generate_report(self, html, stylesheets, scripts, images, fonts):
|
| 325 |
+
"""Gera relatΓ³rio textual do backup."""
|
| 326 |
+
report = []
|
| 327 |
+
report.append("=" * 60)
|
| 328 |
+
report.append(" RELATΓRIO DE BACKUP DO SITE")
|
| 329 |
+
report.append("=" * 60)
|
| 330 |
+
report.append(f"\nURL Original: {self.url}")
|
| 331 |
+
report.append(f"DomΓnio: {self.domain}")
|
| 332 |
+
report.append(f"Data do Backup: {time.strftime('%d/%m/%Y %H:%M:%S')}")
|
| 333 |
+
report.append(f"\n{'β' * 40}")
|
| 334 |
+
report.append("ARQUIVOS CAPTURADOS:")
|
| 335 |
+
report.append(f"{'β' * 40}")
|
| 336 |
+
report.append(f" HTML: 1 arquivo")
|
| 337 |
+
report.append(f" CSS: {len(stylesheets)} arquivo(s)")
|
| 338 |
+
report.append(f" JS: {len(scripts)} arquivo(s)")
|
| 339 |
+
report.append(f" Imagens: {len(images)} arquivo(s)")
|
| 340 |
+
report.append(f" Fontes: {len(fonts)} arquivo(s)")
|
| 341 |
+
report.append(f" Screenshot: 1 arquivo")
|
| 342 |
+
report.append(f"\n TOTAL: {1 + len(stylesheets) + len(scripts) + len(images) + len(fonts) + 1} arquivos")
|
| 343 |
+
report.append(f"\n{'β' * 40}")
|
| 344 |
+
|
| 345 |
+
if self.errors:
|
| 346 |
+
report.append(f"\nAVISOS ({len(self.errors)}):")
|
| 347 |
+
for err in self.errors:
|
| 348 |
+
report.append(f" β {err}")
|
| 349 |
+
|
| 350 |
+
report.append(f"\n{'=' * 60}")
|
| 351 |
+
report.append("Backup gerado pelo Site Backup System")
|
| 352 |
+
report.append("=" * 60)
|
| 353 |
+
|
| 354 |
+
return "\n".join(report)
|
utils/error_checker.py
ADDED
|
@@ -0,0 +1,1106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
import json
|
| 3 |
+
import re
|
| 4 |
+
import requests
|
| 5 |
+
from urllib.parse import urljoin, urlparse
|
| 6 |
+
from selenium.webdriver.common.by import By
|
| 7 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 8 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class SiteErrorChecker:
|
| 12 |
+
"""Classe responsΓ‘vel por encontrar todos os erros de um site."""
|
| 13 |
+
|
| 14 |
+
def __init__(self, driver, url):
|
| 15 |
+
self.driver = driver
|
| 16 |
+
self.url = url
|
| 17 |
+
self.domain = urlparse(url).netloc
|
| 18 |
+
self.errors = {
|
| 19 |
+
"console_errors": [],
|
| 20 |
+
"console_warnings": [],
|
| 21 |
+
"javascript_errors": [],
|
| 22 |
+
"network_errors": [],
|
| 23 |
+
"resource_errors": [],
|
| 24 |
+
"css_errors": [],
|
| 25 |
+
"html_errors": [],
|
| 26 |
+
"accessibility_errors": [],
|
| 27 |
+
"security_warnings": [],
|
| 28 |
+
"performance_warnings": [],
|
| 29 |
+
"broken_links": [],
|
| 30 |
+
"seo_warnings": [],
|
| 31 |
+
}
|
| 32 |
+
self.total_errors = 0
|
| 33 |
+
self.total_warnings = 0
|
| 34 |
+
|
| 35 |
+
def run_all_checks(self):
|
| 36 |
+
"""Executa TODAS as verificaΓ§Γ΅es de erro."""
|
| 37 |
+
self.check_console_logs()
|
| 38 |
+
self.check_javascript_errors()
|
| 39 |
+
self.check_network_errors()
|
| 40 |
+
self.check_resource_loading()
|
| 41 |
+
self.check_css_errors()
|
| 42 |
+
self.check_html_errors()
|
| 43 |
+
self.check_accessibility()
|
| 44 |
+
self.check_security()
|
| 45 |
+
self.check_performance()
|
| 46 |
+
self.check_broken_links()
|
| 47 |
+
self.check_seo()
|
| 48 |
+
|
| 49 |
+
self._count_totals()
|
| 50 |
+
return self.errors
|
| 51 |
+
|
| 52 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
+
# 1. ERROS DO CONSOLE DO NAVEGADOR
|
| 54 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 55 |
+
def check_console_logs(self):
|
| 56 |
+
"""Captura todos os logs do console do navegador."""
|
| 57 |
+
try:
|
| 58 |
+
logs = self.driver.get_log("browser")
|
| 59 |
+
for log_entry in logs:
|
| 60 |
+
level = log_entry.get("level", "").upper()
|
| 61 |
+
message = log_entry.get("message", "")
|
| 62 |
+
source = log_entry.get("source", "unknown")
|
| 63 |
+
timestamp = log_entry.get("timestamp", 0)
|
| 64 |
+
|
| 65 |
+
entry = {
|
| 66 |
+
"level": level,
|
| 67 |
+
"message": message,
|
| 68 |
+
"source": source,
|
| 69 |
+
"timestamp": time.strftime(
|
| 70 |
+
'%H:%M:%S',
|
| 71 |
+
time.localtime(timestamp / 1000)
|
| 72 |
+
) if timestamp else "N/A"
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
if level == "SEVERE":
|
| 76 |
+
self.errors["console_errors"].append(entry)
|
| 77 |
+
elif level == "WARNING":
|
| 78 |
+
self.errors["console_warnings"].append(entry)
|
| 79 |
+
|
| 80 |
+
except Exception as e:
|
| 81 |
+
self.errors["console_errors"].append({
|
| 82 |
+
"level": "INFO",
|
| 83 |
+
"message": f"NΓ£o foi possΓvel acessar logs do console: {str(e)}",
|
| 84 |
+
"source": "checker",
|
| 85 |
+
"timestamp": "N/A"
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 89 |
+
# 2. ERROS DE JAVASCRIPT
|
| 90 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 91 |
+
def check_javascript_errors(self):
|
| 92 |
+
"""Verifica erros de JavaScript na pΓ‘gina."""
|
| 93 |
+
try:
|
| 94 |
+
js_errors = self.driver.execute_script("""
|
| 95 |
+
var errors = [];
|
| 96 |
+
|
| 97 |
+
// Verificar se hΓ‘ variΓ‘veis nΓ£o definidas comuns
|
| 98 |
+
var commonChecks = [
|
| 99 |
+
'jQuery', '$', 'React', 'ReactDOM', 'Vue',
|
| 100 |
+
'angular', 'ng', 'bootstrap'
|
| 101 |
+
];
|
| 102 |
+
|
| 103 |
+
// Verificar scripts com erro de carregamento
|
| 104 |
+
var scripts = document.querySelectorAll('script[src]');
|
| 105 |
+
scripts.forEach(function(script) {
|
| 106 |
+
if (script.src) {
|
| 107 |
+
try {
|
| 108 |
+
var xhr = new XMLHttpRequest();
|
| 109 |
+
xhr.open('HEAD', script.src, false);
|
| 110 |
+
xhr.send();
|
| 111 |
+
if (xhr.status >= 400) {
|
| 112 |
+
errors.push({
|
| 113 |
+
type: 'SCRIPT_LOAD_ERROR',
|
| 114 |
+
message: 'Falha ao carregar script: ' + script.src,
|
| 115 |
+
status: xhr.status,
|
| 116 |
+
url: script.src
|
| 117 |
+
});
|
| 118 |
+
}
|
| 119 |
+
} catch(e) {
|
| 120 |
+
errors.push({
|
| 121 |
+
type: 'SCRIPT_BLOCKED',
|
| 122 |
+
message: 'Script bloqueado ou inacessΓvel: ' + script.src,
|
| 123 |
+
status: 0,
|
| 124 |
+
url: script.src
|
| 125 |
+
});
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
});
|
| 129 |
+
|
| 130 |
+
// Verificar erros de execuΓ§Γ£o inline
|
| 131 |
+
var inlineScripts = document.querySelectorAll('script:not([src])');
|
| 132 |
+
var inlineCount = 0;
|
| 133 |
+
inlineScripts.forEach(function(script) {
|
| 134 |
+
inlineCount++;
|
| 135 |
+
var content = script.textContent || '';
|
| 136 |
+
// Verificar padrΓ΅es suspeitos
|
| 137 |
+
if (content.includes('undefined') && content.includes('null')) {
|
| 138 |
+
errors.push({
|
| 139 |
+
type: 'POTENTIAL_ISSUE',
|
| 140 |
+
message: 'Script inline #' + inlineCount +
|
| 141 |
+
' pode ter referΓͺncias nulas',
|
| 142 |
+
status: 0,
|
| 143 |
+
url: 'inline'
|
| 144 |
+
});
|
| 145 |
+
}
|
| 146 |
+
});
|
| 147 |
+
|
| 148 |
+
// Verificar event listeners com erro
|
| 149 |
+
try {
|
| 150 |
+
var allElements = document.querySelectorAll('[onclick], [onerror], [onload]');
|
| 151 |
+
allElements.forEach(function(el) {
|
| 152 |
+
var onclick = el.getAttribute('onclick');
|
| 153 |
+
if (onclick) {
|
| 154 |
+
try {
|
| 155 |
+
new Function(onclick);
|
| 156 |
+
} catch(e) {
|
| 157 |
+
errors.push({
|
| 158 |
+
type: 'INLINE_HANDLER_ERROR',
|
| 159 |
+
message: 'Erro em handler inline: ' + e.message,
|
| 160 |
+
status: 0,
|
| 161 |
+
url: el.tagName + ' element'
|
| 162 |
+
});
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
});
|
| 166 |
+
} catch(e) {}
|
| 167 |
+
|
| 168 |
+
return errors;
|
| 169 |
+
""")
|
| 170 |
+
|
| 171 |
+
for err in js_errors:
|
| 172 |
+
self.errors["javascript_errors"].append(err)
|
| 173 |
+
|
| 174 |
+
except Exception as e:
|
| 175 |
+
self.errors["javascript_errors"].append({
|
| 176 |
+
"type": "CHECK_FAILED",
|
| 177 |
+
"message": f"Erro ao verificar JS: {str(e)}",
|
| 178 |
+
"status": 0,
|
| 179 |
+
"url": self.url
|
| 180 |
+
})
|
| 181 |
+
|
| 182 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 183 |
+
# 3. ERROS DE REDE
|
| 184 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 185 |
+
def check_network_errors(self):
|
| 186 |
+
"""Verifica erros de rede e recursos que falharam."""
|
| 187 |
+
try:
|
| 188 |
+
network_data = self.driver.execute_script("""
|
| 189 |
+
var entries = performance.getEntriesByType('resource');
|
| 190 |
+
var results = [];
|
| 191 |
+
entries.forEach(function(entry) {
|
| 192 |
+
var data = {
|
| 193 |
+
name: entry.name,
|
| 194 |
+
type: entry.initiatorType,
|
| 195 |
+
duration: Math.round(entry.duration),
|
| 196 |
+
size: entry.transferSize || 0,
|
| 197 |
+
status: 'ok'
|
| 198 |
+
};
|
| 199 |
+
// Se a duraΓ§Γ£o Γ© 0 e o tamanho Γ© 0, pode ter falhado
|
| 200 |
+
if (entry.transferSize === 0 && entry.duration === 0
|
| 201 |
+
&& entry.initiatorType !== 'beacon') {
|
| 202 |
+
data.status = 'possible_failure';
|
| 203 |
+
}
|
| 204 |
+
// Se demorou muito (> 5 segundos)
|
| 205 |
+
if (entry.duration > 5000) {
|
| 206 |
+
data.status = 'slow';
|
| 207 |
+
}
|
| 208 |
+
results.push(data);
|
| 209 |
+
});
|
| 210 |
+
return results;
|
| 211 |
+
""")
|
| 212 |
+
|
| 213 |
+
for entry in network_data:
|
| 214 |
+
if entry["status"] == "possible_failure":
|
| 215 |
+
self.errors["network_errors"].append({
|
| 216 |
+
"type": "RESOURCE_FAILURE",
|
| 217 |
+
"message": f"Recurso pode ter falhado ao carregar: {entry['name']}",
|
| 218 |
+
"resource_type": entry["type"],
|
| 219 |
+
"duration": entry["duration"]
|
| 220 |
+
})
|
| 221 |
+
elif entry["status"] == "slow":
|
| 222 |
+
self.errors["performance_warnings"].append({
|
| 223 |
+
"type": "SLOW_RESOURCE",
|
| 224 |
+
"message": f"Recurso lento ({entry['duration']}ms): {entry['name']}",
|
| 225 |
+
"resource_type": entry["type"],
|
| 226 |
+
"duration": entry["duration"]
|
| 227 |
+
})
|
| 228 |
+
|
| 229 |
+
except Exception as e:
|
| 230 |
+
self.errors["network_errors"].append({
|
| 231 |
+
"type": "CHECK_FAILED",
|
| 232 |
+
"message": f"Erro ao verificar rede: {str(e)}",
|
| 233 |
+
"resource_type": "unknown",
|
| 234 |
+
"duration": 0
|
| 235 |
+
})
|
| 236 |
+
|
| 237 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 238 |
+
# 4. ERROS DE RECURSOS (IMAGENS, SCRIPTS, ETC)
|
| 239 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 240 |
+
def check_resource_loading(self):
|
| 241 |
+
"""Verifica se todos os recursos carregaram corretamente."""
|
| 242 |
+
try:
|
| 243 |
+
resource_errors = self.driver.execute_script("""
|
| 244 |
+
var errors = [];
|
| 245 |
+
|
| 246 |
+
// Imagens quebradas
|
| 247 |
+
var images = document.querySelectorAll('img');
|
| 248 |
+
images.forEach(function(img) {
|
| 249 |
+
if (!img.complete || img.naturalWidth === 0) {
|
| 250 |
+
if (img.src && !img.src.startsWith('data:')) {
|
| 251 |
+
errors.push({
|
| 252 |
+
type: 'BROKEN_IMAGE',
|
| 253 |
+
tag: 'img',
|
| 254 |
+
url: img.src,
|
| 255 |
+
alt: img.alt || '(sem alt)'
|
| 256 |
+
});
|
| 257 |
+
}
|
| 258 |
+
}
|
| 259 |
+
});
|
| 260 |
+
|
| 261 |
+
// Iframes que falharam
|
| 262 |
+
var iframes = document.querySelectorAll('iframe');
|
| 263 |
+
iframes.forEach(function(iframe) {
|
| 264 |
+
try {
|
| 265 |
+
if (iframe.src && !iframe.contentDocument &&
|
| 266 |
+
!iframe.src.startsWith('about:')) {
|
| 267 |
+
errors.push({
|
| 268 |
+
type: 'IFRAME_ERROR',
|
| 269 |
+
tag: 'iframe',
|
| 270 |
+
url: iframe.src,
|
| 271 |
+
alt: ''
|
| 272 |
+
});
|
| 273 |
+
}
|
| 274 |
+
} catch(e) {
|
| 275 |
+
// Cross-origin, normal
|
| 276 |
+
}
|
| 277 |
+
});
|
| 278 |
+
|
| 279 |
+
// VΓdeos e Γ‘udios com erro
|
| 280 |
+
var media = document.querySelectorAll('video, audio');
|
| 281 |
+
media.forEach(function(m) {
|
| 282 |
+
if (m.error) {
|
| 283 |
+
errors.push({
|
| 284 |
+
type: 'MEDIA_ERROR',
|
| 285 |
+
tag: m.tagName.toLowerCase(),
|
| 286 |
+
url: m.src || m.querySelector('source')?.src || 'unknown',
|
| 287 |
+
alt: 'Error code: ' + m.error.code
|
| 288 |
+
});
|
| 289 |
+
}
|
| 290 |
+
});
|
| 291 |
+
|
| 292 |
+
// Fontes que falharam
|
| 293 |
+
if (document.fonts) {
|
| 294 |
+
document.fonts.forEach(function(font) {
|
| 295 |
+
if (font.status === 'error') {
|
| 296 |
+
errors.push({
|
| 297 |
+
type: 'FONT_ERROR',
|
| 298 |
+
tag: 'font',
|
| 299 |
+
url: font.family,
|
| 300 |
+
alt: font.style + ' ' + font.weight
|
| 301 |
+
});
|
| 302 |
+
}
|
| 303 |
+
});
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
return errors;
|
| 307 |
+
""")
|
| 308 |
+
|
| 309 |
+
for err in resource_errors:
|
| 310 |
+
self.errors["resource_errors"].append(err)
|
| 311 |
+
|
| 312 |
+
except Exception as e:
|
| 313 |
+
self.errors["resource_errors"].append({
|
| 314 |
+
"type": "CHECK_FAILED",
|
| 315 |
+
"tag": "unknown",
|
| 316 |
+
"url": str(e),
|
| 317 |
+
"alt": ""
|
| 318 |
+
})
|
| 319 |
+
|
| 320 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 321 |
+
# 5. ERROS DE CSS
|
| 322 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 323 |
+
def check_css_errors(self):
|
| 324 |
+
"""Verifica erros de CSS."""
|
| 325 |
+
try:
|
| 326 |
+
css_errors = self.driver.execute_script("""
|
| 327 |
+
var errors = [];
|
| 328 |
+
|
| 329 |
+
// Verificar stylesheets que falharam ao carregar
|
| 330 |
+
var links = document.querySelectorAll('link[rel="stylesheet"]');
|
| 331 |
+
links.forEach(function(link) {
|
| 332 |
+
try {
|
| 333 |
+
var sheet = link.sheet;
|
| 334 |
+
if (!sheet) {
|
| 335 |
+
errors.push({
|
| 336 |
+
type: 'CSS_LOAD_ERROR',
|
| 337 |
+
message: 'Stylesheet nΓ£o carregou: ' + link.href,
|
| 338 |
+
url: link.href
|
| 339 |
+
});
|
| 340 |
+
} else {
|
| 341 |
+
try {
|
| 342 |
+
var rules = sheet.cssRules;
|
| 343 |
+
} catch(e) {
|
| 344 |
+
if (e.name === 'SecurityError') {
|
| 345 |
+
// Cross-origin, nΓ£o Γ© erro real
|
| 346 |
+
} else {
|
| 347 |
+
errors.push({
|
| 348 |
+
type: 'CSS_ACCESS_ERROR',
|
| 349 |
+
message: 'Erro ao acessar regras CSS: ' +
|
| 350 |
+
e.message,
|
| 351 |
+
url: link.href
|
| 352 |
+
});
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
} catch(e) {
|
| 357 |
+
errors.push({
|
| 358 |
+
type: 'CSS_PARSE_ERROR',
|
| 359 |
+
message: 'Erro ao processar stylesheet: ' + e.message,
|
| 360 |
+
url: link.href || 'unknown'
|
| 361 |
+
});
|
| 362 |
+
}
|
| 363 |
+
});
|
| 364 |
+
|
| 365 |
+
// Verificar elementos com estilos invΓ‘lidos
|
| 366 |
+
var allElements = document.querySelectorAll('*');
|
| 367 |
+
var checkedProps = ['display', 'position', 'z-index', 'overflow'];
|
| 368 |
+
var elementCount = 0;
|
| 369 |
+
allElements.forEach(function(el) {
|
| 370 |
+
if (elementCount > 500) return; // Limitar para performance
|
| 371 |
+
elementCount++;
|
| 372 |
+
var style = window.getComputedStyle(el);
|
| 373 |
+
|
| 374 |
+
// Verificar z-index absurdamente alto
|
| 375 |
+
var zIndex = parseInt(style.zIndex);
|
| 376 |
+
if (zIndex > 999999) {
|
| 377 |
+
errors.push({
|
| 378 |
+
type: 'CSS_WARNING',
|
| 379 |
+
message: 'z-index muito alto (' + zIndex +
|
| 380 |
+
') em ' + el.tagName +
|
| 381 |
+
(el.id ? '#' + el.id : '') +
|
| 382 |
+
(el.className ? '.' +
|
| 383 |
+
String(el.className).split(' ')[0] : ''),
|
| 384 |
+
url: 'inline'
|
| 385 |
+
});
|
| 386 |
+
}
|
| 387 |
+
});
|
| 388 |
+
|
| 389 |
+
return errors;
|
| 390 |
+
""")
|
| 391 |
+
|
| 392 |
+
for err in css_errors:
|
| 393 |
+
self.errors["css_errors"].append(err)
|
| 394 |
+
|
| 395 |
+
except Exception as e:
|
| 396 |
+
self.errors["css_errors"].append({
|
| 397 |
+
"type": "CHECK_FAILED",
|
| 398 |
+
"message": f"Erro ao verificar CSS: {str(e)}",
|
| 399 |
+
"url": self.url
|
| 400 |
+
})
|
| 401 |
+
|
| 402 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 403 |
+
# 6. ERROS DE HTML
|
| 404 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 405 |
+
def check_html_errors(self):
|
| 406 |
+
"""Verifica erros e problemas na estrutura HTML."""
|
| 407 |
+
try:
|
| 408 |
+
html_issues = self.driver.execute_script("""
|
| 409 |
+
var issues = [];
|
| 410 |
+
|
| 411 |
+
// Verificar DOCTYPE
|
| 412 |
+
if (!document.doctype) {
|
| 413 |
+
issues.push({
|
| 414 |
+
type: 'MISSING_DOCTYPE',
|
| 415 |
+
message: 'PΓ‘gina sem DOCTYPE declarado',
|
| 416 |
+
element: 'document'
|
| 417 |
+
});
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
// Verificar <html lang="">
|
| 421 |
+
var htmlEl = document.documentElement;
|
| 422 |
+
if (!htmlEl.lang) {
|
| 423 |
+
issues.push({
|
| 424 |
+
type: 'MISSING_LANG',
|
| 425 |
+
message: 'Atributo lang nΓ£o definido no <html>',
|
| 426 |
+
element: 'html'
|
| 427 |
+
});
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
// Verificar <title>
|
| 431 |
+
if (!document.title || document.title.trim() === '') {
|
| 432 |
+
issues.push({
|
| 433 |
+
type: 'MISSING_TITLE',
|
| 434 |
+
message: 'PΓ‘gina sem tΓtulo (<title> vazio ou ausente)',
|
| 435 |
+
element: 'head'
|
| 436 |
+
});
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
// Verificar meta charset
|
| 440 |
+
var charset = document.querySelector('meta[charset]');
|
| 441 |
+
if (!charset) {
|
| 442 |
+
issues.push({
|
| 443 |
+
type: 'MISSING_CHARSET',
|
| 444 |
+
message: 'Meta charset nΓ£o declarado',
|
| 445 |
+
element: 'head'
|
| 446 |
+
});
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
// Verificar meta viewport
|
| 450 |
+
var viewport = document.querySelector('meta[name="viewport"]');
|
| 451 |
+
if (!viewport) {
|
| 452 |
+
issues.push({
|
| 453 |
+
type: 'MISSING_VIEWPORT',
|
| 454 |
+
message: 'Meta viewport nΓ£o declarado (problemas em mobile)',
|
| 455 |
+
element: 'head'
|
| 456 |
+
});
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
// Imagens sem alt
|
| 460 |
+
var imgs = document.querySelectorAll('img');
|
| 461 |
+
var noAltCount = 0;
|
| 462 |
+
imgs.forEach(function(img) {
|
| 463 |
+
if (!img.hasAttribute('alt')) noAltCount++;
|
| 464 |
+
});
|
| 465 |
+
if (noAltCount > 0) {
|
| 466 |
+
issues.push({
|
| 467 |
+
type: 'MISSING_ALT',
|
| 468 |
+
message: noAltCount + ' imagem(ns) sem atributo alt',
|
| 469 |
+
element: 'img'
|
| 470 |
+
});
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
// Verificar IDs duplicados
|
| 474 |
+
var allIds = document.querySelectorAll('[id]');
|
| 475 |
+
var idMap = {};
|
| 476 |
+
allIds.forEach(function(el) {
|
| 477 |
+
var id = el.id;
|
| 478 |
+
if (idMap[id]) {
|
| 479 |
+
issues.push({
|
| 480 |
+
type: 'DUPLICATE_ID',
|
| 481 |
+
message: 'ID duplicado encontrado: #' + id,
|
| 482 |
+
element: el.tagName
|
| 483 |
+
});
|
| 484 |
+
}
|
| 485 |
+
idMap[id] = true;
|
| 486 |
+
});
|
| 487 |
+
|
| 488 |
+
// Verificar links vazios
|
| 489 |
+
var links = document.querySelectorAll('a');
|
| 490 |
+
links.forEach(function(a) {
|
| 491 |
+
var href = a.getAttribute('href');
|
| 492 |
+
if (!href || href === '#' || href === 'javascript:void(0)') {
|
| 493 |
+
var text = (a.textContent || '').trim().substring(0, 50);
|
| 494 |
+
if (text) {
|
| 495 |
+
issues.push({
|
| 496 |
+
type: 'EMPTY_LINK',
|
| 497 |
+
message: 'Link sem destino vΓ‘lido: "' + text + '"',
|
| 498 |
+
element: 'a'
|
| 499 |
+
});
|
| 500 |
+
}
|
| 501 |
+
}
|
| 502 |
+
});
|
| 503 |
+
|
| 504 |
+
// Verificar formulΓ‘rios sem action
|
| 505 |
+
var forms = document.querySelectorAll('form');
|
| 506 |
+
forms.forEach(function(form, i) {
|
| 507 |
+
if (!form.action || form.action === window.location.href) {
|
| 508 |
+
issues.push({
|
| 509 |
+
type: 'FORM_NO_ACTION',
|
| 510 |
+
message: 'FormulΓ‘rio #' + (i+1) +
|
| 511 |
+
' sem action definido',
|
| 512 |
+
element: 'form'
|
| 513 |
+
});
|
| 514 |
+
}
|
| 515 |
+
});
|
| 516 |
+
|
| 517 |
+
// Verificar hierarquia de headings
|
| 518 |
+
var headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
| 519 |
+
var h1Count = document.querySelectorAll('h1').length;
|
| 520 |
+
if (h1Count === 0) {
|
| 521 |
+
issues.push({
|
| 522 |
+
type: 'NO_H1',
|
| 523 |
+
message: 'PΓ‘gina nΓ£o tem nenhum <h1>',
|
| 524 |
+
element: 'headings'
|
| 525 |
+
});
|
| 526 |
+
} else if (h1Count > 1) {
|
| 527 |
+
issues.push({
|
| 528 |
+
type: 'MULTIPLE_H1',
|
| 529 |
+
message: 'PΓ‘gina tem ' + h1Count + ' tags <h1> (recomendado: 1)',
|
| 530 |
+
element: 'headings'
|
| 531 |
+
});
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
return issues;
|
| 535 |
+
""")
|
| 536 |
+
|
| 537 |
+
for issue in html_issues:
|
| 538 |
+
self.errors["html_errors"].append(issue)
|
| 539 |
+
|
| 540 |
+
except Exception as e:
|
| 541 |
+
self.errors["html_errors"].append({
|
| 542 |
+
"type": "CHECK_FAILED",
|
| 543 |
+
"message": f"Erro ao verificar HTML: {str(e)}",
|
| 544 |
+
"element": "unknown"
|
| 545 |
+
})
|
| 546 |
+
|
| 547 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 548 |
+
# 7. ACESSIBILIDADE
|
| 549 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 550 |
+
def check_accessibility(self):
|
| 551 |
+
"""Verifica problemas de acessibilidade."""
|
| 552 |
+
try:
|
| 553 |
+
a11y_issues = self.driver.execute_script("""
|
| 554 |
+
var issues = [];
|
| 555 |
+
|
| 556 |
+
// Inputs sem label
|
| 557 |
+
var inputs = document.querySelectorAll(
|
| 558 |
+
'input:not([type="hidden"]):not([type="submit"])' +
|
| 559 |
+
':not([type="button"]), select, textarea'
|
| 560 |
+
);
|
| 561 |
+
inputs.forEach(function(input) {
|
| 562 |
+
var id = input.id;
|
| 563 |
+
var ariaLabel = input.getAttribute('aria-label');
|
| 564 |
+
var ariaLabelledBy = input.getAttribute('aria-labelledby');
|
| 565 |
+
var label = id ?
|
| 566 |
+
document.querySelector('label[for="' + id + '"]') :
|
| 567 |
+
null;
|
| 568 |
+
var parentLabel = input.closest('label');
|
| 569 |
+
|
| 570 |
+
if (!label && !parentLabel && !ariaLabel && !ariaLabelledBy) {
|
| 571 |
+
issues.push({
|
| 572 |
+
type: 'INPUT_NO_LABEL',
|
| 573 |
+
message: 'Input sem label acessΓvel: ' +
|
| 574 |
+
(input.type || input.tagName) +
|
| 575 |
+
(input.name ? ' [name=' + input.name + ']' : ''),
|
| 576 |
+
severity: 'error'
|
| 577 |
+
});
|
| 578 |
+
}
|
| 579 |
+
});
|
| 580 |
+
|
| 581 |
+
// BotΓ΅es sem texto acessΓvel
|
| 582 |
+
var buttons = document.querySelectorAll('button, [role="button"]');
|
| 583 |
+
buttons.forEach(function(btn) {
|
| 584 |
+
var text = (btn.textContent || '').trim();
|
| 585 |
+
var ariaLabel = btn.getAttribute('aria-label');
|
| 586 |
+
var title = btn.getAttribute('title');
|
| 587 |
+
if (!text && !ariaLabel && !title) {
|
| 588 |
+
issues.push({
|
| 589 |
+
type: 'BUTTON_NO_TEXT',
|
| 590 |
+
message: 'BotΓ£o sem texto acessΓvel',
|
| 591 |
+
severity: 'error'
|
| 592 |
+
});
|
| 593 |
+
}
|
| 594 |
+
});
|
| 595 |
+
|
| 596 |
+
// Contraste (verificaΓ§Γ£o bΓ‘sica de texto branco em fundo claro)
|
| 597 |
+
var textElements = document.querySelectorAll('p, span, a, li, h1, h2, h3, h4, h5, h6');
|
| 598 |
+
var contrastCount = 0;
|
| 599 |
+
textElements.forEach(function(el) {
|
| 600 |
+
if (contrastCount > 100) return;
|
| 601 |
+
contrastCount++;
|
| 602 |
+
var style = window.getComputedStyle(el);
|
| 603 |
+
var color = style.color;
|
| 604 |
+
var fontSize = parseFloat(style.fontSize);
|
| 605 |
+
if (fontSize < 12) {
|
| 606 |
+
issues.push({
|
| 607 |
+
type: 'SMALL_TEXT',
|
| 608 |
+
message: 'Texto muito pequeno (' + fontSize +
|
| 609 |
+
'px): "' +
|
| 610 |
+
(el.textContent || '').trim().substring(0, 30) +
|
| 611 |
+
'"',
|
| 612 |
+
severity: 'warning'
|
| 613 |
+
});
|
| 614 |
+
}
|
| 615 |
+
});
|
| 616 |
+
|
| 617 |
+
// Verificar tabindex negativo
|
| 618 |
+
var negTabindex = document.querySelectorAll('[tabindex]');
|
| 619 |
+
negTabindex.forEach(function(el) {
|
| 620 |
+
var val = parseInt(el.getAttribute('tabindex'));
|
| 621 |
+
if (val > 0) {
|
| 622 |
+
issues.push({
|
| 623 |
+
type: 'POSITIVE_TABINDEX',
|
| 624 |
+
message: 'tabindex positivo (' + val +
|
| 625 |
+
') pode causar navegaΓ§Γ£o confusa em: ' +
|
| 626 |
+
el.tagName,
|
| 627 |
+
severity: 'warning'
|
| 628 |
+
});
|
| 629 |
+
}
|
| 630 |
+
});
|
| 631 |
+
|
| 632 |
+
// Verificar ARIA roles invΓ‘lidos
|
| 633 |
+
var ariaElements = document.querySelectorAll('[role]');
|
| 634 |
+
var validRoles = [
|
| 635 |
+
'alert','alertdialog','application','article','banner',
|
| 636 |
+
'button','cell','checkbox','columnheader','combobox',
|
| 637 |
+
'complementary','contentinfo','definition','dialog',
|
| 638 |
+
'directory','document','feed','figure','form','grid',
|
| 639 |
+
'gridcell','group','heading','img','link','list',
|
| 640 |
+
'listbox','listitem','log','main','marquee','math',
|
| 641 |
+
'menu','menubar','menuitem','menuitemcheckbox',
|
| 642 |
+
'menuitemradio','navigation','none','note','option',
|
| 643 |
+
'presentation','progressbar','radio','radiogroup',
|
| 644 |
+
'region','row','rowgroup','rowheader','scrollbar',
|
| 645 |
+
'search','searchbox','separator','slider','spinbutton',
|
| 646 |
+
'status','switch','tab','table','tablist','tabpanel',
|
| 647 |
+
'term','textbox','timer','toolbar','tooltip','tree',
|
| 648 |
+
'treegrid','treeitem'
|
| 649 |
+
];
|
| 650 |
+
ariaElements.forEach(function(el) {
|
| 651 |
+
var role = el.getAttribute('role');
|
| 652 |
+
if (validRoles.indexOf(role) === -1) {
|
| 653 |
+
issues.push({
|
| 654 |
+
type: 'INVALID_ROLE',
|
| 655 |
+
message: 'Role ARIA invΓ‘lido: "' + role + '"',
|
| 656 |
+
severity: 'error'
|
| 657 |
+
});
|
| 658 |
+
}
|
| 659 |
+
});
|
| 660 |
+
|
| 661 |
+
return issues;
|
| 662 |
+
""")
|
| 663 |
+
|
| 664 |
+
for issue in a11y_issues:
|
| 665 |
+
self.errors["accessibility_errors"].append(issue)
|
| 666 |
+
|
| 667 |
+
except Exception as e:
|
| 668 |
+
self.errors["accessibility_errors"].append({
|
| 669 |
+
"type": "CHECK_FAILED",
|
| 670 |
+
"message": f"Erro ao verificar acessibilidade: {str(e)}",
|
| 671 |
+
"severity": "info"
|
| 672 |
+
})
|
| 673 |
+
|
| 674 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 675 |
+
# 8. SEGURANΓA
|
| 676 |
+
# βββββββββββββββββββββββββββββββββββββοΏ½οΏ½βββββββ
|
| 677 |
+
def check_security(self):
|
| 678 |
+
"""Verifica problemas de seguranΓ§a bΓ‘sicos."""
|
| 679 |
+
try:
|
| 680 |
+
security_issues = self.driver.execute_script("""
|
| 681 |
+
var issues = [];
|
| 682 |
+
|
| 683 |
+
// Verificar HTTPS
|
| 684 |
+
if (window.location.protocol !== 'https:') {
|
| 685 |
+
issues.push({
|
| 686 |
+
type: 'NO_HTTPS',
|
| 687 |
+
message: 'Site nΓ£o usa HTTPS'
|
| 688 |
+
});
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
// Verificar mixed content (recursos HTTP em pΓ‘gina HTTPS)
|
| 692 |
+
if (window.location.protocol === 'https:') {
|
| 693 |
+
var allResources = document.querySelectorAll(
|
| 694 |
+
'img[src^="http:"], script[src^="http:"], ' +
|
| 695 |
+
'link[href^="http:"], iframe[src^="http:"]'
|
| 696 |
+
);
|
| 697 |
+
allResources.forEach(function(el) {
|
| 698 |
+
var url = el.src || el.href;
|
| 699 |
+
issues.push({
|
| 700 |
+
type: 'MIXED_CONTENT',
|
| 701 |
+
message: 'ConteΓΊdo misto (HTTP em HTTPS): ' + url
|
| 702 |
+
});
|
| 703 |
+
});
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
// Verificar links target="_blank" sem rel="noopener"
|
| 707 |
+
var blankLinks = document.querySelectorAll('a[target="_blank"]');
|
| 708 |
+
blankLinks.forEach(function(a) {
|
| 709 |
+
var rel = (a.getAttribute('rel') || '').toLowerCase();
|
| 710 |
+
if (!rel.includes('noopener') && !rel.includes('noreferrer')) {
|
| 711 |
+
issues.push({
|
| 712 |
+
type: 'UNSAFE_BLANK_LINK',
|
| 713 |
+
message: 'Link com target="_blank" sem rel="noopener": ' +
|
| 714 |
+
(a.href || '').substring(0, 80)
|
| 715 |
+
});
|
| 716 |
+
}
|
| 717 |
+
});
|
| 718 |
+
|
| 719 |
+
// Verificar forms inseguros
|
| 720 |
+
var forms = document.querySelectorAll('form');
|
| 721 |
+
forms.forEach(function(form) {
|
| 722 |
+
var action = form.action || '';
|
| 723 |
+
if (action.startsWith('http:') &&
|
| 724 |
+
window.location.protocol === 'https:') {
|
| 725 |
+
issues.push({
|
| 726 |
+
type: 'INSECURE_FORM',
|
| 727 |
+
message: 'FormulΓ‘rio envia dados via HTTP inseguro'
|
| 728 |
+
});
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
// Verificar autocomplete em campos sensΓveis
|
| 732 |
+
var passwordFields = form.querySelectorAll(
|
| 733 |
+
'input[type="password"]'
|
| 734 |
+
);
|
| 735 |
+
passwordFields.forEach(function(pw) {
|
| 736 |
+
if (pw.getAttribute('autocomplete') === 'on') {
|
| 737 |
+
issues.push({
|
| 738 |
+
type: 'PASSWORD_AUTOCOMPLETE',
|
| 739 |
+
message: 'Campo de senha com autocomplete habilitado'
|
| 740 |
+
});
|
| 741 |
+
}
|
| 742 |
+
});
|
| 743 |
+
});
|
| 744 |
+
|
| 745 |
+
// Verificar inline event handlers (potencial XSS)
|
| 746 |
+
var inlineHandlers = document.querySelectorAll(
|
| 747 |
+
'[onload], [onerror], [onclick], [onmouseover]'
|
| 748 |
+
);
|
| 749 |
+
if (inlineHandlers.length > 10) {
|
| 750 |
+
issues.push({
|
| 751 |
+
type: 'EXCESSIVE_INLINE_HANDLERS',
|
| 752 |
+
message: inlineHandlers.length +
|
| 753 |
+
' elementos com event handlers inline ' +
|
| 754 |
+
'(considere usar addEventListener)'
|
| 755 |
+
});
|
| 756 |
+
}
|
| 757 |
+
|
| 758 |
+
return issues;
|
| 759 |
+
""")
|
| 760 |
+
|
| 761 |
+
for issue in security_issues:
|
| 762 |
+
self.errors["security_warnings"].append(issue)
|
| 763 |
+
|
| 764 |
+
except Exception as e:
|
| 765 |
+
self.errors["security_warnings"].append({
|
| 766 |
+
"type": "CHECK_FAILED",
|
| 767 |
+
"message": f"Erro ao verificar seguranΓ§a: {str(e)}"
|
| 768 |
+
})
|
| 769 |
+
|
| 770 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 771 |
+
# 9. PERFORMANCE
|
| 772 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 773 |
+
def check_performance(self):
|
| 774 |
+
"""Verifica problemas de performance."""
|
| 775 |
+
try:
|
| 776 |
+
perf_data = self.driver.execute_script("""
|
| 777 |
+
var issues = [];
|
| 778 |
+
var timing = performance.timing;
|
| 779 |
+
|
| 780 |
+
// Tempo de carregamento total
|
| 781 |
+
var loadTime = timing.loadEventEnd - timing.navigationStart;
|
| 782 |
+
if (loadTime > 5000) {
|
| 783 |
+
issues.push({
|
| 784 |
+
type: 'SLOW_LOAD',
|
| 785 |
+
message: 'PΓ‘gina demorou ' +
|
| 786 |
+
(loadTime / 1000).toFixed(2) +
|
| 787 |
+
's para carregar (recomendado: < 3s)',
|
| 788 |
+
value: loadTime
|
| 789 |
+
});
|
| 790 |
+
}
|
| 791 |
+
|
| 792 |
+
// DOM muito grande
|
| 793 |
+
var domSize = document.querySelectorAll('*').length;
|
| 794 |
+
if (domSize > 1500) {
|
| 795 |
+
issues.push({
|
| 796 |
+
type: 'LARGE_DOM',
|
| 797 |
+
message: 'DOM muito grande: ' + domSize +
|
| 798 |
+
' elementos (recomendado: < 1500)',
|
| 799 |
+
value: domSize
|
| 800 |
+
});
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
+
// Muitas requisiΓ§Γ΅es
|
| 804 |
+
var resources = performance.getEntriesByType('resource');
|
| 805 |
+
if (resources.length > 80) {
|
| 806 |
+
issues.push({
|
| 807 |
+
type: 'TOO_MANY_REQUESTS',
|
| 808 |
+
message: resources.length +
|
| 809 |
+
' requisiΓ§Γ΅es de recursos (recomendado: < 80)',
|
| 810 |
+
value: resources.length
|
| 811 |
+
});
|
| 812 |
+
}
|
| 813 |
+
|
| 814 |
+
// Imagens sem dimensΓ΅es (causa layout shift)
|
| 815 |
+
var imgs = document.querySelectorAll('img');
|
| 816 |
+
var noDimCount = 0;
|
| 817 |
+
imgs.forEach(function(img) {
|
| 818 |
+
if (!img.getAttribute('width') && !img.getAttribute('height') &&
|
| 819 |
+
!img.style.width && !img.style.height) {
|
| 820 |
+
noDimCount++;
|
| 821 |
+
}
|
| 822 |
+
});
|
| 823 |
+
if (noDimCount > 0) {
|
| 824 |
+
issues.push({
|
| 825 |
+
type: 'IMG_NO_DIMENSIONS',
|
| 826 |
+
message: noDimCount +
|
| 827 |
+
' imagem(ns) sem dimensΓ΅es definidas ' +
|
| 828 |
+
'(causa Layout Shift)',
|
| 829 |
+
value: noDimCount
|
| 830 |
+
});
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
// CSS render-blocking
|
| 834 |
+
var cssLinks = document.querySelectorAll(
|
| 835 |
+
'link[rel="stylesheet"]:not([media="print"])' +
|
| 836 |
+
':not([media="none"])'
|
| 837 |
+
);
|
| 838 |
+
if (cssLinks.length > 5) {
|
| 839 |
+
issues.push({
|
| 840 |
+
type: 'MANY_CSS_FILES',
|
| 841 |
+
message: cssLinks.length +
|
| 842 |
+
' arquivos CSS render-blocking ' +
|
| 843 |
+
'(considere concatenar)',
|
| 844 |
+
value: cssLinks.length
|
| 845 |
+
});
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
// JS no <head> sem defer/async
|
| 849 |
+
var blockingJS = document.querySelectorAll(
|
| 850 |
+
'head script[src]:not([defer]):not([async])'
|
| 851 |
+
);
|
| 852 |
+
if (blockingJS.length > 0) {
|
| 853 |
+
issues.push({
|
| 854 |
+
type: 'BLOCKING_JS',
|
| 855 |
+
message: blockingJS.length +
|
| 856 |
+
' script(s) bloqueantes no <head> ' +
|
| 857 |
+
'(adicione defer ou async)',
|
| 858 |
+
value: blockingJS.length
|
| 859 |
+
});
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
return issues;
|
| 863 |
+
""")
|
| 864 |
+
|
| 865 |
+
for issue in perf_data:
|
| 866 |
+
self.errors["performance_warnings"].append(issue)
|
| 867 |
+
|
| 868 |
+
except Exception as e:
|
| 869 |
+
self.errors["performance_warnings"].append({
|
| 870 |
+
"type": "CHECK_FAILED",
|
| 871 |
+
"message": f"Erro ao verificar performance: {str(e)}",
|
| 872 |
+
"value": 0
|
| 873 |
+
})
|
| 874 |
+
|
| 875 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 876 |
+
# 10. LINKS QUEBRADOS
|
| 877 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 878 |
+
def check_broken_links(self):
|
| 879 |
+
"""Verifica links quebrados na pΓ‘gina."""
|
| 880 |
+
try:
|
| 881 |
+
links = self.driver.execute_script("""
|
| 882 |
+
var anchors = document.querySelectorAll('a[href]');
|
| 883 |
+
var urls = [];
|
| 884 |
+
anchors.forEach(function(a) {
|
| 885 |
+
var href = a.href;
|
| 886 |
+
if (href && !href.startsWith('javascript:') &&
|
| 887 |
+
!href.startsWith('mailto:') &&
|
| 888 |
+
!href.startsWith('tel:') &&
|
| 889 |
+
!href.startsWith('#') &&
|
| 890 |
+
!href.startsWith('data:')) {
|
| 891 |
+
urls.push({
|
| 892 |
+
url: href,
|
| 893 |
+
text: (a.textContent || '').trim().substring(0, 50)
|
| 894 |
+
});
|
| 895 |
+
}
|
| 896 |
+
});
|
| 897 |
+
// Limitar a 50 links para nΓ£o demorar
|
| 898 |
+
return urls.slice(0, 50);
|
| 899 |
+
""")
|
| 900 |
+
|
| 901 |
+
headers = {
|
| 902 |
+
"User-Agent": "Mozilla/5.0 (compatible; SiteChecker/1.0)"
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
for link_data in links:
|
| 906 |
+
try:
|
| 907 |
+
resp = requests.head(
|
| 908 |
+
link_data["url"],
|
| 909 |
+
headers=headers,
|
| 910 |
+
timeout=8,
|
| 911 |
+
allow_redirects=True,
|
| 912 |
+
verify=False
|
| 913 |
+
)
|
| 914 |
+
if resp.status_code >= 400:
|
| 915 |
+
self.errors["broken_links"].append({
|
| 916 |
+
"url": link_data["url"],
|
| 917 |
+
"text": link_data["text"],
|
| 918 |
+
"status_code": resp.status_code
|
| 919 |
+
})
|
| 920 |
+
except requests.exceptions.Timeout:
|
| 921 |
+
self.errors["broken_links"].append({
|
| 922 |
+
"url": link_data["url"],
|
| 923 |
+
"text": link_data["text"],
|
| 924 |
+
"status_code": "TIMEOUT"
|
| 925 |
+
})
|
| 926 |
+
except requests.exceptions.ConnectionError:
|
| 927 |
+
self.errors["broken_links"].append({
|
| 928 |
+
"url": link_data["url"],
|
| 929 |
+
"text": link_data["text"],
|
| 930 |
+
"status_code": "CONNECTION_ERROR"
|
| 931 |
+
})
|
| 932 |
+
except Exception:
|
| 933 |
+
pass
|
| 934 |
+
|
| 935 |
+
except Exception as e:
|
| 936 |
+
self.errors["broken_links"].append({
|
| 937 |
+
"url": self.url,
|
| 938 |
+
"text": f"Erro ao verificar links: {str(e)}",
|
| 939 |
+
"status_code": "CHECK_FAILED"
|
| 940 |
+
})
|
| 941 |
+
|
| 942 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 943 |
+
# 11. SEO BΓSICO
|
| 944 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 945 |
+
def check_seo(self):
|
| 946 |
+
"""Verifica problemas de SEO bΓ‘sicos."""
|
| 947 |
+
try:
|
| 948 |
+
seo_issues = self.driver.execute_script("""
|
| 949 |
+
var issues = [];
|
| 950 |
+
|
| 951 |
+
// Meta description
|
| 952 |
+
var metaDesc = document.querySelector('meta[name="description"]');
|
| 953 |
+
if (!metaDesc) {
|
| 954 |
+
issues.push({
|
| 955 |
+
type: 'NO_META_DESCRIPTION',
|
| 956 |
+
message: 'Sem meta description'
|
| 957 |
+
});
|
| 958 |
+
} else {
|
| 959 |
+
var content = metaDesc.getAttribute('content') || '';
|
| 960 |
+
if (content.length < 50) {
|
| 961 |
+
issues.push({
|
| 962 |
+
type: 'SHORT_META_DESCRIPTION',
|
| 963 |
+
message: 'Meta description muito curta (' +
|
| 964 |
+
content.length + ' chars, recomendado: 120-160)'
|
| 965 |
+
});
|
| 966 |
+
} else if (content.length > 160) {
|
| 967 |
+
issues.push({
|
| 968 |
+
type: 'LONG_META_DESCRIPTION',
|
| 969 |
+
message: 'Meta description muito longa (' +
|
| 970 |
+
content.length + ' chars, recomendado: 120-160)'
|
| 971 |
+
});
|
| 972 |
+
}
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
// Canonical
|
| 976 |
+
var canonical = document.querySelector('link[rel="canonical"]');
|
| 977 |
+
if (!canonical) {
|
| 978 |
+
issues.push({
|
| 979 |
+
type: 'NO_CANONICAL',
|
| 980 |
+
message: 'Sem link canonical definido'
|
| 981 |
+
});
|
| 982 |
+
}
|
| 983 |
+
|
| 984 |
+
// Open Graph
|
| 985 |
+
var ogTitle = document.querySelector('meta[property="og:title"]');
|
| 986 |
+
var ogDesc = document.querySelector(
|
| 987 |
+
'meta[property="og:description"]'
|
| 988 |
+
);
|
| 989 |
+
var ogImage = document.querySelector('meta[property="og:image"]');
|
| 990 |
+
if (!ogTitle || !ogDesc || !ogImage) {
|
| 991 |
+
issues.push({
|
| 992 |
+
type: 'INCOMPLETE_OG',
|
| 993 |
+
message: 'Open Graph incompleto (faltam: ' +
|
| 994 |
+
(!ogTitle ? 'og:title ' : '') +
|
| 995 |
+
(!ogDesc ? 'og:description ' : '') +
|
| 996 |
+
(!ogImage ? 'og:image' : '') + ')'
|
| 997 |
+
});
|
| 998 |
+
}
|
| 999 |
+
|
| 1000 |
+
// Robots meta
|
| 1001 |
+
var robots = document.querySelector('meta[name="robots"]');
|
| 1002 |
+
if (robots) {
|
| 1003 |
+
var content = (robots.getAttribute('content') || '').toLowerCase();
|
| 1004 |
+
if (content.includes('noindex')) {
|
| 1005 |
+
issues.push({
|
| 1006 |
+
type: 'NOINDEX',
|
| 1007 |
+
message: 'PΓ‘gina marcada como noindex (nΓ£o serΓ‘ indexada)'
|
| 1008 |
+
});
|
| 1009 |
+
}
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
+
return issues;
|
| 1013 |
+
""")
|
| 1014 |
+
|
| 1015 |
+
for issue in seo_issues:
|
| 1016 |
+
self.errors["seo_warnings"].append(issue)
|
| 1017 |
+
|
| 1018 |
+
except Exception as e:
|
| 1019 |
+
self.errors["seo_warnings"].append({
|
| 1020 |
+
"type": "CHECK_FAILED",
|
| 1021 |
+
"message": f"Erro ao verificar SEO: {str(e)}"
|
| 1022 |
+
})
|
| 1023 |
+
|
| 1024 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 1025 |
+
# CONTAGEM E GERAΓΓO DO RELATΓRIO
|
| 1026 |
+
# βββββββββββββββββββββββββββββββββββββββββββββ
|
| 1027 |
+
def _count_totals(self):
|
| 1028 |
+
"""Conta totais de erros e warnings."""
|
| 1029 |
+
error_categories = [
|
| 1030 |
+
"console_errors", "javascript_errors", "network_errors",
|
| 1031 |
+
"resource_errors", "css_errors", "html_errors",
|
| 1032 |
+
"accessibility_errors", "broken_links"
|
| 1033 |
+
]
|
| 1034 |
+
warning_categories = [
|
| 1035 |
+
"console_warnings", "security_warnings",
|
| 1036 |
+
"performance_warnings", "seo_warnings"
|
| 1037 |
+
]
|
| 1038 |
+
|
| 1039 |
+
self.total_errors = sum(
|
| 1040 |
+
len(self.errors[cat]) for cat in error_categories
|
| 1041 |
+
)
|
| 1042 |
+
self.total_warnings = sum(
|
| 1043 |
+
len(self.errors[cat]) for cat in warning_categories
|
| 1044 |
+
)
|
| 1045 |
+
|
| 1046 |
+
def generate_report_txt(self):
|
| 1047 |
+
"""Gera relatΓ³rio completo em TXT."""
|
| 1048 |
+
lines = []
|
| 1049 |
+
lines.append("=" * 70)
|
| 1050 |
+
lines.append(" RELATΓRIO COMPLETO DE ERROS DO SITE")
|
| 1051 |
+
lines.append("=" * 70)
|
| 1052 |
+
lines.append(f"\n URL Analisada: {self.url}")
|
| 1053 |
+
lines.append(f" DomΓnio: {self.domain}")
|
| 1054 |
+
lines.append(f" Data da AnΓ‘lise: {time.strftime('%d/%m/%Y %H:%M:%S')}")
|
| 1055 |
+
lines.append(f"\n TOTAL DE ERROS: {self.total_errors}")
|
| 1056 |
+
lines.append(f" TOTAL DE AVISOS: {self.total_warnings}")
|
| 1057 |
+
lines.append(f"\n{'=' * 70}")
|
| 1058 |
+
|
| 1059 |
+
sections = [
|
| 1060 |
+
("ERROS DO CONSOLE", "console_errors"),
|
| 1061 |
+
("AVISOS DO CONSOLE", "console_warnings"),
|
| 1062 |
+
("ERROS DE JAVASCRIPT", "javascript_errors"),
|
| 1063 |
+
("ERROS DE REDE", "network_errors"),
|
| 1064 |
+
("ERROS DE RECURSOS", "resource_errors"),
|
| 1065 |
+
("ERROS DE CSS", "css_errors"),
|
| 1066 |
+
("ERROS DE HTML", "html_errors"),
|
| 1067 |
+
("ERROS DE ACESSIBILIDADE", "accessibility_errors"),
|
| 1068 |
+
("AVISOS DE SEGURANΓA", "security_warnings"),
|
| 1069 |
+
("AVISOS DE PERFORMANCE", "performance_warnings"),
|
| 1070 |
+
("LINKS QUEBRADOS", "broken_links"),
|
| 1071 |
+
("AVISOS DE SEO", "seo_warnings"),
|
| 1072 |
+
]
|
| 1073 |
+
|
| 1074 |
+
for title, key in sections:
|
| 1075 |
+
items = self.errors[key]
|
| 1076 |
+
lines.append(f"\n{'β' * 70}")
|
| 1077 |
+
lines.append(f" {title} ({len(items)} encontrado(s))")
|
| 1078 |
+
lines.append(f"{'β' * 70}")
|
| 1079 |
+
|
| 1080 |
+
if not items:
|
| 1081 |
+
lines.append(" β
Nenhum problema encontrado.")
|
| 1082 |
+
else:
|
| 1083 |
+
for i, item in enumerate(items, 1):
|
| 1084 |
+
lines.append(f"\n [{i}]")
|
| 1085 |
+
if isinstance(item, dict):
|
| 1086 |
+
for k, v in item.items():
|
| 1087 |
+
lines.append(f" {k}: {v}")
|
| 1088 |
+
else:
|
| 1089 |
+
lines.append(f" {item}")
|
| 1090 |
+
|
| 1091 |
+
lines.append(f"\n{'=' * 70}")
|
| 1092 |
+
lines.append(" RelatΓ³rio gerado pelo Site Error Checker System")
|
| 1093 |
+
lines.append("=" * 70)
|
| 1094 |
+
|
| 1095 |
+
return "\n".join(lines)
|
| 1096 |
+
|
| 1097 |
+
def generate_report_json(self):
|
| 1098 |
+
"""Gera relatΓ³rio em formato JSON."""
|
| 1099 |
+
return {
|
| 1100 |
+
"url": self.url,
|
| 1101 |
+
"domain": self.domain,
|
| 1102 |
+
"date": time.strftime('%d/%m/%Y %H:%M:%S'),
|
| 1103 |
+
"total_errors": self.total_errors,
|
| 1104 |
+
"total_warnings": self.total_warnings,
|
| 1105 |
+
"details": self.errors
|
| 1106 |
+
}
|