#!/usr/bin/env node /** * Test: Back-to-Top Button and Footer Text Fixes * * Verifies: * 1. Back-to-top button shows full opacity (no transparency) when at page bottom * 2. Footer text enlarges when footer is hovered/touched */ 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 testBackToTopAndFooterFixes() { console.log('๐Ÿงช Testing Back-to-Top Button Opacity & Footer Text Enlargement'); 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); let allTestsPassed = true; // TEST 1: Back-to-top button should have full opacity when at bottom console.log('\n๐Ÿ“Š TEST 1: Back-to-top button full opacity at page bottom'); console.log('-'.repeat(70)); // Scroll to bottom await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight); }); await page.waitForTimeout(500); const backToTopAtBottom = await page.evaluate(() => { const btn = document.querySelector('.back-to-top'); if (!btn) return { exists: false }; const hasAtBottomClass = btn.classList.contains('at-bottom'); const styles = window.getComputedStyle(btn); const opacity = parseFloat(styles.opacity); const backgroundColor = styles.backgroundColor; return { exists: true, hasAtBottomClass, opacity, backgroundColor }; }); if (backToTopAtBottom.exists) { // Should have opacity: 1 (full opacity, no transparency) if (backToTopAtBottom.opacity >= 0.95) { console.log(`โœ… Back-to-top button has full opacity: ${backToTopAtBottom.opacity}`); console.log(` Background: ${backToTopAtBottom.backgroundColor}`); console.log(` Has .at-bottom class: ${backToTopAtBottom.hasAtBottomClass}`); } else { console.log(`โŒ Back-to-top button still has transparency: opacity=${backToTopAtBottom.opacity} (expected >= 0.95)`); allTestsPassed = false; } } else { console.log('โŒ Back-to-top button not found'); allTestsPassed = false; } // TEST 2: Footer text should enlarge when footer is hovered console.log('\n๐Ÿ“Š TEST 2: Footer text enlarges on hover'); console.log('-'.repeat(70)); // Get initial font size before hover const initialFooterState = await page.evaluate(() => { const footer = document.querySelector('footer.no-print'); const footerP = document.querySelector('footer.no-print p'); if (!footer || !footerP) return { exists: false }; const styles = window.getComputedStyle(footerP); return { exists: true, fontSize: parseFloat(styles.fontSize), footerHasClass: footer.classList.contains('footer-hovered') }; }); if (!initialFooterState.exists) { console.log('โŒ Footer or footer paragraph not found'); allTestsPassed = false; } else { console.log(` Initial font size: ${initialFooterState.fontSize.toFixed(1)}px`); console.log(` Footer has .footer-hovered class: ${initialFooterState.footerHasClass}`); // Trigger footer hover by adding class via JavaScript await page.evaluate(() => { const footer = document.querySelector('footer.no-print'); if (footer) { // Dispatch mouseenter event const event = new MouseEvent('mouseenter', { bubbles: true, cancelable: true, view: window }); footer.dispatchEvent(event); } }); // Wait for CSS transition await page.waitForTimeout(500); // Get font size after hover const hoveredFooterState = await page.evaluate(() => { const footer = document.querySelector('footer.no-print'); const footerP = document.querySelector('footer.no-print p'); if (!footer || !footerP) return { exists: false }; const styles = window.getComputedStyle(footerP); return { exists: true, fontSize: parseFloat(styles.fontSize), footerHasClass: footer.classList.contains('footer-hovered'), computedFontSize: styles.fontSize }; }); if (hoveredFooterState.exists) { console.log(` After hover font size: ${hoveredFooterState.fontSize.toFixed(1)}px`); console.log(` Footer has .footer-hovered class: ${hoveredFooterState.footerHasClass}`); if (hoveredFooterState.footerHasClass) { console.log('โœ… Footer received .footer-hovered class'); } else { console.log('โŒ Footer did NOT receive .footer-hovered class'); allTestsPassed = false; } // Check if font size increased (should be 1.2x larger) if (hoveredFooterState.fontSize > initialFooterState.fontSize) { const increase = ((hoveredFooterState.fontSize - initialFooterState.fontSize) / initialFooterState.fontSize) * 100; console.log(`โœ… Footer text enlarged by ${increase.toFixed(1)}%`); } else { console.log(`โŒ Footer text did NOT enlarge (${initialFooterState.fontSize.toFixed(1)}px โ†’ ${hoveredFooterState.fontSize.toFixed(1)}px)`); allTestsPassed = false; } } else { console.log('โŒ Could not measure hovered footer state'); allTestsPassed = false; } } console.log('-'.repeat(70)); if (allTestsPassed) { console.log('\nโœ… ALL TESTS PASSED!'); console.log(' โ€ข Back-to-top button has full opacity at page bottom'); console.log(' โ€ข Footer text enlarges when hovered'); } 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); } } testBackToTopAndFooterFixes();