arda-site-scan
Version:
A standalone CLI tool for comprehensive website analysis including screenshots, SEO, and accessibility testing using Playwright
245 lines • 10.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SEOTester = void 0;
const chalk_1 = __importDefault(require("chalk"));
const session_manager_js_1 = require("../utils/session-manager.js");
const test_output_handler_js_1 = require("../utils/test-output-handler.js");
class SEOTester {
sessionManager;
constructor() {
this.sessionManager = new session_manager_js_1.SessionManager();
}
async runSEOScan(page, pageUrl, sessionId) {
const pageName = test_output_handler_js_1.StandardTestOutputHandler.getPageNameFromUrl(pageUrl);
// Create initial test result using standardized system
const testResult = this.sessionManager.createStandardTestResult('seo', 'pending');
try {
console.log(chalk_1.default.gray(` 🔍 Running SEO scan...`));
const seoData = await this.extractSEOData(page);
const seoReport = this.generateSEOReport(seoData, pageUrl);
// Prepare output context for the SEO scan
const context = {
url: pageUrl,
pageName
};
// Save using the standardized output system
const saveResult = await this.sessionManager.saveTestOutput(seoReport, sessionId, 'seo', context);
if (saveResult.success) {
testResult.status = 'success';
testResult.outputPath = saveResult.outputPath;
}
else {
throw new Error(saveResult.error || 'Failed to save SEO report');
}
testResult.endTime = new Date();
}
catch (error) {
testResult.status = 'failed';
testResult.error = error instanceof Error ? error.message : String(error);
testResult.endTime = new Date();
console.log(chalk_1.default.red(` ❌ SEO scan failed: ${testResult.error}`));
}
return testResult;
}
async extractSEOData(page) {
return await page.evaluate(() => {
const seoData = {
title: document.title || '',
metaDescription: '',
metaKeywords: '',
headings: {},
images: [],
links: [],
openGraph: {},
canonicalUrl: '',
robots: '',
structuredData: []
};
// Meta tags
const metaTags = document.querySelectorAll('meta');
metaTags.forEach((meta) => {
const name = meta.getAttribute('name')?.toLowerCase();
const property = meta.getAttribute('property')?.toLowerCase();
const content = meta.getAttribute('content') || '';
if (name === 'description')
seoData.metaDescription = content;
if (name === 'keywords')
seoData.metaKeywords = content;
if (name === 'robots')
seoData.robots = content;
if (property?.startsWith('og:')) {
seoData.openGraph[property] = content;
}
});
// Canonical URL
const canonical = document.querySelector('link[rel="canonical"]');
if (canonical)
seoData.canonicalUrl = canonical.href;
// Headings
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
const elements = document.querySelectorAll(tag);
seoData.headings[tag] = Array.from(elements).map((el) => el.textContent?.trim() || '');
});
// Images
const images = document.querySelectorAll('img');
seoData.images = Array.from(images).map((img) => ({
src: img.src,
alt: img.alt || ''
}));
// Links
const links = document.querySelectorAll('a[href]');
seoData.links = Array.from(links).map((link) => {
const anchorLink = link;
const href = anchorLink.href;
const text = anchorLink.textContent?.trim() || '';
const isExternal = !href.startsWith(window.location.origin) &&
(href.startsWith('http') || href.startsWith('//'));
return { href, text, isExternal };
});
// Structured data
const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
jsonLdScripts.forEach((script) => {
const scriptElement = script;
try {
const data = JSON.parse(scriptElement.textContent || '');
seoData.structuredData.push(data);
}
catch (e) {
// Invalid JSON, skip
}
});
return seoData;
});
}
generateSEOReport(seoData, pageUrl) {
let report = `# SEO Scan Report\n\n`;
report += `**URL:** ${pageUrl}\n`;
report += `**Scan Date:** ${new Date().toISOString()}\n\n`;
// Title Analysis
report += `## Title Tag\n`;
if (seoData.title) {
report += `**Title:** ${seoData.title}\n`;
report += `**Length:** ${seoData.title.length} characters\n`;
if (seoData.title.length < 30) {
report += `⚠️ **Warning:** Title is too short (recommended: 30-60 characters)\n`;
}
else if (seoData.title.length > 60) {
report += `⚠️ **Warning:** Title is too long (recommended: 30-60 characters)\n`;
}
else {
report += `✅ **Good:** Title length is optimal\n`;
}
}
else {
report += `❌ **Error:** No title tag found\n`;
}
report += '\n';
// Meta Description
report += `## Meta Description\n`;
if (seoData.metaDescription) {
report += `**Description:** ${seoData.metaDescription}\n`;
report += `**Length:** ${seoData.metaDescription.length} characters\n`;
if (seoData.metaDescription.length < 120) {
report += `⚠️ **Warning:** Meta description is too short (recommended: 120-160 characters)\n`;
}
else if (seoData.metaDescription.length > 160) {
report += `⚠️ **Warning:** Meta description is too long (recommended: 120-160 characters)\n`;
}
else {
report += `✅ **Good:** Meta description length is optimal\n`;
}
}
else {
report += `❌ **Error:** No meta description found\n`;
}
report += '\n';
// Headings Analysis
report += `## Heading Structure\n`;
Object.entries(seoData.headings).forEach(([tag, headings]) => {
if (headings.length > 0) {
report += `**${tag.toUpperCase()}** (${headings.length}):\n`;
headings.forEach(heading => {
report += `- ${heading}\n`;
});
report += '\n';
}
});
if (seoData.headings.h1?.length === 0) {
report += `❌ **Error:** No H1 tag found\n`;
}
else if (seoData.headings.h1?.length > 1) {
report += `⚠️ **Warning:** Multiple H1 tags found (${seoData.headings.h1.length})\n`;
}
else {
report += `✅ **Good:** Single H1 tag found\n`;
}
report += '\n';
// Images Analysis
report += `## Images\n`;
const imagesWithoutAlt = seoData.images.filter(img => !img.alt);
report += `**Total Images:** ${seoData.images.length}\n`;
report += `**Images without Alt Text:** ${imagesWithoutAlt.length}\n`;
if (imagesWithoutAlt.length > 0) {
report += `⚠️ **Warning:** ${imagesWithoutAlt.length} images missing alt text\n`;
imagesWithoutAlt.slice(0, 5).forEach(img => {
report += `- ${img.src}\n`;
});
if (imagesWithoutAlt.length > 5) {
report += `... and ${imagesWithoutAlt.length - 5} more\n`;
}
}
else if (seoData.images.length > 0) {
report += `✅ **Good:** All images have alt text\n`;
}
report += '\n';
// Links Analysis
report += `## Links\n`;
const externalLinks = seoData.links.filter(link => link.isExternal);
report += `**Total Links:** ${seoData.links.length}\n`;
report += `**External Links:** ${externalLinks.length}\n`;
report += `**Internal Links:** ${seoData.links.length - externalLinks.length}\n\n`;
// Open Graph
if (Object.keys(seoData.openGraph).length > 0) {
report += `## Open Graph Tags\n`;
Object.entries(seoData.openGraph).forEach(([property, content]) => {
report += `**${property}:** ${content}\n`;
});
report += '\n';
}
// Canonical URL
report += `## Canonical URL\n`;
if (seoData.canonicalUrl) {
report += `**Canonical:** ${seoData.canonicalUrl}\n`;
report += `✅ **Good:** Canonical URL is set\n`;
}
else {
report += `⚠️ **Warning:** No canonical URL found\n`;
}
report += '\n';
// Robots
report += `## Robots Meta Tag\n`;
if (seoData.robots) {
report += `**Robots:** ${seoData.robots}\n`;
}
else {
report += `**Robots:** Not specified (defaults to index, follow)\n`;
}
report += '\n';
// Structured Data
if (seoData.structuredData.length > 0) {
report += `## Structured Data\n`;
report += `**Schema.org scripts found:** ${seoData.structuredData.length}\n`;
seoData.structuredData.forEach((data, index) => {
const type = data['@type'] || 'Unknown';
report += `- Schema ${index + 1}: ${type}\n`;
});
report += '\n';
}
return report;
}
}
exports.SEOTester = SEOTester;
//# sourceMappingURL=seo-tester.js.map