import time import json import re import requests from urllib.parse import urljoin, urlparse from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class SiteErrorChecker: """Classe responsável por encontrar todos os erros de um site.""" def __init__(self, driver, url): self.driver = driver self.url = url self.domain = urlparse(url).netloc self.errors = { "console_errors": [], "console_warnings": [], "javascript_errors": [], "network_errors": [], "resource_errors": [], "css_errors": [], "html_errors": [], "accessibility_errors": [], "security_warnings": [], "performance_warnings": [], "broken_links": [], "seo_warnings": [], } self.total_errors = 0 self.total_warnings = 0 def run_all_checks(self): """Executa TODAS as verificações de erro.""" self.check_console_logs() self.check_javascript_errors() self.check_network_errors() self.check_resource_loading() self.check_css_errors() self.check_html_errors() self.check_accessibility() self.check_security() self.check_performance() self.check_broken_links() self.check_seo() self._count_totals() return self.errors # ───────────────────────────────────────────── # 1. ERROS DO CONSOLE DO NAVEGADOR # ───────────────────────────────────────────── def check_console_logs(self): """Captura todos os logs do console do navegador.""" try: logs = self.driver.get_log("browser") for log_entry in logs: level = log_entry.get("level", "").upper() message = log_entry.get("message", "") source = log_entry.get("source", "unknown") timestamp = log_entry.get("timestamp", 0) entry = { "level": level, "message": message, "source": source, "timestamp": time.strftime( '%H:%M:%S', time.localtime(timestamp / 1000) ) if timestamp else "N/A" } if level == "SEVERE": self.errors["console_errors"].append(entry) elif level == "WARNING": self.errors["console_warnings"].append(entry) except Exception as e: self.errors["console_errors"].append({ "level": "INFO", "message": f"Não foi possível acessar logs do console: {str(e)}", "source": "checker", "timestamp": "N/A" }) # ───────────────────────────────────────────── # 2. ERROS DE JAVASCRIPT # ───────────────────────────────────────────── def check_javascript_errors(self): """Verifica erros de JavaScript na página.""" try: js_errors = self.driver.execute_script(""" var errors = []; // Verificar se há variáveis não definidas comuns var commonChecks = [ 'jQuery', '$', 'React', 'ReactDOM', 'Vue', 'angular', 'ng', 'bootstrap' ]; // Verificar scripts com erro de carregamento var scripts = document.querySelectorAll('script[src]'); scripts.forEach(function(script) { if (script.src) { try { var xhr = new XMLHttpRequest(); xhr.open('HEAD', script.src, false); xhr.send(); if (xhr.status >= 400) { errors.push({ type: 'SCRIPT_LOAD_ERROR', message: 'Falha ao carregar script: ' + script.src, status: xhr.status, url: script.src }); } } catch(e) { errors.push({ type: 'SCRIPT_BLOCKED', message: 'Script bloqueado ou inacessível: ' + script.src, status: 0, url: script.src }); } } }); // Verificar erros de execução inline var inlineScripts = document.querySelectorAll('script:not([src])'); var inlineCount = 0; inlineScripts.forEach(function(script) { inlineCount++; var content = script.textContent || ''; // Verificar padrões suspeitos if (content.includes('undefined') && content.includes('null')) { errors.push({ type: 'POTENTIAL_ISSUE', message: 'Script inline #' + inlineCount + ' pode ter referências nulas', status: 0, url: 'inline' }); } }); // Verificar event listeners com erro try { var allElements = document.querySelectorAll('[onclick], [onerror], [onload]'); allElements.forEach(function(el) { var onclick = el.getAttribute('onclick'); if (onclick) { try { new Function(onclick); } catch(e) { errors.push({ type: 'INLINE_HANDLER_ERROR', message: 'Erro em handler inline: ' + e.message, status: 0, url: el.tagName + ' element' }); } } }); } catch(e) {} return errors; """) for err in js_errors: self.errors["javascript_errors"].append(err) except Exception as e: self.errors["javascript_errors"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar JS: {str(e)}", "status": 0, "url": self.url }) # ───────────────────────────────────────────── # 3. ERROS DE REDE # ───────────────────────────────────────────── def check_network_errors(self): """Verifica erros de rede e recursos que falharam.""" try: network_data = self.driver.execute_script(""" var entries = performance.getEntriesByType('resource'); var results = []; entries.forEach(function(entry) { var data = { name: entry.name, type: entry.initiatorType, duration: Math.round(entry.duration), size: entry.transferSize || 0, status: 'ok' }; // Se a duração é 0 e o tamanho é 0, pode ter falhado if (entry.transferSize === 0 && entry.duration === 0 && entry.initiatorType !== 'beacon') { data.status = 'possible_failure'; } // Se demorou muito (> 5 segundos) if (entry.duration > 5000) { data.status = 'slow'; } results.push(data); }); return results; """) for entry in network_data: if entry["status"] == "possible_failure": self.errors["network_errors"].append({ "type": "RESOURCE_FAILURE", "message": f"Recurso pode ter falhado ao carregar: {entry['name']}", "resource_type": entry["type"], "duration": entry["duration"] }) elif entry["status"] == "slow": self.errors["performance_warnings"].append({ "type": "SLOW_RESOURCE", "message": f"Recurso lento ({entry['duration']}ms): {entry['name']}", "resource_type": entry["type"], "duration": entry["duration"] }) except Exception as e: self.errors["network_errors"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar rede: {str(e)}", "resource_type": "unknown", "duration": 0 }) # ───────────────────────────────────────────── # 4. ERROS DE RECURSOS (IMAGENS, SCRIPTS, ETC) # ───────────────────────────────────────────── def check_resource_loading(self): """Verifica se todos os recursos carregaram corretamente.""" try: resource_errors = self.driver.execute_script(""" var errors = []; // Imagens quebradas var images = document.querySelectorAll('img'); images.forEach(function(img) { if (!img.complete || img.naturalWidth === 0) { if (img.src && !img.src.startsWith('data:')) { errors.push({ type: 'BROKEN_IMAGE', tag: 'img', url: img.src, alt: img.alt || '(sem alt)' }); } } }); // Iframes que falharam var iframes = document.querySelectorAll('iframe'); iframes.forEach(function(iframe) { try { if (iframe.src && !iframe.contentDocument && !iframe.src.startsWith('about:')) { errors.push({ type: 'IFRAME_ERROR', tag: 'iframe', url: iframe.src, alt: '' }); } } catch(e) { // Cross-origin, normal } }); // Vídeos e áudios com erro var media = document.querySelectorAll('video, audio'); media.forEach(function(m) { if (m.error) { errors.push({ type: 'MEDIA_ERROR', tag: m.tagName.toLowerCase(), url: m.src || m.querySelector('source')?.src || 'unknown', alt: 'Error code: ' + m.error.code }); } }); // Fontes que falharam if (document.fonts) { document.fonts.forEach(function(font) { if (font.status === 'error') { errors.push({ type: 'FONT_ERROR', tag: 'font', url: font.family, alt: font.style + ' ' + font.weight }); } }); } return errors; """) for err in resource_errors: self.errors["resource_errors"].append(err) except Exception as e: self.errors["resource_errors"].append({ "type": "CHECK_FAILED", "tag": "unknown", "url": str(e), "alt": "" }) # ───────────────────────────────────────────── # 5. ERROS DE CSS # ───────────────────────────────────────────── def check_css_errors(self): """Verifica erros de CSS.""" try: css_errors = self.driver.execute_script(""" var errors = []; // Verificar stylesheets que falharam ao carregar var links = document.querySelectorAll('link[rel="stylesheet"]'); links.forEach(function(link) { try { var sheet = link.sheet; if (!sheet) { errors.push({ type: 'CSS_LOAD_ERROR', message: 'Stylesheet não carregou: ' + link.href, url: link.href }); } else { try { var rules = sheet.cssRules; } catch(e) { if (e.name === 'SecurityError') { // Cross-origin, não é erro real } else { errors.push({ type: 'CSS_ACCESS_ERROR', message: 'Erro ao acessar regras CSS: ' + e.message, url: link.href }); } } } } catch(e) { errors.push({ type: 'CSS_PARSE_ERROR', message: 'Erro ao processar stylesheet: ' + e.message, url: link.href || 'unknown' }); } }); // Verificar elementos com estilos inválidos var allElements = document.querySelectorAll('*'); var checkedProps = ['display', 'position', 'z-index', 'overflow']; var elementCount = 0; allElements.forEach(function(el) { if (elementCount > 500) return; // Limitar para performance elementCount++; var style = window.getComputedStyle(el); // Verificar z-index absurdamente alto var zIndex = parseInt(style.zIndex); if (zIndex > 999999) { errors.push({ type: 'CSS_WARNING', message: 'z-index muito alto (' + zIndex + ') em ' + el.tagName + (el.id ? '#' + el.id : '') + (el.className ? '.' + String(el.className).split(' ')[0] : ''), url: 'inline' }); } }); return errors; """) for err in css_errors: self.errors["css_errors"].append(err) except Exception as e: self.errors["css_errors"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar CSS: {str(e)}", "url": self.url }) # ───────────────────────────────────────────── # 6. ERROS DE HTML # ───────────────────────────────────────────── def check_html_errors(self): """Verifica erros e problemas na estrutura HTML.""" try: html_issues = self.driver.execute_script(""" var issues = []; // Verificar DOCTYPE if (!document.doctype) { issues.push({ type: 'MISSING_DOCTYPE', message: 'Página sem DOCTYPE declarado', element: 'document' }); } // Verificar var htmlEl = document.documentElement; if (!htmlEl.lang) { issues.push({ type: 'MISSING_LANG', message: 'Atributo lang não definido no ', element: 'html' }); } // Verificar if (!document.title || document.title.trim() === '') { issues.push({ type: 'MISSING_TITLE', message: 'Página sem título (<title> vazio ou ausente)', element: 'head' }); } // Verificar meta charset var charset = document.querySelector('meta[charset]'); if (!charset) { issues.push({ type: 'MISSING_CHARSET', message: 'Meta charset não declarado', element: 'head' }); } // Verificar meta viewport var viewport = document.querySelector('meta[name="viewport"]'); if (!viewport) { issues.push({ type: 'MISSING_VIEWPORT', message: 'Meta viewport não declarado (problemas em mobile)', element: 'head' }); } // Imagens sem alt var imgs = document.querySelectorAll('img'); var noAltCount = 0; imgs.forEach(function(img) { if (!img.hasAttribute('alt')) noAltCount++; }); if (noAltCount > 0) { issues.push({ type: 'MISSING_ALT', message: noAltCount + ' imagem(ns) sem atributo alt', element: 'img' }); } // Verificar IDs duplicados var allIds = document.querySelectorAll('[id]'); var idMap = {}; allIds.forEach(function(el) { var id = el.id; if (idMap[id]) { issues.push({ type: 'DUPLICATE_ID', message: 'ID duplicado encontrado: #' + id, element: el.tagName }); } idMap[id] = true; }); // Verificar links vazios var links = document.querySelectorAll('a'); links.forEach(function(a) { var href = a.getAttribute('href'); if (!href || href === '#' || href === 'javascript:void(0)') { var text = (a.textContent || '').trim().substring(0, 50); if (text) { issues.push({ type: 'EMPTY_LINK', message: 'Link sem destino válido: "' + text + '"', element: 'a' }); } } }); // Verificar formulários sem action var forms = document.querySelectorAll('form'); forms.forEach(function(form, i) { if (!form.action || form.action === window.location.href) { issues.push({ type: 'FORM_NO_ACTION', message: 'Formulário #' + (i+1) + ' sem action definido', element: 'form' }); } }); // Verificar hierarquia de headings var headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); var h1Count = document.querySelectorAll('h1').length; if (h1Count === 0) { issues.push({ type: 'NO_H1', message: 'Página não tem nenhum <h1>', element: 'headings' }); } else if (h1Count > 1) { issues.push({ type: 'MULTIPLE_H1', message: 'Página tem ' + h1Count + ' tags <h1> (recomendado: 1)', element: 'headings' }); } return issues; """) for issue in html_issues: self.errors["html_errors"].append(issue) except Exception as e: self.errors["html_errors"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar HTML: {str(e)}", "element": "unknown" }) # ───────────────────────────────────────────── # 7. ACESSIBILIDADE # ───────────────────────────────────────────── def check_accessibility(self): """Verifica problemas de acessibilidade.""" try: a11y_issues = self.driver.execute_script(""" var issues = []; // Inputs sem label var inputs = document.querySelectorAll( 'input:not([type="hidden"]):not([type="submit"])' + ':not([type="button"]), select, textarea' ); inputs.forEach(function(input) { var id = input.id; var ariaLabel = input.getAttribute('aria-label'); var ariaLabelledBy = input.getAttribute('aria-labelledby'); var label = id ? document.querySelector('label[for="' + id + '"]') : null; var parentLabel = input.closest('label'); if (!label && !parentLabel && !ariaLabel && !ariaLabelledBy) { issues.push({ type: 'INPUT_NO_LABEL', message: 'Input sem label acessível: ' + (input.type || input.tagName) + (input.name ? ' [name=' + input.name + ']' : ''), severity: 'error' }); } }); // Botões sem texto acessível var buttons = document.querySelectorAll('button, [role="button"]'); buttons.forEach(function(btn) { var text = (btn.textContent || '').trim(); var ariaLabel = btn.getAttribute('aria-label'); var title = btn.getAttribute('title'); if (!text && !ariaLabel && !title) { issues.push({ type: 'BUTTON_NO_TEXT', message: 'Botão sem texto acessível', severity: 'error' }); } }); // Contraste (verificação básica de texto branco em fundo claro) var textElements = document.querySelectorAll('p, span, a, li, h1, h2, h3, h4, h5, h6'); var contrastCount = 0; textElements.forEach(function(el) { if (contrastCount > 100) return; contrastCount++; var style = window.getComputedStyle(el); var color = style.color; var fontSize = parseFloat(style.fontSize); if (fontSize < 12) { issues.push({ type: 'SMALL_TEXT', message: 'Texto muito pequeno (' + fontSize + 'px): "' + (el.textContent || '').trim().substring(0, 30) + '"', severity: 'warning' }); } }); // Verificar tabindex negativo var negTabindex = document.querySelectorAll('[tabindex]'); negTabindex.forEach(function(el) { var val = parseInt(el.getAttribute('tabindex')); if (val > 0) { issues.push({ type: 'POSITIVE_TABINDEX', message: 'tabindex positivo (' + val + ') pode causar navegação confusa em: ' + el.tagName, severity: 'warning' }); } }); // Verificar ARIA roles inválidos var ariaElements = document.querySelectorAll('[role]'); var validRoles = [ 'alert','alertdialog','application','article','banner', 'button','cell','checkbox','columnheader','combobox', 'complementary','contentinfo','definition','dialog', 'directory','document','feed','figure','form','grid', 'gridcell','group','heading','img','link','list', 'listbox','listitem','log','main','marquee','math', 'menu','menubar','menuitem','menuitemcheckbox', 'menuitemradio','navigation','none','note','option', 'presentation','progressbar','radio','radiogroup', 'region','row','rowgroup','rowheader','scrollbar', 'search','searchbox','separator','slider','spinbutton', 'status','switch','tab','table','tablist','tabpanel', 'term','textbox','timer','toolbar','tooltip','tree', 'treegrid','treeitem' ]; ariaElements.forEach(function(el) { var role = el.getAttribute('role'); if (validRoles.indexOf(role) === -1) { issues.push({ type: 'INVALID_ROLE', message: 'Role ARIA inválido: "' + role + '"', severity: 'error' }); } }); return issues; """) for issue in a11y_issues: self.errors["accessibility_errors"].append(issue) except Exception as e: self.errors["accessibility_errors"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar acessibilidade: {str(e)}", "severity": "info" }) # ───────────────────────────────────────────── # 8. SEGURANÇA # ───────────────────────────────────────────── def check_security(self): """Verifica problemas de segurança básicos.""" try: security_issues = self.driver.execute_script(""" var issues = []; // Verificar HTTPS if (window.location.protocol !== 'https:') { issues.push({ type: 'NO_HTTPS', message: 'Site não usa HTTPS' }); } // Verificar mixed content (recursos HTTP em página HTTPS) if (window.location.protocol === 'https:') { var allResources = document.querySelectorAll( 'img[src^="http:"], script[src^="http:"], ' + 'link[href^="http:"], iframe[src^="http:"]' ); allResources.forEach(function(el) { var url = el.src || el.href; issues.push({ type: 'MIXED_CONTENT', message: 'Conteúdo misto (HTTP em HTTPS): ' + url }); }); } // Verificar links target="_blank" sem rel="noopener" var blankLinks = document.querySelectorAll('a[target="_blank"]'); blankLinks.forEach(function(a) { var rel = (a.getAttribute('rel') || '').toLowerCase(); if (!rel.includes('noopener') && !rel.includes('noreferrer')) { issues.push({ type: 'UNSAFE_BLANK_LINK', message: 'Link com target="_blank" sem rel="noopener": ' + (a.href || '').substring(0, 80) }); } }); // Verificar forms inseguros var forms = document.querySelectorAll('form'); forms.forEach(function(form) { var action = form.action || ''; if (action.startsWith('http:') && window.location.protocol === 'https:') { issues.push({ type: 'INSECURE_FORM', message: 'Formulário envia dados via HTTP inseguro' }); } // Verificar autocomplete em campos sensíveis var passwordFields = form.querySelectorAll( 'input[type="password"]' ); passwordFields.forEach(function(pw) { if (pw.getAttribute('autocomplete') === 'on') { issues.push({ type: 'PASSWORD_AUTOCOMPLETE', message: 'Campo de senha com autocomplete habilitado' }); } }); }); // Verificar inline event handlers (potencial XSS) var inlineHandlers = document.querySelectorAll( '[onload], [onerror], [onclick], [onmouseover]' ); if (inlineHandlers.length > 10) { issues.push({ type: 'EXCESSIVE_INLINE_HANDLERS', message: inlineHandlers.length + ' elementos com event handlers inline ' + '(considere usar addEventListener)' }); } return issues; """) for issue in security_issues: self.errors["security_warnings"].append(issue) except Exception as e: self.errors["security_warnings"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar segurança: {str(e)}" }) # ───────────────────────────────────────────── # 9. PERFORMANCE # ───────────────────────────────────────────── def check_performance(self): """Verifica problemas de performance.""" try: perf_data = self.driver.execute_script(""" var issues = []; var timing = performance.timing; // Tempo de carregamento total var loadTime = timing.loadEventEnd - timing.navigationStart; if (loadTime > 5000) { issues.push({ type: 'SLOW_LOAD', message: 'Página demorou ' + (loadTime / 1000).toFixed(2) + 's para carregar (recomendado: < 3s)', value: loadTime }); } // DOM muito grande var domSize = document.querySelectorAll('*').length; if (domSize > 1500) { issues.push({ type: 'LARGE_DOM', message: 'DOM muito grande: ' + domSize + ' elementos (recomendado: < 1500)', value: domSize }); } // Muitas requisições var resources = performance.getEntriesByType('resource'); if (resources.length > 80) { issues.push({ type: 'TOO_MANY_REQUESTS', message: resources.length + ' requisições de recursos (recomendado: < 80)', value: resources.length }); } // Imagens sem dimensões (causa layout shift) var imgs = document.querySelectorAll('img'); var noDimCount = 0; imgs.forEach(function(img) { if (!img.getAttribute('width') && !img.getAttribute('height') && !img.style.width && !img.style.height) { noDimCount++; } }); if (noDimCount > 0) { issues.push({ type: 'IMG_NO_DIMENSIONS', message: noDimCount + ' imagem(ns) sem dimensões definidas ' + '(causa Layout Shift)', value: noDimCount }); } // CSS render-blocking var cssLinks = document.querySelectorAll( 'link[rel="stylesheet"]:not([media="print"])' + ':not([media="none"])' ); if (cssLinks.length > 5) { issues.push({ type: 'MANY_CSS_FILES', message: cssLinks.length + ' arquivos CSS render-blocking ' + '(considere concatenar)', value: cssLinks.length }); } // JS no <head> sem defer/async var blockingJS = document.querySelectorAll( 'head script[src]:not([defer]):not([async])' ); if (blockingJS.length > 0) { issues.push({ type: 'BLOCKING_JS', message: blockingJS.length + ' script(s) bloqueantes no <head> ' + '(adicione defer ou async)', value: blockingJS.length }); } return issues; """) for issue in perf_data: self.errors["performance_warnings"].append(issue) except Exception as e: self.errors["performance_warnings"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar performance: {str(e)}", "value": 0 }) # ───────────────────────────────────────────── # 10. LINKS QUEBRADOS # ───────────────────────────────────────────── def check_broken_links(self): """Verifica links quebrados na página.""" try: links = self.driver.execute_script(""" var anchors = document.querySelectorAll('a[href]'); var urls = []; anchors.forEach(function(a) { var href = a.href; if (href && !href.startsWith('javascript:') && !href.startsWith('mailto:') && !href.startsWith('tel:') && !href.startsWith('#') && !href.startsWith('data:')) { urls.push({ url: href, text: (a.textContent || '').trim().substring(0, 50) }); } }); // Limitar a 50 links para não demorar return urls.slice(0, 50); """) headers = { "User-Agent": "Mozilla/5.0 (compatible; SiteChecker/1.0)" } for link_data in links: try: resp = requests.head( link_data["url"], headers=headers, timeout=8, allow_redirects=True, verify=False ) if resp.status_code >= 400: self.errors["broken_links"].append({ "url": link_data["url"], "text": link_data["text"], "status_code": resp.status_code }) except requests.exceptions.Timeout: self.errors["broken_links"].append({ "url": link_data["url"], "text": link_data["text"], "status_code": "TIMEOUT" }) except requests.exceptions.ConnectionError: self.errors["broken_links"].append({ "url": link_data["url"], "text": link_data["text"], "status_code": "CONNECTION_ERROR" }) except Exception: pass except Exception as e: self.errors["broken_links"].append({ "url": self.url, "text": f"Erro ao verificar links: {str(e)}", "status_code": "CHECK_FAILED" }) # ───────────────────────────────────────────── # 11. SEO BÁSICO # ───────────────────────────────────────────── def check_seo(self): """Verifica problemas de SEO básicos.""" try: seo_issues = self.driver.execute_script(""" var issues = []; // Meta description var metaDesc = document.querySelector('meta[name="description"]'); if (!metaDesc) { issues.push({ type: 'NO_META_DESCRIPTION', message: 'Sem meta description' }); } else { var content = metaDesc.getAttribute('content') || ''; if (content.length < 50) { issues.push({ type: 'SHORT_META_DESCRIPTION', message: 'Meta description muito curta (' + content.length + ' chars, recomendado: 120-160)' }); } else if (content.length > 160) { issues.push({ type: 'LONG_META_DESCRIPTION', message: 'Meta description muito longa (' + content.length + ' chars, recomendado: 120-160)' }); } } // Canonical var canonical = document.querySelector('link[rel="canonical"]'); if (!canonical) { issues.push({ type: 'NO_CANONICAL', message: 'Sem link canonical definido' }); } // Open Graph var ogTitle = document.querySelector('meta[property="og:title"]'); var ogDesc = document.querySelector( 'meta[property="og:description"]' ); var ogImage = document.querySelector('meta[property="og:image"]'); if (!ogTitle || !ogDesc || !ogImage) { issues.push({ type: 'INCOMPLETE_OG', message: 'Open Graph incompleto (faltam: ' + (!ogTitle ? 'og:title ' : '') + (!ogDesc ? 'og:description ' : '') + (!ogImage ? 'og:image' : '') + ')' }); } // Robots meta var robots = document.querySelector('meta[name="robots"]'); if (robots) { var content = (robots.getAttribute('content') || '').toLowerCase(); if (content.includes('noindex')) { issues.push({ type: 'NOINDEX', message: 'Página marcada como noindex (não será indexada)' }); } } return issues; """) for issue in seo_issues: self.errors["seo_warnings"].append(issue) except Exception as e: self.errors["seo_warnings"].append({ "type": "CHECK_FAILED", "message": f"Erro ao verificar SEO: {str(e)}" }) # ───────────────────────────────────────────── # CONTAGEM E GERAÇÃO DO RELATÓRIO # ───────────────────────────────────────────── def _count_totals(self): """Conta totais de erros e warnings.""" error_categories = [ "console_errors", "javascript_errors", "network_errors", "resource_errors", "css_errors", "html_errors", "accessibility_errors", "broken_links" ] warning_categories = [ "console_warnings", "security_warnings", "performance_warnings", "seo_warnings" ] self.total_errors = sum( len(self.errors[cat]) for cat in error_categories ) self.total_warnings = sum( len(self.errors[cat]) for cat in warning_categories ) def generate_report_txt(self): """Gera relatório completo em TXT.""" lines = [] lines.append("=" * 70) lines.append(" RELATÓRIO COMPLETO DE ERROS DO SITE") lines.append("=" * 70) lines.append(f"\n URL Analisada: {self.url}") lines.append(f" Domínio: {self.domain}") lines.append(f" Data da Análise: {time.strftime('%d/%m/%Y %H:%M:%S')}") lines.append(f"\n TOTAL DE ERROS: {self.total_errors}") lines.append(f" TOTAL DE AVISOS: {self.total_warnings}") lines.append(f"\n{'=' * 70}") sections = [ ("ERROS DO CONSOLE", "console_errors"), ("AVISOS DO CONSOLE", "console_warnings"), ("ERROS DE JAVASCRIPT", "javascript_errors"), ("ERROS DE REDE", "network_errors"), ("ERROS DE RECURSOS", "resource_errors"), ("ERROS DE CSS", "css_errors"), ("ERROS DE HTML", "html_errors"), ("ERROS DE ACESSIBILIDADE", "accessibility_errors"), ("AVISOS DE SEGURANÇA", "security_warnings"), ("AVISOS DE PERFORMANCE", "performance_warnings"), ("LINKS QUEBRADOS", "broken_links"), ("AVISOS DE SEO", "seo_warnings"), ] for title, key in sections: items = self.errors[key] lines.append(f"\n{'─' * 70}") lines.append(f" {title} ({len(items)} encontrado(s))") lines.append(f"{'─' * 70}") if not items: lines.append(" ✅ Nenhum problema encontrado.") else: for i, item in enumerate(items, 1): lines.append(f"\n [{i}]") if isinstance(item, dict): for k, v in item.items(): lines.append(f" {k}: {v}") else: lines.append(f" {item}") lines.append(f"\n{'=' * 70}") lines.append(" Relatório gerado pelo Site Error Checker System") lines.append("=" * 70) return "\n".join(lines) def generate_report_json(self): """Gera relatório em formato JSON.""" return { "url": self.url, "domain": self.domain, "date": time.strftime('%d/%m/%Y %H:%M:%S'), "total_errors": self.total_errors, "total_warnings": self.total_warnings, "details": self.errors }