#!/usr/bin/env bun /** * MOBILE FAB OVERFLOW TEST * ======================== * Tests that floating action buttons don't overflow on very small screens * like iPhone 13 mini (375px) and similar devices. * * The issue: FAB buttons were calculated for minimum 380px width, * causing overflow on 375px screens. * * The fix: Added @media (max-width: 400px) with tighter button spacing. */ import { chromium } from 'playwright'; const URL = "http://localhost:1999"; async function testMobileFabOverflow() { console.log('🎯 MOBILE FAB OVERFLOW TEST\n'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: true }); const testResults = []; try { // ======================================================================== // TEST 1: iPhone 13 mini (375px) - buttons don't overflow // ======================================================================== console.log("\n1️⃣ Testing iPhone 13 mini (375x812) - no overflow..."); const context1 = await browser.newContext({ viewport: { width: 375, height: 812 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true, }); const page1 = await context1.newPage(); // Add is-mobile-device class to simulate real mobile await page1.goto(URL); await page1.evaluate(() => document.body.classList.add('is-mobile-device')); await page1.waitForTimeout(1500); const iphone13Mini = await page1.evaluate(() => { const viewportWidth = window.innerWidth; const buttons = [ '.download-btn', '.print-friendly-btn', '.fixed-btn.contact-btn', '.color-theme-switcher', '.info-button', '.back-to-top' ]; let leftMost = Infinity; let rightMost = -Infinity; let visibleCount = 0; const buttonDetails = []; buttons.forEach(selector => { const btn = document.querySelector(selector); if (btn) { const style = window.getComputedStyle(btn); const rect = btn.getBoundingClientRect(); const isVisible = style.display !== 'none' && style.visibility !== 'hidden'; if (isVisible) { visibleCount++; leftMost = Math.min(leftMost, rect.left); rightMost = Math.max(rightMost, rect.right); buttonDetails.push({ selector, left: Math.round(rect.left), right: Math.round(rect.right), width: Math.round(rect.width) }); } } }); // Check for horizontal overflow const hasOverflow = rightMost > viewportWidth || leftMost < 0; const overflowAmount = Math.max(0, rightMost - viewportWidth, -leftMost); // Check document has no horizontal scroll const hasHorizontalScroll = document.documentElement.scrollWidth > viewportWidth; return { viewportWidth, leftMost: Math.round(leftMost), rightMost: Math.round(rightMost), visibleCount, hasOverflow, overflowAmount: Math.round(overflowAmount), hasHorizontalScroll, buttonDetails, passed: !hasOverflow && visibleCount === 6 }; }); console.log(` Viewport width: ${iphone13Mini.viewportWidth}px`); console.log(` Visible buttons: ${iphone13Mini.visibleCount}/6`); console.log(` Button range: ${iphone13Mini.leftMost}px - ${iphone13Mini.rightMost}px`); console.log(` Has overflow: ${iphone13Mini.hasOverflow ? '❌ YES (' + iphone13Mini.overflowAmount + 'px)' : '✅ NO'}`); console.log(` Horizontal scroll: ${iphone13Mini.hasHorizontalScroll ? '❌ YES' : '✅ NO'}`); const test1Passed = iphone13Mini.passed; console.log(` ${test1Passed ? '✅ PASS' : '❌ FAIL'} - iPhone 13 mini no overflow`); testResults.push({ test: 'iPhone 13 mini (375px) no overflow', passed: test1Passed }); await context1.close(); // ======================================================================== // TEST 2: iPhone SE (320px) - even smaller screen // ======================================================================== console.log("\n2️⃣ Testing iPhone SE (320x568) - no overflow..."); const context2 = await browser.newContext({ viewport: { width: 320, height: 568 }, deviceScaleFactor: 2, isMobile: true, hasTouch: true, }); const page2 = await context2.newPage(); await page2.goto(URL); await page2.evaluate(() => document.body.classList.add('is-mobile-device')); await page2.waitForTimeout(1500); const iphoneSE = await page2.evaluate(() => { const viewportWidth = window.innerWidth; const buttons = [ '.download-btn', '.print-friendly-btn', '.fixed-btn.contact-btn', '.color-theme-switcher', '.info-button', '.back-to-top' ]; let leftMost = Infinity; let rightMost = -Infinity; let visibleCount = 0; buttons.forEach(selector => { const btn = document.querySelector(selector); if (btn) { const style = window.getComputedStyle(btn); const rect = btn.getBoundingClientRect(); const isVisible = style.display !== 'none' && style.visibility !== 'hidden'; if (isVisible) { visibleCount++; leftMost = Math.min(leftMost, rect.left); rightMost = Math.max(rightMost, rect.right); } } }); const hasOverflow = rightMost > viewportWidth || leftMost < 0; return { viewportWidth, leftMost: Math.round(leftMost), rightMost: Math.round(rightMost), visibleCount, hasOverflow, passed: !hasOverflow && visibleCount === 6 }; }); console.log(` Viewport width: ${iphoneSE.viewportWidth}px`); console.log(` Visible buttons: ${iphoneSE.visibleCount}/6`); console.log(` Button range: ${iphoneSE.leftMost}px - ${iphoneSE.rightMost}px`); console.log(` Has overflow: ${iphoneSE.hasOverflow ? '❌ YES' : '✅ NO'}`); const test2Passed = iphoneSE.passed; console.log(` ${test2Passed ? '✅ PASS' : '❌ FAIL'} - iPhone SE no overflow`); testResults.push({ test: 'iPhone SE (320px) no overflow', passed: test2Passed }); await context2.close(); // ======================================================================== // TEST 3: Buttons are centered on small screens // ======================================================================== console.log("\n3️⃣ Testing buttons centered on small screens (375px)..."); const context3 = await browser.newContext({ viewport: { width: 375, height: 812 }, isMobile: true, hasTouch: true, }); const page3 = await context3.newPage(); await page3.goto(URL); await page3.evaluate(() => document.body.classList.add('is-mobile-device')); await page3.waitForTimeout(1500); const centered = await page3.evaluate(() => { const viewportWidth = window.innerWidth; const buttons = [ '.download-btn', '.print-friendly-btn', '.fixed-btn.contact-btn', '.color-theme-switcher', '.info-button', '.back-to-top' ]; let leftMost = Infinity; let rightMost = -Infinity; buttons.forEach(selector => { const btn = document.querySelector(selector); if (btn) { const style = window.getComputedStyle(btn); const rect = btn.getBoundingClientRect(); const isVisible = style.display !== 'none' && style.visibility !== 'hidden'; if (isVisible) { leftMost = Math.min(leftMost, rect.left); rightMost = Math.max(rightMost, rect.right); } } }); const groupWidth = rightMost - leftMost; const groupCenter = leftMost + (groupWidth / 2); const viewportCenter = viewportWidth / 2; const centerOffset = Math.abs(groupCenter - viewportCenter); // Allow 20px tolerance for centering const isCentered = centerOffset < 20; return { viewportWidth, viewportCenter, groupCenter: Math.round(groupCenter), centerOffset: Math.round(centerOffset), isCentered, passed: isCentered }; }); console.log(` Viewport center: ${centered.viewportCenter}px`); console.log(` Button group center: ${centered.groupCenter}px`); console.log(` Center offset: ${centered.centerOffset}px`); console.log(` Is centered: ${centered.isCentered ? '✅ YES' : '❌ NO'}`); const test3Passed = centered.passed; console.log(` ${test3Passed ? '✅ PASS' : '❌ FAIL'} - Buttons centered`); testResults.push({ test: 'Buttons centered on small screens', passed: test3Passed }); await context3.close(); // ======================================================================== // TEST 4: Button size reduced on very small screens // ======================================================================== console.log("\n4️⃣ Testing button size reduction on small screens (375px)..."); const context4 = await browser.newContext({ viewport: { width: 375, height: 812 }, isMobile: true, hasTouch: true, }); const page4 = await context4.newPage(); await page4.goto(URL); await page4.evaluate(() => document.body.classList.add('is-mobile-device')); await page4.waitForTimeout(1500); const buttonSize = await page4.evaluate(() => { const btn = document.querySelector('.download-btn'); if (!btn) return { passed: false, reason: 'Button not found' }; const rect = btn.getBoundingClientRect(); const width = Math.round(rect.width); const height = Math.round(rect.height); // On screens <= 400px, buttons should be 34px const expectedSize = 34; const tolerance = 2; // Allow 2px tolerance const correctSize = Math.abs(width - expectedSize) <= tolerance && Math.abs(height - expectedSize) <= tolerance; return { width, height, expectedSize, correctSize, passed: correctSize }; }); console.log(` Button size: ${buttonSize.width}x${buttonSize.height}px`); console.log(` Expected: ~${buttonSize.expectedSize}px`); console.log(` Correct size: ${buttonSize.correctSize ? '✅ YES' : '❌ NO'}`); const test4Passed = buttonSize.passed; console.log(` ${test4Passed ? '✅ PASS' : '❌ FAIL'} - Button size reduced`); testResults.push({ test: 'Button size reduced on small screens', passed: test4Passed }); await context4.close(); // ======================================================================== // TEST 5: Compare with larger screen (no size reduction) // ======================================================================== console.log("\n5️⃣ Testing normal button size on larger mobile (500px)..."); const context5 = await browser.newContext({ viewport: { width: 500, height: 800 }, isMobile: true, hasTouch: true, }); const page5 = await context5.newPage(); await page5.goto(URL); await page5.evaluate(() => document.body.classList.add('is-mobile-device')); await page5.waitForTimeout(1500); const largerScreen = await page5.evaluate(() => { const btn = document.querySelector('.download-btn'); if (!btn) return { passed: false, reason: 'Button not found' }; const rect = btn.getBoundingClientRect(); const width = Math.round(rect.width); // On screens > 400px, buttons should be larger (36-50px based on clamp) const isLarger = width > 34; return { width, isLarger, passed: isLarger }; }); console.log(` Button size: ${largerScreen.width}px`); console.log(` Is larger than 34px: ${largerScreen.isLarger ? '✅ YES' : '❌ NO'}`); const test5Passed = largerScreen.passed; console.log(` ${test5Passed ? '✅ PASS' : '❌ FAIL'} - Normal size on larger screens`); testResults.push({ test: 'Normal button size on larger screens (500px)', passed: test5Passed }); await context5.close(); // ======================================================================== // 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`); console.log("=".repeat(70) + "\n"); await browser.close(); if (failedTests === 0) { console.log("🎉 ALL MOBILE FAB OVERFLOW TESTS PASSED!"); process.exit(0); } else { console.log("⚠️ SOME TESTS FAILED - See details above"); process.exit(1); } } catch (error) { console.error('❌ Test failed:', error); await browser.close(); process.exit(1); } } await testMobileFabOverflow();