#!/usr/bin/env node /** * Test: Button and Icon Fluid Sizing * * Verifies that on mobile view (max-width: 900px): * - Buttons scale fluidly from 36px (at 380px viewport) to 50px (at 900px viewport) * - Icons scale proportionally from 18px to 24px * - Icons properly override HTML width/height attributes */ import { chromium } from 'playwright'; const TEST_URL = 'http://localhost:1999'; // Use height > width to ensure portrait orientation (avoid landscape breakpoint) const VIEWPORT_HEIGHT = 1000; // Expected sizes at different viewport widths based on CSS formulas: // Buttons: clamp(36px, calc(2.7vw + 25.7px), 50px) // Icons: clamp(18px, calc(1.15vw + 13.6px), 24px) function expectedSizes(viewportWidth) { const btnCalc = 0.027 * viewportWidth + 25.7; const iconCalc = 0.0115 * viewportWidth + 13.6; return { button: Math.min(50, Math.max(36, btnCalc)), icon: Math.min(24, Math.max(18, iconCalc)) }; } async function testButtonIconFluidSizing() { console.log('๐Ÿงช Testing Button and Icon Fluid Sizing'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 900, height: VIEWPORT_HEIGHT }, deviceScaleFactor: 1, }); 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(500); const viewportWidths = [900, 700, 500, 380]; const buttons = [ { selector: '.cmd-k-btn', name: 'CmdK' }, { selector: '.download-btn', name: 'Download' }, { selector: '.print-friendly-btn', name: 'Print' }, { selector: '.fixed-btn.contact-btn', name: 'Contact' }, { selector: '.shortcuts-btn', name: 'Shortcuts' }, { selector: '.color-theme-switcher', name: 'Theme' }, { selector: '.info-button', name: 'Info' }, { selector: '.back-to-top', name: 'BackToTop' }, ]; let allTestsPassed = true; const tolerance = 4; // Allow 4px tolerance for rounding for (const width of viewportWidths) { await page.setViewportSize({ width, height: VIEWPORT_HEIGHT }); await page.waitForTimeout(300); const expected = expectedSizes(width); console.log(`\n๐Ÿ“ Viewport: ${width}px (expected button: ~${Math.round(expected.button)}px, icon: ~${Math.round(expected.icon)}px)`); console.log('-'.repeat(70)); for (const btn of buttons) { try { const buttonElement = await page.$(btn.selector); if (!buttonElement) { // Some buttons may be hidden on certain viewports continue; } const isVisible = await buttonElement.isVisible(); if (!isVisible) { continue; } // Get button bounding box const btnBox = await buttonElement.boundingBox(); if (!btnBox) continue; const btnWidth = Math.round(btnBox.width); const btnHeight = Math.round(btnBox.height); // Get icon inside button const iconElement = await buttonElement.$('iconify-icon'); let iconWidth = 0; let iconHeight = 0; if (iconElement) { const iconBox = await iconElement.boundingBox(); if (iconBox) { iconWidth = Math.round(iconBox.width); iconHeight = Math.round(iconBox.height); } } // Check if sizes are within tolerance of expected const btnOk = Math.abs(btnWidth - expected.button) <= tolerance; const iconOk = iconWidth === 0 || Math.abs(iconWidth - expected.icon) <= tolerance; const status = btnOk && iconOk ? 'โœ…' : 'โŒ'; if (btnOk && iconOk) { console.log(`${status} ${btn.name}: button=${btnWidth}x${btnHeight}, icon=${iconWidth}x${iconHeight}`); } else { console.log(`${status} ${btn.name}: button=${btnWidth}x${btnHeight} (expected ~${Math.round(expected.button)}), icon=${iconWidth}x${iconHeight} (expected ~${Math.round(expected.icon)})`); allTestsPassed = false; } } catch (error) { // Button may not exist } } } // Test landscape orientation (width > height) console.log('\n\n๐Ÿ“ LANDSCAPE ORIENTATION TESTS'); console.log('='.repeat(70)); // Landscape viewports: width > height triggers landscape media query // Note: At 400x400, width == height so CSS treats it as portrait, not landscape const landscapeViewports = [ { width: 800, height: 400 }, // Clear landscape { width: 600, height: 350 }, // Clear landscape { width: 500, height: 300 }, // Clear landscape ]; // Landscape formula: clamp(32px, calc(2.2vw + 19.6px), 40px) for buttons // clamp(16px, calc(1.1vw + 9.8px), 20px) for icons function expectedLandscapeSizes(viewportWidth) { const btnCalc = 0.022 * viewportWidth + 19.6; const iconCalc = 0.011 * viewportWidth + 9.8; return { button: Math.min(40, Math.max(32, btnCalc)), icon: Math.min(20, Math.max(16, iconCalc)) }; } for (const viewport of landscapeViewports) { await page.setViewportSize({ width: viewport.width, height: viewport.height }); await page.waitForTimeout(300); const expected = expectedLandscapeSizes(viewport.width); console.log(`\n๐Ÿ“ Landscape ${viewport.width}x${viewport.height} (expected button: ~${Math.round(expected.button)}px, icon: ~${Math.round(expected.icon)}px)`); console.log('-'.repeat(70)); for (const btn of buttons) { try { const buttonElement = await page.$(btn.selector); if (!buttonElement) continue; const isVisible = await buttonElement.isVisible(); if (!isVisible) continue; const btnBox = await buttonElement.boundingBox(); if (!btnBox) continue; const btnWidth = Math.round(btnBox.width); const btnHeight = Math.round(btnBox.height); const iconElement = await buttonElement.$('iconify-icon'); let iconWidth = 0; if (iconElement) { const iconBox = await iconElement.boundingBox(); if (iconBox) iconWidth = Math.round(iconBox.width); } const btnOk = Math.abs(btnWidth - expected.button) <= tolerance; const iconOk = iconWidth === 0 || Math.abs(iconWidth - expected.icon) <= tolerance; const status = btnOk && iconOk ? 'โœ…' : 'โŒ'; if (btnOk && iconOk) { console.log(`${status} ${btn.name}: button=${btnWidth}x${btnHeight}, icon=${iconWidth}`); } else { console.log(`${status} ${btn.name}: button=${btnWidth}x${btnHeight} (expected ~${Math.round(expected.button)}), icon=${iconWidth} (expected ~${Math.round(expected.icon)})`); allTestsPassed = false; } } catch (error) {} } } console.log('\n' + '='.repeat(70)); if (allTestsPassed) { console.log('\nโœ… ALL TESTS PASSED - Button and icon fluid sizing working correctly!'); console.log(' โ€ข Portrait: Buttons 36-50px, Icons 18-24px'); console.log(' โ€ข Landscape: Buttons 32-40px, Icons 16-20px'); } 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); } } testButtonIconFluidSizing();