#!/usr/bin/env node /** * REAL SEO Verification Test * Tests actual rendered HTML output, not just file existence */ const https = require('https'); const http = require('http'); const BASE_URL = 'localhost'; const PORT = 3000; function fetchPage(path) { return new Promise((resolve, reject) => { const req = http.get({ hostname: BASE_URL, port: PORT, path }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(data)); }); req.on('error', reject); req.setTimeout(5000, () => { req.destroy(); reject(new Error('Timeout')); }); }); } function extractMetaTags(html) { const tags = {}; // Title const titleMatch = html.match(/([^<]*)<\/title>/); if (titleMatch) tags.title = titleMatch[1]; // Meta description const descMatch = html.match(/<meta[^>]*name="description"[^>]*content="([^"]*)"[^>]*>/); if (descMatch) tags.description = descMatch[1]; // Meta keywords const keywordsMatch = html.match(/<meta[^>]*name="keywords"[^>]*content="([^"]*)"[^>]*>/); if (keywordsMatch) tags.keywords = keywordsMatch[1]; // Canonical const canonicalMatch = html.match(/<link[^>]*rel="canonical"[^>]*href="([^"]*)"[^>]*>/); if (canonicalMatch) tags.canonical = canonicalMatch[1]; // Robots const robotsMatch = html.match(/<meta[^>]*name="robots"[^>]*content="([^"]*)"[^>]*>/); if (robotsMatch) tags.robots = robotsMatch[1]; // OpenGraph tags const ogTitle = html.match(/<meta[^>]*property="og:title"[^>]*content="([^"]*)"[^>]*>/); if (ogTitle) tags.ogTitle = ogTitle[1]; const ogDesc = html.match(/<meta[^>]*property="og:description"[^>]*content="([^"]*)"[^>]*>/); if (ogDesc) tags.ogDescription = ogDesc[1]; const ogUrl = html.match(/<meta[^>]*property="og:url"[^>]*content="([^"]*)"[^>]*>/); if (ogUrl) tags.ogUrl = ogUrl[1]; // Twitter cards const twitterCard = html.match(/<meta[^>]*name="twitter:card"[^>]*content="([^"]*)"[^>]*>/); if (twitterCard) tags.twitterCard = twitterCard[1]; return tags; } function checkJsonLd(html) { const schemas = []; const scriptMatches = html.matchAll(/<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/g); for (const match of scriptMatches) { try { const json = JSON.parse(match[1]); schemas.push(json); } catch (e) { // Invalid JSON, skip } } return schemas; } async function runTests() { console.log('šŸ” Testing ACTUAL Rendered SEO Output...\n'); console.log(`Testing: http://${BASE_URL}:${PORT}/sr\n`); try { const html = await fetchPage('/sr'); console.log('āœ… Page fetched successfully'); console.log(` Size: ${(html.length / 1024).toFixed(1)} KB\n`); // Test 1: Meta Tags console.log('šŸ“‹ META TAGS:'); const meta = extractMetaTags(html); console.log(` Title: ${meta.title ? 'āœ… ' + meta.title.substring(0, 60) + '...' : 'āŒ MISSING'}`); console.log(` Description: ${meta.description ? 'āœ… ' + meta.description.substring(0, 60) + '...' : 'āŒ MISSING'}`); console.log(` Keywords: ${meta.keywords ? 'āœ… ' + meta.keywords.split(',').length + ' keywords' : 'āŒ MISSING'}`); console.log(` Canonical: ${meta.canonical ? 'āœ… ' + meta.canonical : 'āŒ MISSING'}`); console.log(` Robots: ${meta.robots ? 'āœ… ' + meta.robots : 'āŒ MISSING'}`); console.log(); // Test 2: OpenGraph console.log('šŸ“± OPEN GRAPH:'); console.log(` og:title: ${meta.ogTitle ? 'āœ… Present' : 'āŒ MISSING'}`); console.log(` og:description: ${meta.ogDescription ? 'āœ… Present' : 'āŒ MISSING'}`); console.log(` og:url: ${meta.ogUrl ? 'āœ… ' + meta.ogUrl : 'āŒ MISSING'}`); console.log(); // Test 3: Twitter Cards console.log('🐦 TWITTER CARDS:'); console.log(` twitter:card: ${meta.twitterCard ? 'āœ… ' + meta.twitterCard : 'āŒ MISSING'}`); console.log(); // Test 4: JSON-LD Schemas console.log('šŸ—ļø JSON-LD SCHEMAS:'); const schemas = checkJsonLd(html); console.log(` Found: ${schemas.length} schema(s)`); schemas.forEach((schema, i) => { console.log(` Schema ${i + 1}: āœ… @type="${schema['@type']}"`); }); console.log(); // Summary const hasTitle = !!meta.title; const hasDesc = !!meta.description; const hasKeywords = !!meta.keywords; const hasCanonical = !!meta.canonical; const hasOg = !!meta.ogTitle; const hasTwitter = !!meta.twitterCard; const hasSchemas = schemas.length > 0; const passed = [hasTitle, hasDesc, hasKeywords, hasCanonical, hasOg, hasTwitter, hasSchemas].filter(Boolean).length; const total = 7; console.log('='.repeat(50)); console.log(`Results: ${passed}/${total} checks passed`); console.log('='.repeat(50)); if (passed === total) { console.log('\nšŸŽ‰ All SEO elements are rendering correctly!'); process.exit(0); } else { console.log(`\nāš ļø ${total - passed} SEO element(s) missing`); process.exit(1); } } catch (error) { console.error('\nāŒ Error:', error.message); console.log('\nMake sure the dev server is running on port 3000'); process.exit(1); } } runTests();