Files
cv-site/tests/mjs/7-mobile-responsive.test.mjs
T
juanatsap e9d650d152 fix: update domain from morenoyrubio.com to morenorub.io
Updated personal website domain across all configuration files:
- Changed website URL in CV data (English and Spanish)
- Updated robots.txt domain reference and sitemap location
- Updated all sitemap.xml URLs (English, Spanish, default, health check)
- Removed obsolete test files (styling comparison, shortcuts button report)

Domain change: morenoyrubio.com → morenorub.io
2025-11-17 13:40:05 +00:00

284 lines
11 KiB
JavaScript
Executable File

#!/usr/bin/env bun
/**
* MOBILE RESPONSIVE TEST
* =======================
* Tests mobile viewport rendering and interactions
* - Mobile viewport sizing (375px, 768px, 1024px)
* - Touch interactions
* - Mobile menu functionality
* - Responsive layout breakpoints
* - Text readability at small sizes
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
// Common mobile viewports
const VIEWPORTS = {
mobile: { width: 375, height: 667 }, // iPhone SE
tablet: { width: 768, height: 1024 }, // iPad
desktop: { width: 1920, height: 1080 } // Desktop baseline
};
async function testMobileResponsive() {
console.log('📱 MOBILE RESPONSIVE TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: false });
const errors = [];
const testResults = [];
// ========================================================================
// TEST 1: Mobile viewport (375px)
// ========================================================================
console.log("\n1️⃣ Testing Mobile Viewport (375px)...");
const mobilePage = await browser.newPage({ viewport: VIEWPORTS.mobile });
mobilePage.on('console', msg => {
if (msg.type() === 'error') {
errors.push(msg.text());
console.log(`❌ ERROR: ${msg.text()}`);
}
});
await mobilePage.goto(URL);
await mobilePage.waitForTimeout(2000);
const mobileTest = await mobilePage.evaluate(() => {
const paper = document.querySelector('.cv-paper');
const body = document.body;
const hamburger = document.querySelector('.hamburger-btn');
// Check for horizontal overflow
const hasHorizontalScroll = document.documentElement.scrollWidth > window.innerWidth;
// Check if text is readable (not too small)
const paragraphs = Array.from(document.querySelectorAll('p, li'));
const fontSizes = paragraphs.map(p => {
const size = parseFloat(window.getComputedStyle(p).fontSize);
return size;
});
const minFontSize = Math.min(...fontSizes);
// Check if hamburger menu exists (mobile navigation)
const hasHamburger = !!hamburger;
const hamburgerVisible = hasHamburger ?
window.getComputedStyle(hamburger).display !== 'none' : false;
return {
width: window.innerWidth,
height: window.innerHeight,
hasHorizontalScroll,
minFontSize,
hasHamburger,
hamburgerVisible,
paperWidth: paper ? paper.offsetWidth : 0
};
});
console.log(` Viewport: ${mobileTest.width}x${mobileTest.height}`);
console.log(` Horizontal scroll: ${mobileTest.hasHorizontalScroll ? '❌ YES (BAD)' : '✅ NO (GOOD)'}`);
console.log(` Min font size: ${mobileTest.minFontSize.toFixed(1)}px ${mobileTest.minFontSize >= 14 ? '✅' : '⚠️'}`);
console.log(` Hamburger menu: ${mobileTest.hasHamburger ? '✅ Present' : '⚠️ Not found'}`);
console.log(` Hamburger visible: ${mobileTest.hamburgerVisible ? '✅ YES' : '⚠️ NO'}`);
const mobileViewportPassed = !mobileTest.hasHorizontalScroll && mobileTest.minFontSize >= 14;
console.log(` ${mobileViewportPassed ? '✅ PASS' : '❌ FAIL'} - Mobile viewport`);
testResults.push({ test: 'Mobile Viewport (375px)', passed: mobileViewportPassed });
// ========================================================================
// TEST 2: Touch interactions (hamburger menu)
// ========================================================================
console.log("\n2️⃣ Testing Touch Interactions...");
const hamburger = await mobilePage.$('.hamburger-btn');
if (hamburger) {
// Tap hamburger to open menu
await hamburger.tap();
await mobilePage.waitForTimeout(500);
const menuTest = await mobilePage.evaluate(() => {
const menu = document.querySelector('.navigation-menu');
if (!menu) return { found: false };
const isOpen = menu.classList.contains('menu-open') ||
window.getComputedStyle(menu).display !== 'none';
return {
found: true,
isOpen,
isVisible: menu.offsetHeight > 0
};
});
console.log(` Menu found: ${menuTest.found ? '✅' : '❌'}`);
console.log(` Menu opens on tap: ${menuTest.isOpen ? '✅' : '❌'}`);
console.log(` ${menuTest.found && menuTest.isOpen ? '✅ PASS' : '❌ FAIL'} - Touch interactions`);
testResults.push({ test: 'Touch Interactions', passed: menuTest.found && menuTest.isOpen });
// Close menu
if (menuTest.isOpen) {
await hamburger.tap();
await mobilePage.waitForTimeout(300);
}
} else {
console.log(` ⚠️ SKIP - Hamburger menu not found`);
testResults.push({ test: 'Touch Interactions', passed: true });
}
// ========================================================================
// TEST 3: Tablet viewport (768px)
// ========================================================================
console.log("\n3️⃣ Testing Tablet Viewport (768px)...");
const tabletPage = await browser.newPage({ viewport: VIEWPORTS.tablet });
await tabletPage.goto(URL);
await tabletPage.waitForTimeout(2000);
const tabletTest = await tabletPage.evaluate(() => {
const hasHorizontalScroll = document.documentElement.scrollWidth > window.innerWidth;
const paper = document.querySelector('.cv-paper');
const actionBar = document.querySelector('.action-bar, .cv-controls');
return {
width: window.innerWidth,
hasHorizontalScroll,
paperWidth: paper ? paper.offsetWidth : 0,
hasActionBar: !!actionBar,
actionBarVisible: actionBar ? window.getComputedStyle(actionBar).display !== 'none' : false
};
});
console.log(` Viewport: ${tabletTest.width}px`);
console.log(` Horizontal scroll: ${tabletTest.hasHorizontalScroll ? '❌ YES' : '✅ NO'}`);
console.log(` Action bar: ${tabletTest.hasActionBar ? '✅ Present' : '⚠️ Not found'}`);
const tabletViewportPassed = !tabletTest.hasHorizontalScroll;
console.log(` ${tabletViewportPassed ? '✅ PASS' : '❌ FAIL'} - Tablet viewport`);
testResults.push({ test: 'Tablet Viewport (768px)', passed: tabletViewportPassed });
await tabletPage.close();
// ========================================================================
// TEST 4: Responsive breakpoints
// ========================================================================
console.log("\n4️⃣ Testing Responsive Breakpoints...");
const breakpoints = [
{ name: 'Small Mobile', width: 320 },
{ name: 'Mobile', width: 375 },
{ name: 'Large Mobile', width: 414 },
{ name: 'Small Tablet', width: 600 },
{ name: 'Tablet', width: 768 },
{ name: 'Large Tablet', width: 1024 },
{ name: 'Desktop', width: 1920 }
];
const breakpointResults = [];
for (const bp of breakpoints) {
await mobilePage.setViewportSize({ width: bp.width, height: 800 });
await mobilePage.waitForTimeout(200);
const result = await mobilePage.evaluate(() => {
return {
hasHorizontalScroll: document.documentElement.scrollWidth > window.innerWidth,
bodyWidth: document.body.offsetWidth
};
});
const passed = !result.hasHorizontalScroll;
breakpointResults.push({ name: bp.name, width: bp.width, passed });
console.log(` ${bp.name} (${bp.width}px): ${passed ? '✅' : '❌'}`);
}
const allBreakpointsPassed = breakpointResults.every(r => r.passed);
console.log(` ${allBreakpointsPassed ? '✅ PASS' : '❌ FAIL'} - All breakpoints`);
testResults.push({ test: 'Responsive Breakpoints', passed: allBreakpointsPassed });
// ========================================================================
// TEST 5: Mobile-specific features
// ========================================================================
console.log("\n5️⃣ Testing Mobile-Specific Features...");
await mobilePage.setViewportSize(VIEWPORTS.mobile);
await mobilePage.waitForTimeout(500);
const mobileFeatures = await mobilePage.evaluate(() => {
// Check viewport meta tag
const viewportMeta = document.querySelector('meta[name="viewport"]');
const hasViewportMeta = !!viewportMeta;
const viewportContent = viewportMeta?.getAttribute('content') || '';
// Check for touch-friendly button sizes (minimum 44x44px)
const buttons = Array.from(document.querySelectorAll('button, a[role="button"], .btn, input[type="checkbox"]'));
const buttonSizes = buttons.map(btn => {
const rect = btn.getBoundingClientRect();
return { width: rect.width, height: rect.height };
});
const tooSmallButtons = buttonSizes.filter(s => s.width < 44 || s.height < 44).length;
// Check for text overflow
const hasTextOverflow = Array.from(document.querySelectorAll('*')).some(el => {
return el.scrollWidth > el.clientWidth && window.getComputedStyle(el).overflow === 'visible';
});
return {
hasViewportMeta,
viewportContent,
totalButtons: buttons.length,
tooSmallButtons,
hasTextOverflow
};
});
console.log(` Viewport meta tag: ${mobileFeatures.hasViewportMeta ? '✅' : '❌'}`);
console.log(` Content: "${mobileFeatures.viewportContent}"`);
console.log(` Touch-friendly buttons: ${mobileFeatures.totalButtons - mobileFeatures.tooSmallButtons}/${mobileFeatures.totalButtons}`);
console.log(` Too small (<44px): ${mobileFeatures.tooSmallButtons} ${mobileFeatures.tooSmallButtons === 0 ? '✅' : '⚠️'}`);
console.log(` Text overflow: ${mobileFeatures.hasTextOverflow ? '⚠️ YES' : '✅ NO'}`);
const mobileFeaturesPass = mobileFeatures.hasViewportMeta && !mobileFeatures.hasTextOverflow;
console.log(` ${mobileFeaturesPass ? '✅ PASS' : '❌ FAIL'} - Mobile features`);
testResults.push({ test: 'Mobile Features', passed: mobileFeaturesPass });
await mobilePage.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 CONSOLE ERRORS");
} else {
console.log(`\n⚠️ ${errors.length} CONSOLE ERRORS`);
}
console.log("=".repeat(70) + "\n");
if (failedTests === 0) {
console.log("🎉 MOBILE RESPONSIVE VALIDATED!");
} 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
}
await testMobileResponsive();