#!/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 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(` : ${initialState.htmlLang}`);
console.log(` : ${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(` : ${spanishState.htmlLang}`);
console.log(` : ${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'} - 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(` : ${englishState.htmlLang}`);
console.log(` : ${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'} - 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(` tags: ${duplicateCheck.titles}`);
console.log(` tags: ${duplicateCheck.descriptions}`);
console.log(` 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();