#!/usr/bin/env node /** * Test: Footer Hover Programmatic (JavaScript Events) * * Tests footer hover interaction by programmatically triggering mouseenter/mouseleave events * instead of using Playwright's hover (which is blocked by overlapping buttons) */ import { chromium } from 'playwright'; const TEST_URL = 'http://localhost:1999'; const VIEWPORT_WIDTH = 375; // Mobile width const VIEWPORT_HEIGHT = 812; // iPhone X height async function testFooterHoverProgrammatic() { console.log('๐Ÿงช Testing Footer Hover with Programmatic Events'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }, deviceScaleFactor: 2, }); const page = await context.newPage(); // Disable cache await page.route('**/*', (route) => { route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache, no-store, must-revalidate', }, }); }); try { await page.goto(TEST_URL, { waitUntil: 'networkidle' }); console.log(`โœ… Navigated to ${TEST_URL}`); await page.waitForTimeout(1500); // Scroll to footer await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight); }); await page.waitForTimeout(500); const buttons = [ '.download-btn', '.print-friendly-btn', '.shortcuts-btn', '.color-theme-switcher', '.info-button', '.back-to-top' ]; let allTestsPassed = true; console.log('\n๐Ÿ“Š Test 1: Check if footer-buttons-interaction.js is loaded'); console.log('-'.repeat(70)); const jsLoaded = await page.evaluate(() => { const scripts = Array.from(document.querySelectorAll('script')); return scripts.some(script => script.src.includes('footer-buttons-interaction.js')); }); if (jsLoaded) { console.log('โœ… footer-buttons-interaction.js is loaded in the page'); } else { console.log('โŒ footer-buttons-interaction.js NOT found in the page'); allTestsPassed = false; } console.log('\n๐Ÿ“Š Test 2: Check if footer element exists'); console.log('-'.repeat(70)); const footerExists = await page.evaluate(() => { const footer = document.querySelector('footer.no-print'); return footer !== null; }); if (footerExists) { console.log('โœ… Footer element (footer.no-print) found'); } else { console.log('โŒ Footer element (footer.no-print) NOT found'); allTestsPassed = false; } console.log('\n๐Ÿ“Š Test 3: Programmatically trigger footer mouseenter'); console.log('-'.repeat(70)); // Trigger mouseenter event on footer const mouseEnterResult = await page.evaluate(() => { const footer = document.querySelector('footer.no-print'); if (!footer) return { success: false, error: 'Footer not found' }; const event = new MouseEvent('mouseenter', { bubbles: true, cancelable: true, view: window }); footer.dispatchEvent(event); // Wait for event handlers to execute AND CSS transitions to complete (300ms) return new Promise(resolve => { setTimeout(() => { const buttons = document.querySelectorAll( '.download-btn, .print-friendly-btn, .shortcuts-btn, ' + '.info-button, .back-to-top, .color-theme-switcher' ); const results = {}; buttons.forEach(btn => { const className = btn.className.split(' ').find(c => c.includes('btn') || c.includes('switcher')); results[className] = { hasClass: btn.classList.contains('footer-hovered'), opacity: window.getComputedStyle(btn).opacity, pointerEvents: window.getComputedStyle(btn).pointerEvents }; }); resolve({ success: true, buttons: results }); }, 500); // Wait 500ms for 300ms transition to complete }); }); if (mouseEnterResult.success) { console.log('โœ… Mouseenter event dispatched successfully'); console.log('\n Button states after footer mouseenter:'); for (const [btnName, data] of Object.entries(mouseEnterResult.buttons)) { const opacityCorrect = Math.abs(parseFloat(data.opacity) - 0.2) < 0.05; const pointerEventsCorrect = data.pointerEvents === 'none'; if (data.hasClass && opacityCorrect && pointerEventsCorrect) { console.log(` โœ… ${btnName}: class=${data.hasClass}, opacity=${data.opacity}, pointerEvents=${data.pointerEvents}`); } else { console.log(` โŒ ${btnName}: class=${data.hasClass}, opacity=${data.opacity}, pointerEvents=${data.pointerEvents}`); console.log(` Expected: class=true, opacity=~0.2, pointerEvents=none`); allTestsPassed = false; } } } else { console.log(`โŒ Failed to dispatch mouseenter: ${mouseEnterResult.error}`); allTestsPassed = false; } console.log('\n๐Ÿ“Š Test 4: Programmatically trigger footer mouseleave'); console.log('-'.repeat(70)); // Trigger mouseleave event on footer const mouseLeaveResult = await page.evaluate(() => { const footer = document.querySelector('footer.no-print'); if (!footer) return { success: false, error: 'Footer not found' }; const event = new MouseEvent('mouseleave', { bubbles: true, cancelable: true, view: window }); footer.dispatchEvent(event); // Wait a moment for event handlers to execute return new Promise(resolve => { setTimeout(() => { const buttons = document.querySelectorAll( '.download-btn, .print-friendly-btn, .shortcuts-btn, ' + '.info-button, .back-to-top, .color-theme-switcher' ); const results = {}; buttons.forEach(btn => { const className = btn.className.split(' ').find(c => c.includes('btn') || c.includes('switcher')); results[className] = { hasClass: btn.classList.contains('footer-hovered') }; }); resolve({ success: true, buttons: results }); }, 500); // Wait 500ms for 300ms transition to complete }); }); if (mouseLeaveResult.success) { console.log('โœ… Mouseleave event dispatched successfully'); console.log('\n Button states after footer mouseleave:'); for (const [btnName, data] of Object.entries(mouseLeaveResult.buttons)) { if (!data.hasClass) { console.log(` โœ… ${btnName}: footer-hovered class removed`); } else { console.log(` โŒ ${btnName}: footer-hovered class still present`); allTestsPassed = false; } } } else { console.log(`โŒ Failed to dispatch mouseleave: ${mouseLeaveResult.error}`); allTestsPassed = false; } console.log('-'.repeat(70)); if (allTestsPassed) { console.log('\nโœ… ALL TESTS PASSED!'); console.log(' โ€ข JavaScript file loaded correctly'); console.log(' โ€ข Footer element exists'); console.log(' โ€ข Buttons become transparent (0.2) on footer hover'); console.log(' โ€ข Buttons restore normal state on footer leave'); } else { console.log('\nโŒ SOME TESTS FAILED - Check output above'); } await browser.close(); process.exit(allTestsPassed ? 0 : 1); } catch (error) { console.error('\nโŒ Test error:', error); await browser.close(); process.exit(1); } } testFooterHoverProgrammatic();