Files
cv-site/tests/mjs/14-button-positioning.test.mjs
T

331 lines
13 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env bun
/**
* BUTTON POSITIONING & RESPONSIVE LAYOUT TEST
* ==============================================
* Tests button positioning across different viewport sizes
* - Desktop: Vertical layout on left side
* - Wide Mobile (483-900px): Horizontal layout at bottom + back-to-top on right
* - Narrow Mobile (<483px): Horizontal layout + back-to-top moved up on right
* - Visibility: Zoom button hidden in mobile
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
async function testButtonPositioning() {
console.log('🎯 BUTTON POSITIONING & RESPONSIVE TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: false });
const errors = [];
const testResults = [];
try {
// ========================================================================
// TEST 1: Desktop Layout (>900px) - Vertical on Left Side
// ========================================================================
console.log("\n1️⃣ Testing Desktop Layout (1920x1080)...");
const desktopPage = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
await desktopPage.goto(URL);
await desktopPage.waitForTimeout(1000);
const desktopLayout = await desktopPage.evaluate(() => {
const buttons = {
download: document.querySelector('.download-btn'),
print: document.querySelector('.print-friendly-btn'),
shortcuts: document.querySelector('.shortcuts-btn'),
info: document.querySelector('.info-button'),
backToTop: document.querySelector('.back-to-top'),
zoom: document.querySelector('.zoom-toggle-btn')
};
// Get computed styles
const getPosition = (el) => {
if (!el) return null;
const style = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
return {
display: style.display,
position: style.position,
left: style.left,
right: style.right,
bottom: style.bottom,
top: rect.top,
leftPx: rect.left,
visible: style.display !== 'none' && style.visibility !== 'hidden'
};
};
// Check if buttons are vertically stacked (left side)
const positions = {};
for (const [key, button] of Object.entries(buttons)) {
positions[key] = getPosition(button);
}
// Check LEFT side buttons (download, print, shortcuts, info)
const leftSideButtons = ['download', 'print', 'shortcuts', 'info'];
const leftButtonsOnLeft = leftSideButtons.every(key => {
const pos = positions[key];
return pos && pos.left !== 'auto' && parseFloat(pos.left) < 100; // Left side positioning
});
// Check back-to-top is on RIGHT side (as intended)
const backToTopOnRight = positions.backToTop &&
positions.backToTop.right !== 'auto' &&
parseFloat(positions.backToTop.right) < 100;
// Check vertical stacking (different bottom values for left side buttons)
const bottomValues = leftSideButtons.map(key => parseFloat(positions[key]?.bottom || '0'));
const isVertical = new Set(bottomValues).size === leftSideButtons.length; // All different
return {
positions,
leftButtonsOnLeft,
backToTopOnRight,
isVertical,
zoomVisible: positions.zoom?.visible || false
};
});
console.log(` Left side buttons on left: ${desktopLayout.leftButtonsOnLeft ? '✅' : '❌'}`);
console.log(` Back-to-top on right side: ${desktopLayout.backToTopOnRight ? '✅' : '❌'}`);
console.log(` Vertical layout (stacked): ${desktopLayout.isVertical ? '✅' : '❌'}`);
console.log(` Zoom button visible: ${desktopLayout.zoomVisible ? '✅' : '❌'}`);
const desktopPassed = desktopLayout.leftButtonsOnLeft &&
desktopLayout.backToTopOnRight &&
desktopLayout.isVertical &&
desktopLayout.zoomVisible;
console.log(` ${desktopPassed ? '✅ PASS' : '❌ FAIL'} - Desktop vertical layout`);
testResults.push({ test: 'Desktop Layout (>900px)', passed: desktopPassed });
await desktopPage.close();
// ========================================================================
// TEST 2: Wide Mobile Layout (483-900px) - Horizontal + Back-to-top Right
// ========================================================================
console.log("\n2️⃣ Testing Wide Mobile Layout (768x1024)...");
const wideMobilePage = await browser.newPage({ viewport: { width: 768, height: 1024 } });
await wideMobilePage.goto(URL);
await wideMobilePage.waitForTimeout(1000);
const wideMobileLayout = await wideMobilePage.evaluate(() => {
const buttons = {
download: document.querySelector('.download-btn'),
print: document.querySelector('.print-friendly-btn'),
shortcuts: document.querySelector('.shortcuts-btn'),
info: document.querySelector('.info-button'),
backToTop: document.querySelector('.back-to-top'),
zoom: document.querySelector('.zoom-toggle-btn')
};
const getPosition = (el) => {
if (!el) return null;
const style = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
return {
display: style.display,
left: style.left,
right: style.right,
bottom: style.bottom,
bottomPx: window.innerHeight - rect.bottom,
leftPx: rect.left,
rightPx: window.innerWidth - rect.right,
visible: style.display !== 'none' && style.visibility !== 'hidden'
};
};
const positions = {};
for (const [key, button] of Object.entries(buttons)) {
positions[key] = getPosition(button);
}
// Check horizontal layout (all at same bottom, different left positions)
const centerButtons = ['download', 'print', 'shortcuts', 'info'];
const bottomValues = centerButtons.map(key => parseFloat(positions[key]?.bottom || '0'));
const sameBottom = new Set(bottomValues).size === 1; // All same bottom
// Check back-to-top on right side
const backToTopRight = positions.backToTop &&
parseFloat(positions.backToTop.right) < 50 && // Right side
parseFloat(positions.backToTop.right) > 0;
// Check zoom hidden
const zoomHidden = !positions.zoom?.visible;
return {
positions,
sameBottom,
backToTopRight,
zoomHidden
};
});
console.log(` Buttons at same bottom (horizontal): ${wideMobileLayout.sameBottom ? '✅' : '❌'}`);
console.log(` Back-to-top on right side: ${wideMobileLayout.backToTopRight ? '✅' : '❌'}`);
console.log(` Zoom button hidden: ${wideMobileLayout.zoomHidden ? '✅' : '❌'}`);
const wideMobilePassed = wideMobileLayout.sameBottom &&
wideMobileLayout.backToTopRight &&
wideMobileLayout.zoomHidden;
console.log(` ${wideMobilePassed ? '✅ PASS' : '❌ FAIL'} - Wide mobile layout`);
testResults.push({ test: 'Wide Mobile Layout (483-900px)', passed: wideMobilePassed });
await wideMobilePage.close();
// ========================================================================
// TEST 3: Narrow Mobile Layout (<483px) - Back-to-top Moved Up
// ========================================================================
console.log("\n3️⃣ Testing Narrow Mobile Layout (375x667)...");
const narrowMobilePage = await browser.newPage({ viewport: { width: 375, height: 667 } });
await narrowMobilePage.goto(URL);
await narrowMobilePage.waitForTimeout(1000);
const narrowMobileLayout = await narrowMobilePage.evaluate(() => {
const buttons = {
download: document.querySelector('.download-btn'),
info: document.querySelector('.info-button'),
backToTop: document.querySelector('.back-to-top')
};
const getPosition = (el) => {
if (!el) return null;
const style = window.getComputedStyle(el);
const rect = el.getBoundingClientRect();
return {
left: style.left,
right: style.right,
bottom: style.bottom,
bottomPx: parseFloat(style.bottom)
};
};
const positions = {};
for (const [key, button] of Object.entries(buttons)) {
positions[key] = getPosition(button);
}
// Check back-to-top is higher than other buttons
const backToTopBottom = positions.backToTop?.bottomPx || 0;
const infoBottom = positions.info?.bottomPx || 0;
const backToTopHigher = backToTopBottom > infoBottom + 30; // At least 30px higher
// Check back-to-top still on right
const backToTopRight = positions.backToTop &&
parseFloat(positions.backToTop.right) < 50 &&
parseFloat(positions.backToTop.right) > 0;
return {
positions,
backToTopHigher,
backToTopRight,
backToTopBottomPx: backToTopBottom,
infoBottomPx: infoBottom
};
});
console.log(` Back-to-top higher than info button: ${narrowMobileLayout.backToTopHigher ? '✅' : '❌'}`);
console.log(` Back-to-top still on right side: ${narrowMobileLayout.backToTopRight ? '✅' : '❌'}`);
console.log(` Info button bottom: ${narrowMobileLayout.infoBottomPx}px`);
console.log(` Back-to-top bottom: ${narrowMobileLayout.backToTopBottomPx}px`);
const narrowMobilePassed = narrowMobileLayout.backToTopHigher &&
narrowMobileLayout.backToTopRight;
console.log(` ${narrowMobilePassed ? '✅ PASS' : '❌ FAIL'} - Narrow mobile layout`);
testResults.push({ test: 'Narrow Mobile Layout (<483px)', passed: narrowMobilePassed });
await narrowMobilePage.close();
// ========================================================================
// TEST 4: Button Visibility & Accessibility
// ========================================================================
console.log("\n4️⃣ Testing Button Visibility & Accessibility...");
const a11yPage = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
await a11yPage.goto(URL);
await a11yPage.waitForTimeout(1000);
const a11yCheck = await a11yPage.evaluate(() => {
const buttons = document.querySelectorAll('.download-btn, .print-friendly-btn, .shortcuts-btn, .info-button, .back-to-top, .zoom-toggle-btn');
const checks = {
allButtonsPresent: buttons.length >= 6,
allHaveAriaLabels: true,
allClickable: true,
buttonDetails: []
};
buttons.forEach(button => {
const ariaLabel = button.getAttribute('aria-label') || button.getAttribute('title');
const style = window.getComputedStyle(button);
const isVisible = style.display !== 'none' && style.visibility !== 'hidden';
const isClickable = style.pointerEvents !== 'none';
checks.buttonDetails.push({
class: button.className,
hasAriaLabel: !!ariaLabel,
visible: isVisible,
clickable: isClickable
});
// Only check clickability for visible buttons
if (isVisible && !isClickable) {
checks.allClickable = false;
}
});
return checks;
});
console.log(` All buttons present: ${a11yCheck.allButtonsPresent ? '✅' : '❌'} (${a11yCheck.buttonDetails.length} buttons)`);
console.log(` All clickable: ${a11yCheck.allClickable ? '✅' : '❌'}`);
const a11yPassed = a11yCheck.allButtonsPresent && a11yCheck.allClickable;
console.log(` ${a11yPassed ? '✅ PASS' : '❌ FAIL'} - Button visibility & accessibility`);
testResults.push({ test: 'Button Visibility & Accessibility', passed: a11yPassed });
await a11yPage.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`);
if (errors.length === 0) {
console.log("\n✅ NO ERRORS");
} else {
console.log(`\n⚠️ ${errors.length} ERRORS`);
}
console.log("=".repeat(70) + "\n");
if (failedTests === 0) {
console.log("🎉 ALL BUTTON POSITIONING TESTS PASSED!");
} 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
} catch (error) {
console.error('❌ Test failed:', error);
await browser.close();
}
}
await testButtonPositioning();