#!/usr/bin/env node import { chromium } from 'playwright'; const VIEWPORTS = { desktop: { width: 1278, height: 800, name: 'Desktop (1278px)' }, portraitMobile: { width: 375, height: 667, name: 'Portrait Mobile (375ร—667)' }, landscapeMobile: { width: 667, height: 375, name: 'Landscape Mobile (667ร—375)' } }; async function testViewport(browser, viewport) { const context = await browser.newContext({ viewport, userAgent: viewport.width < 768 ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1' : undefined, hasTouch: viewport.width < 768 }); const page = await context.newPage(); await page.goto('http://localhost:1999/?lang=en&view=extended'); await page.waitForLoadState('networkidle'); const results = await page.evaluate(() => { // Page 1 - Left Sidebar const sidebarLeft = document.querySelector('.cv-sidebar-left'); const accordionLeft = sidebarLeft?.querySelector('.sidebar-accordion'); const accordionContentLeft = sidebarLeft?.querySelector('.sidebar-accordion-content'); // Page 2 - Right Sidebar (scroll to it first) document.querySelector('.page-2')?.scrollIntoView(); const sidebarRight = document.querySelector('.cv-sidebar-right'); const accordionRight = sidebarRight?.querySelector('.sidebar-accordion'); const accordionContentRight = sidebarRight?.querySelector('.sidebar-accordion-content'); // Buttons const downloadBtn = document.querySelector('.download-btn'); const printBtn = document.querySelector('.print-friendly-btn'); const infoBtn = document.querySelector('.info-button'); const backToTop = document.querySelector('.back-to-top'); const themeSwitcher = document.querySelector('.color-theme-switcher'); const backdrop = document.querySelector('.fixed-buttons-backdrop'); const sidebarLeftRect = sidebarLeft?.getBoundingClientRect(); const sidebarRightRect = sidebarRight?.getBoundingClientRect(); return { viewport: { width: window.innerWidth, height: window.innerHeight, isPortrait: window.innerHeight > window.innerWidth, isLandscape: window.innerWidth > window.innerHeight }, mediaQueries: { desktop: window.matchMedia('(min-width: 769px)').matches, mobile: window.matchMedia('(max-width: 768px)').matches, landscape: window.matchMedia('(max-width: 915px) and (orientation: landscape)').matches, }, leftSidebar: { exists: !!sidebarLeft, visible: sidebarLeft && window.getComputedStyle(sidebarLeft).display !== 'none', height: sidebarLeftRect ? Math.round(sidebarLeftRect.height) : 0, accordionOpen: accordionLeft?.hasAttribute('open'), contentHeight: accordionContentLeft ? Math.round(accordionContentLeft.getBoundingClientRect().height) : 0, }, rightSidebar: { exists: !!sidebarRight, visible: sidebarRight && window.getComputedStyle(sidebarRight).display !== 'none', height: sidebarRightRect ? Math.round(sidebarRightRect.height) : 0, accordionOpen: accordionRight?.hasAttribute('open'), contentHeight: accordionContentRight ? Math.round(accordionContentRight.getBoundingClientRect().height) : 0, }, buttons: { downloadBtn: { exists: !!downloadBtn, visible: downloadBtn && window.getComputedStyle(downloadBtn).display !== 'none', bottom: downloadBtn ? window.getComputedStyle(downloadBtn).bottom : 'N/A', zIndex: downloadBtn ? window.getComputedStyle(downloadBtn).zIndex : 'N/A', }, printBtn: { exists: !!printBtn, visible: printBtn && window.getComputedStyle(printBtn).display !== 'none', }, infoBtn: { exists: !!infoBtn, visible: infoBtn && window.getComputedStyle(infoBtn).display !== 'none', }, backToTop: { exists: !!backToTop, visible: backToTop && window.getComputedStyle(backToTop).display !== 'none', }, themeSwitcher: { exists: !!themeSwitcher, visible: themeSwitcher && window.getComputedStyle(themeSwitcher).display !== 'none', opacity: themeSwitcher ? window.getComputedStyle(themeSwitcher).opacity : 'N/A', }, backdrop: { exists: !!backdrop, visible: backdrop && window.getComputedStyle(backdrop).display !== 'none', height: backdrop ? window.getComputedStyle(backdrop).height : 'N/A', }, } }; }); await page.screenshot({ path: `tests/screenshots/comprehensive-${viewport.name.toLowerCase().replace(/[^a-z0-9]/g, '-')}.png`, fullPage: true }); await context.close(); return results; } (async () => { const browser = await chromium.launch({ headless: true }); console.log('๐Ÿงช Comprehensive Multi-Viewport Test\n'); console.log('Testing: Sidebars, Accordions, and Buttons\n'); const allResults = {}; const failures = []; for (const [key, viewport] of Object.entries(VIEWPORTS)) { console.log(`๐Ÿ“ฑ Testing: ${viewport.name}`); console.log('='.repeat(50)); const results = await testViewport(browser, viewport); allResults[key] = results; // Print results console.log(`\nViewport: ${results.viewport.width}ร—${results.viewport.height}`); console.log(`Orientation: ${results.viewport.isLandscape ? 'Landscape' : 'Portrait'}\n`); console.log('Left Sidebar:'); console.log(` โ€ข Exists: ${results.leftSidebar.exists ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Visible: ${results.leftSidebar.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Height: ${results.leftSidebar.height}px`); console.log(` โ€ข Accordion open: ${results.leftSidebar.accordionOpen ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Content height: ${results.leftSidebar.contentHeight}px\n`); console.log('Right Sidebar:'); console.log(` โ€ข Exists: ${results.rightSidebar.exists ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Visible: ${results.rightSidebar.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Height: ${results.rightSidebar.height}px`); console.log(` โ€ข Accordion open: ${results.rightSidebar.accordionOpen ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Content height: ${results.rightSidebar.contentHeight}px\n`); console.log('Buttons:'); console.log(` โ€ข Download button: ${results.buttons.downloadBtn.exists && results.buttons.downloadBtn.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Print button: ${results.buttons.printBtn.exists && results.buttons.printBtn.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Info button: ${results.buttons.infoBtn.exists && results.buttons.infoBtn.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Back to top: ${results.buttons.backToTop.exists && results.buttons.backToTop.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Theme switcher: ${results.buttons.themeSwitcher.exists && results.buttons.themeSwitcher.visible ? 'โœ…' : 'โŒ'}`); console.log(` โ€ข Backdrop: ${results.buttons.backdrop.exists && results.buttons.backdrop.visible ? 'โœ…' : 'โŒ'}\n`); // Validation rules const issues = []; // Desktop: Sidebars should be open with content if (results.mediaQueries.desktop) { if (!results.leftSidebar.accordionOpen) issues.push('Desktop: Left sidebar accordion should be open'); if (!results.rightSidebar.accordionOpen) issues.push('Desktop: Right sidebar accordion should be open'); if (results.leftSidebar.contentHeight < 100) issues.push('Desktop: Left sidebar has no content'); if (results.rightSidebar.contentHeight < 100) issues.push('Desktop: Right sidebar has no content'); } // Landscape mobile: Sidebars should be open if (results.mediaQueries.landscape) { if (!results.leftSidebar.accordionOpen) issues.push('Landscape: Left sidebar accordion should be open'); if (!results.rightSidebar.accordionOpen) issues.push('Landscape: Right sidebar accordion should be open'); if (results.leftSidebar.contentHeight < 100) issues.push('Landscape: Left sidebar has no content'); } // All viewports: Buttons should be visible if (!results.buttons.downloadBtn.visible) issues.push('Download button not visible'); if (!results.buttons.printBtn.visible) issues.push('Print button not visible'); if (!results.buttons.infoBtn.visible) issues.push('Info button not visible'); // Note: Back to top button may not be visible on page load (only shows after scrolling) // This is expected behavior, so we don't flag it as an issue if (!results.buttons.themeSwitcher.visible) issues.push('Theme switcher not visible'); // Mobile: Backdrop should be visible if (results.mediaQueries.mobile || results.mediaQueries.landscape) { if (!results.buttons.backdrop.visible) issues.push('Mobile: Button backdrop not visible'); } if (issues.length > 0) { console.log('โŒ ISSUES FOUND:'); issues.forEach(issue => { console.log(` - ${issue}`); failures.push(`${viewport.name}: ${issue}`); }); } else { console.log('โœ… All checks passed'); } console.log('\n'); } // Final summary console.log('='.repeat(50)); console.log('FINAL SUMMARY\n'); if (failures.length === 0) { console.log('โœ… ALL VIEWPORTS PASSED ALL CHECKS\n'); } else { console.log(`โŒ ${failures.length} ISSUE(S) FOUND:\n`); failures.forEach((failure, i) => { console.log(`${i + 1}. ${failure}`); }); console.log(''); } await browser.close(); process.exit(failures.length === 0 ? 0 : 1); })();