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 ( 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 ',
element: 'headings'
});
} else if (h1Count > 1) {
issues.push({
type: 'MULTIPLE_H1',
message: 'Página tem ' + h1Count + ' tags (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 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 ' +
'(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
}