#!/usr/bin/env bun /** * HTMX FUNCTIONALITY TEST * ======================== * Tests HTMX swap behavior and indicators * - Verifies HTMX is loaded * - Tests hx-get requests * - Validates swap behavior * - Checks loading indicators */ import { chromium } from 'playwright'; const URL = "http://localhost:1999"; async function testHTMX() { console.log('⚡ HTMX FUNCTIONALITY TEST\n'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: false }); const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); const errors = []; const testResults = []; page.on('console', msg => { const text = msg.text(); if (msg.type() === 'error') { errors.push(text); console.log(`❌ ERROR: ${text}`); } }); console.log("\n1️⃣ Loading page..."); await page.goto(URL); await page.waitForTimeout(2000); // ======================================================================== // TEST 1: HTMX is loaded // ======================================================================== console.log("\n2️⃣ Testing HTMX Loaded..."); const htmxLoaded = await page.evaluate(() => { return { htmxExists: typeof htmx !== 'undefined', version: typeof htmx !== 'undefined' ? htmx.version : 'N/A' }; }); console.log(` HTMX loaded: ${htmxLoaded.htmxExists ? '✅' : '❌'}`); console.log(` Version: ${htmxLoaded.version}`); console.log(` ${htmxLoaded.htmxExists ? '✅ PASS' : '❌ FAIL'} - HTMX loaded`); testResults.push({ test: 'HTMX Loaded', passed: htmxLoaded.htmxExists }); // ======================================================================== // TEST 2: Check for HTMX elements // ======================================================================== console.log("\n3️⃣ Testing HTMX Elements..."); const htmxElements = await page.evaluate(() => { const hxGetElements = document.querySelectorAll('[hx-get]').length; const hxPostElements = document.querySelectorAll('[hx-post]').length; const hxSwapElements = document.querySelectorAll('[hx-swap]').length; const hxTargetElements = document.querySelectorAll('[hx-target]').length; return { hxGet: hxGetElements, hxPost: hxPostElements, hxSwap: hxSwapElements, hxTarget: hxTargetElements, total: hxGetElements + hxPostElements + hxSwapElements + hxTargetElements }; }); console.log(` hx-get elements: ${htmxElements.hxGet}`); console.log(` hx-post elements: ${htmxElements.hxPost}`); console.log(` hx-swap elements: ${htmxElements.hxSwap}`); console.log(` hx-target elements: ${htmxElements.hxTarget}`); console.log(` Total HTMX attributes: ${htmxElements.total}`); console.log(` ${htmxElements.total > 0 ? '✅ PASS' : '❌ FAIL'} - HTMX elements found`); testResults.push({ test: 'HTMX Elements Present', passed: htmxElements.total > 0 }); // ======================================================================== // TEST 3: HTMX request/response (if any interactive elements exist) // ======================================================================== console.log("\n4️⃣ Testing HTMX Request Handling..."); // Look for any clickable HTMX elements const htmxButton = await page.$('[hx-get], [hx-post]'); if (htmxButton) { // Track HTMX events const htmxEvents = await page.evaluate(() => { return new Promise((resolve) => { const events = []; let timeout; const handlers = { 'htmx:beforeRequest': (e) => events.push({ type: 'beforeRequest', target: e.detail.target.id }), 'htmx:afterRequest': (e) => events.push({ type: 'afterRequest', status: e.detail.xhr.status }), 'htmx:beforeSwap': (e) => events.push({ type: 'beforeSwap' }), 'htmx:afterSwap': (e) => events.push({ type: 'afterSwap' }) }; // Add event listeners Object.entries(handlers).forEach(([event, handler]) => { document.body.addEventListener(event, handler); }); // Find first HTMX element and click it const element = document.querySelector('[hx-get], [hx-post]'); if (element) { element.click(); // Wait for events or timeout timeout = setTimeout(() => { Object.entries(handlers).forEach(([event, handler]) => { document.body.removeEventListener(event, handler); }); resolve(events); }, 3000); } else { resolve([]); } }); }); const hasBeforeRequest = htmxEvents.some(e => e.type === 'beforeRequest'); const hasAfterRequest = htmxEvents.some(e => e.type === 'afterRequest'); const requestWorked = hasBeforeRequest && hasAfterRequest; console.log(` HTMX events captured: ${htmxEvents.length}`); htmxEvents.forEach(e => { console.log(` - ${e.type}${e.status ? ` (${e.status})` : ''}`); }); console.log(` ${requestWorked ? '✅ PASS' : '⚠️ SKIP'} - HTMX request cycle`); testResults.push({ test: 'HTMX Request Cycle', passed: requestWorked }); } else { console.log(` ⚠️ SKIP - No interactive HTMX elements found`); testResults.push({ test: 'HTMX Request Cycle', passed: true }); // Skip = pass } // ======================================================================== // TEST 4: HTMX indicators // ======================================================================== console.log("\n5️⃣ Testing HTMX Indicators..."); const indicatorsTest = await page.evaluate(() => { const hasIndicator = document.querySelector('.htmx-indicator') !== null; const hasSwapping = document.querySelector('.htmx-swapping') !== null; const hasSettling = document.querySelector('.htmx-settling') !== null; return { indicator: hasIndicator, swapping: hasSwapping, settling: hasSettling, hasAny: hasIndicator || hasSwapping || hasSettling }; }); console.log(` htmx-indicator: ${indicatorsTest.indicator ? '✅' : '⚠️ Not found'}`); console.log(` htmx-swapping: ${indicatorsTest.swapping ? '✅' : '⚠️ Not in use'}`); console.log(` htmx-settling: ${indicatorsTest.settling ? '✅' : '⚠️ Not in use'}`); console.log(` ${indicatorsTest.hasAny ? '✅ PASS' : '⚠️ INFO'} - Indicators configured`); testResults.push({ test: 'HTMX Indicators', passed: true }); // Info only // ======================================================================== // 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"); if (failedTests === 0) { console.log("🎉 HTMX FUNCTIONALITY VALIDATED!"); } else { console.log("⚠️ SOME TESTS FAILED - See details above"); } console.log("\nBrowser will stay open for manual inspection."); console.log("Press Ctrl+C when done.\n"); await new Promise(() => {}); // Keep browser open } await testHTMX();