Spaces:
Running
Running
| 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 <html lang=""> | |
| var htmlEl = document.documentElement; | |
| if (!htmlEl.lang) { | |
| issues.push({ | |
| type: 'MISSING_LANG', | |
| message: 'Atributo lang nΓ£o definido no <html>', | |
| element: 'html' | |
| }); | |
| } | |
| // Verificar <title> | |
| 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 | |
| } | |