backupsites / utils /error_checker.py
Wanderhalleylee's picture
Upload 2 files
a9cff6b verified
Raw History Blame Contribute Delete
48.8 kB
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
}