e06f98d1d8
- Add media query for screens ≤400px (iPhone 13 mini = 375px) - Reduce button size to 34px and icon size to 16px - Recalculate 6-button positions with 6px gaps (234px total width) - Ensures buttons stay centered within narrow viewports
378 lines
13 KiB
JavaScript
378 lines
13 KiB
JavaScript
#!/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();
|