#!/usr/bin/env bun /** * HEAD-SUPPORT EXTENSION TEST * ============================ * Tests that the head-support extension updates tags * and on language switching via HTMX * - Verifies updates on language switch * - Verifies updates on language switch * - Verifies <meta description> updates * - Verifies no duplicate tags after multiple switches */ import { chromium } from 'playwright'; const URL = "http://localhost:1999"; async function testHeadSupport() { console.log('🏷️ HEAD-SUPPORT EXTENSION TEST\n'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: true }); const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); const errors = []; const testResults = []; page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); } }); console.log("\n1️⃣ Loading page (English default)..."); await page.goto(URL); await page.waitForTimeout(2000); // ======================================================================== // TEST 1: Head-support extension is loaded // ======================================================================== console.log("\n2️⃣ Testing head-support extension loaded..."); const extLoaded = await page.evaluate(() => { const body = document.body; const hxExt = body.getAttribute('hx-ext'); return { hasHxExt: hxExt !== null && hxExt.includes('head-support'), hxExtValue: hxExt }; }); console.log(` hx-ext attribute: ${extLoaded.hxExtValue}`); console.log(` ${extLoaded.hasHxExt ? 'βœ… PASS' : '❌ FAIL'} - head-support extension activated`); testResults.push({ test: 'Head-support Extension Loaded', passed: extLoaded.hasHxExt }); // ======================================================================== // TEST 2: Initial state - English // ======================================================================== console.log("\n3️⃣ Testing initial state (English)..."); const initialState = await page.evaluate(() => { return { htmlLang: document.documentElement.getAttribute('lang'), title: document.title, metaDesc: document.querySelector('meta[name="description"]')?.getAttribute('content') || '', canonical: document.querySelector('link[rel="canonical"]')?.getAttribute('href') || '' }; }); console.log(` <html lang>: ${initialState.htmlLang}`); console.log(` <title>: ${initialState.title}`); console.log(` canonical: ${initialState.canonical}`); const initialOk = initialState.htmlLang === 'en'; console.log(` ${initialOk ? 'βœ… PASS' : '❌ FAIL'} - Initial state is English`); testResults.push({ test: 'Initial English State', passed: initialOk }); // ======================================================================== // TEST 3: Switch to Spanish via HTMX button // ======================================================================== console.log("\n4️⃣ Switching to Spanish via HTMX..."); // Click the Spanish button const esButton = await page.$('button[aria-label="EspaΓ±ol"]'); if (esButton) { await esButton.click(); await page.waitForTimeout(3000); // Wait for HTMX swap + head-support processing const spanishState = await page.evaluate(() => { return { htmlLang: document.documentElement.getAttribute('lang'), title: document.title, metaDesc: document.querySelector('meta[name="description"]')?.getAttribute('content') || '', canonical: document.querySelector('link[rel="canonical"]')?.getAttribute('href') || '', ogLocale: document.querySelector('meta[property="og:locale"]')?.getAttribute('content') || '' }; }); console.log(` <html lang>: ${spanishState.htmlLang}`); console.log(` <title>: ${spanishState.title}`); console.log(` canonical: ${spanishState.canonical}`); console.log(` og:locale: ${spanishState.ogLocale}`); const langChanged = spanishState.htmlLang === 'es'; const canonicalChanged = spanishState.canonical.includes('lang=es'); const descChanged = spanishState.metaDesc.length > 0; console.log(` ${langChanged ? 'βœ… PASS' : '❌ FAIL'} - <html lang> changed to "es"`); console.log(` ${canonicalChanged ? 'βœ… PASS' : '❌ FAIL'} - canonical URL updated to Spanish`); console.log(` ${descChanged ? 'βœ… PASS' : '❌ FAIL'} - meta description present`); testResults.push({ test: 'HTML Lang Updated to ES', passed: langChanged }); testResults.push({ test: 'Canonical URL Updated to ES', passed: canonicalChanged }); testResults.push({ test: 'Meta Description Present', passed: descChanged }); } else { console.log(' ❌ FAIL - Spanish button not found'); testResults.push({ test: 'HTML Lang Updated to ES', passed: false }); testResults.push({ test: 'Canonical URL Updated to ES', passed: false }); testResults.push({ test: 'Meta Description Present', passed: false }); } // ======================================================================== // TEST 4: Switch back to English // ======================================================================== console.log("\n5️⃣ Switching back to English..."); const enButton = await page.$('button[aria-label="English"]'); if (enButton) { await enButton.click(); await page.waitForTimeout(3000); const englishState = await page.evaluate(() => { return { htmlLang: document.documentElement.getAttribute('lang'), title: document.title, canonical: document.querySelector('link[rel="canonical"]')?.getAttribute('href') || '', ogLocale: document.querySelector('meta[property="og:locale"]')?.getAttribute('content') || '' }; }); console.log(` <html lang>: ${englishState.htmlLang}`); console.log(` <title>: ${englishState.title}`); console.log(` canonical: ${englishState.canonical}`); console.log(` og:locale: ${englishState.ogLocale}`); const langBack = englishState.htmlLang === 'en'; const canonicalBack = englishState.canonical.includes('lang=en'); console.log(` ${langBack ? 'βœ… PASS' : '❌ FAIL'} - <html lang> back to "en"`); console.log(` ${canonicalBack ? 'βœ… PASS' : '❌ FAIL'} - canonical URL back to English`); testResults.push({ test: 'HTML Lang Restored to EN', passed: langBack }); testResults.push({ test: 'Canonical URL Restored to EN', passed: canonicalBack }); } else { console.log(' ❌ FAIL - English button not found'); testResults.push({ test: 'HTML Lang Restored to EN', passed: false }); testResults.push({ test: 'Canonical URL Restored to EN', passed: false }); } // ======================================================================== // TEST 5: No duplicate tags after multiple switches // ======================================================================== console.log("\n6️⃣ Testing no duplicate tags after multiple switches..."); const duplicateCheck = await page.evaluate(() => { const titles = document.querySelectorAll('title').length; const descriptions = document.querySelectorAll('meta[name="description"]').length; const canonicals = document.querySelectorAll('link[rel="canonical"]').length; const ogLocales = document.querySelectorAll('meta[property="og:locale"]').length; return { titles, descriptions, canonicals, ogLocales }; }); console.log(` <title> tags: ${duplicateCheck.titles}`); console.log(` <meta description> tags: ${duplicateCheck.descriptions}`); console.log(` <link canonical> tags: ${duplicateCheck.canonicals}`); console.log(` og:locale tags: ${duplicateCheck.ogLocales}`); const noDuplicates = duplicateCheck.titles === 1 && duplicateCheck.descriptions === 1 && duplicateCheck.canonicals === 1 && duplicateCheck.ogLocales === 1; console.log(` ${noDuplicates ? 'βœ… PASS' : '❌ FAIL'} - No duplicate tags`); testResults.push({ test: 'No Duplicate Tags', passed: noDuplicates }); // ======================================================================== // FINAL SUMMARY // ======================================================================== console.log("\n" + "=".repeat(70)); console.log("πŸ“Š TEST SUMMARY\n"); const totalTests = testResults.length; const passedTests = testResults.filter(r => r.passed).length; const failedTests = totalTests - passedTests; testResults.forEach(result => { console.log(` ${result.passed ? 'βœ…' : '❌'} ${result.test}`); }); console.log(`\n Total: ${passedTests}/${totalTests} tests passed`); if (errors.length === 0) { console.log("\nβœ… NO CONSOLE ERRORS"); } else { console.log(`\n⚠️ ${errors.length} CONSOLE ERRORS`); } console.log("=".repeat(70) + "\n"); await browser.close(); if (failedTests === 0) { console.log("πŸŽ‰ HEAD-SUPPORT EXTENSION VALIDATED!\n"); process.exit(0); } else { console.log("⚠️ SOME TESTS FAILED - See details above\n"); process.exit(1); } } await testHeadSupport();