← back to Handbag Auth Nextjs
mobile-analysis.js
428 lines
const puppeteer = require('puppeteer');
const fs = require('fs').promises;
const SITE_URL = 'http://45.61.58.125:7991';
async function analyzeMobileUI() {
console.log('Mobile UI Analysis\n' + '='.repeat(50));
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
});
const results = {
home: { issues: [], warnings: [], good: [] },
camera: { issues: [], warnings: [], good: [] },
priceTracker: { issues: [], warnings: [], good: [] }
};
try {
const page = await browser.newPage();
// Mobile iPhone SE viewport (smallest common)
await page.setViewport({
width: 375,
height: 667,
isMobile: true,
hasTouch: true,
deviceScaleFactor: 2
});
// Set mobile user agent
await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1');
// Test Home Page
console.log('\n📱 Testing HOME page (375px width)...');
try {
await page.goto(SITE_URL, {
waitUntil: 'domcontentloaded',
timeout: 15000
});
const homeAnalysis = await page.evaluate(() => {
const analysis = { issues: [], warnings: [], good: [] };
// Check viewport meta tag
const viewportMeta = document.querySelector('meta[name="viewport"]');
if (viewportMeta) {
const content = viewportMeta.getAttribute('content');
if (content.includes('width=device-width')) {
analysis.good.push('✅ Proper viewport meta tag found');
}
} else {
analysis.issues.push('❌ Missing viewport meta tag');
}
// Check horizontal scrolling
if (document.documentElement.scrollWidth > window.innerWidth) {
analysis.issues.push(`❌ Horizontal scroll detected (${document.documentElement.scrollWidth}px > ${window.innerWidth}px)`);
} else {
analysis.good.push('✅ No horizontal overflow');
}
// Check navigation menu
const nav = document.querySelector('nav, [class*="nav"]');
if (nav) {
const navDisplay = window.getComputedStyle(nav).display;
const hasHamburger = document.querySelector('[class*="menu"], button svg, [aria-label*="menu"]');
if (hasHamburger) {
analysis.good.push('✅ Mobile menu button found');
} else if (navDisplay === 'none' || navDisplay === 'hidden') {
analysis.warnings.push('⚠️ Navigation hidden but no mobile menu button found');
}
}
// Check input elements
const inputs = document.querySelectorAll('input, textarea, select');
let inputIssues = 0;
inputs.forEach(input => {
const fontSize = parseInt(window.getComputedStyle(input).fontSize);
if (fontSize < 16) {
inputIssues++;
}
});
if (inputIssues > 0) {
analysis.issues.push(`❌ ${inputIssues} input(s) with font-size < 16px (causes iOS zoom)`);
} else if (inputs.length > 0) {
analysis.good.push('✅ All inputs have proper font size (≥16px)');
}
// Check touch targets
const clickables = document.querySelectorAll('button, a, [role="button"]');
let smallTargets = 0;
clickables.forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0 && (rect.width < 44 || rect.height < 44)) {
smallTargets++;
}
});
if (smallTargets > 0) {
analysis.warnings.push(`⚠️ ${smallTargets} touch targets < 44px (Apple HIG recommendation)`);
}
// Check grid responsiveness
const grids = document.querySelectorAll('[class*="grid"]');
grids.forEach(grid => {
const classes = grid.className;
if (classes.includes('sm:') || classes.includes('md:') || classes.includes('lg:')) {
analysis.good.push('✅ Responsive grid classes detected');
return;
}
});
// Check text readability
const textElements = document.querySelectorAll('p, span, h1, h2, h3, h4, h5, h6');
let tinyText = 0;
textElements.forEach(el => {
if (el.textContent && el.textContent.trim()) {
const fontSize = parseInt(window.getComputedStyle(el).fontSize);
if (fontSize > 0 && fontSize < 14) {
tinyText++;
}
}
});
if (tinyText > 0) {
analysis.warnings.push(`⚠️ ${tinyText} text elements < 14px`);
}
// Check images
const images = document.querySelectorAll('img');
let overflowImages = 0;
images.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.right > window.innerWidth) {
overflowImages++;
}
});
if (overflowImages > 0) {
analysis.issues.push(`❌ ${overflowImages} image(s) overflow viewport`);
}
// Check flexbox/grid usage
const containers = document.querySelectorAll('div, section, main');
let responsiveContainers = 0;
containers.forEach(container => {
const display = window.getComputedStyle(container).display;
if (display === 'flex' || display === 'grid') {
responsiveContainers++;
}
});
if (responsiveContainers > 5) {
analysis.good.push(`✅ Modern layout (${responsiveContainers} flex/grid containers)`);
}
return analysis;
});
results.home = homeAnalysis;
// Take screenshot
await page.screenshot({
path: 'mobile-screenshots/analysis-home-375px.png',
fullPage: false
});
} catch (error) {
results.home.issues.push(`❌ Failed to load: ${error.message}`);
}
// Test Camera Page
console.log('\n📱 Testing CAMERA page (375px width)...');
try {
await page.goto(`${SITE_URL}/camera`, {
waitUntil: 'domcontentloaded',
timeout: 15000
});
// Wait a bit for any dynamic content
await new Promise(resolve => setTimeout(resolve, 2000));
const cameraAnalysis = await page.evaluate(() => {
const analysis = { issues: [], warnings: [], good: [] };
// Check for camera-specific elements
const cameraElements = document.querySelectorAll('video, canvas, [class*="camera"], [id*="camera"]');
if (cameraElements.length > 0) {
analysis.good.push('✅ Camera elements found');
// Check if camera view is responsive
cameraElements.forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width > window.innerWidth) {
analysis.issues.push('❌ Camera view overflows viewport');
}
});
}
// Check horizontal scrolling
if (document.documentElement.scrollWidth > window.innerWidth) {
analysis.issues.push(`❌ Horizontal scroll detected`);
} else {
analysis.good.push('✅ No horizontal overflow');
}
// Check button sizes
const buttons = document.querySelectorAll('button');
let goodButtons = 0;
buttons.forEach(btn => {
const rect = btn.getBoundingClientRect();
if (rect.width >= 44 && rect.height >= 44) {
goodButtons++;
}
});
if (goodButtons === buttons.length && buttons.length > 0) {
analysis.good.push('✅ All buttons are touch-friendly (≥44px)');
}
return analysis;
});
results.camera = cameraAnalysis;
await page.screenshot({
path: 'mobile-screenshots/analysis-camera-375px.png',
fullPage: false
});
} catch (error) {
results.camera.issues.push(`❌ Failed to load: ${error.message}`);
}
// Test Price Tracker Page
console.log('\n📱 Testing PRICE TRACKER page (375px width)...');
try {
await page.goto(`${SITE_URL}/price-tracker`, {
waitUntil: 'domcontentloaded',
timeout: 15000
});
const priceTrackerAnalysis = await page.evaluate(() => {
const analysis = { issues: [], warnings: [], good: [] };
// Check horizontal scrolling
if (document.documentElement.scrollWidth > window.innerWidth) {
analysis.issues.push(`❌ Horizontal scroll detected`);
} else {
analysis.good.push('✅ No horizontal overflow');
}
// Check form elements
const forms = document.querySelectorAll('form');
if (forms.length > 0) {
analysis.good.push('✅ Forms found');
// Check form inputs
const formInputs = document.querySelectorAll('form input, form select, form textarea');
let properInputs = 0;
formInputs.forEach(input => {
const fontSize = parseInt(window.getComputedStyle(input).fontSize);
if (fontSize >= 16) {
properInputs++;
}
});
if (properInputs === formInputs.length && formInputs.length > 0) {
analysis.good.push('✅ All form inputs have proper font size');
}
}
// Check tables/lists for mobile friendliness
const tables = document.querySelectorAll('table');
if (tables.length > 0) {
tables.forEach(table => {
const rect = table.getBoundingClientRect();
if (rect.width > window.innerWidth) {
analysis.warnings.push('⚠️ Table may not be mobile-optimized');
}
});
}
// Check cards/list items
const cards = document.querySelectorAll('[class*="card"], [class*="list"] > *');
if (cards.length > 0) {
let overflowCards = 0;
cards.forEach(card => {
const rect = card.getBoundingClientRect();
if (rect.right > window.innerWidth) {
overflowCards++;
}
});
if (overflowCards === 0) {
analysis.good.push('✅ Cards/lists fit within viewport');
} else {
analysis.issues.push(`❌ ${overflowCards} card(s) overflow viewport`);
}
}
return analysis;
});
results.priceTracker = priceTrackerAnalysis;
await page.screenshot({
path: 'mobile-screenshots/analysis-price-tracker-375px.png',
fullPage: false
});
} catch (error) {
results.priceTracker.issues.push(`❌ Failed to load: ${error.message}`);
}
// Test tablet view
console.log('\n📱 Testing iPad view (768px width)...');
await page.setViewport({
width: 768,
height: 1024,
isMobile: true,
hasTouch: true
});
await page.goto(SITE_URL, {
waitUntil: 'domcontentloaded',
timeout: 15000
});
await page.screenshot({
path: 'mobile-screenshots/analysis-home-768px.png',
fullPage: false
});
} finally {
await browser.close();
}
// Print results
console.log('\n' + '='.repeat(50));
console.log('MOBILE UI/UX TEST RESULTS');
console.log('='.repeat(50));
const pages = [
{ name: 'HOME PAGE', data: results.home },
{ name: 'CAMERA PAGE', data: results.camera },
{ name: 'PRICE TRACKER PAGE', data: results.priceTracker }
];
let totalIssues = 0;
let totalWarnings = 0;
pages.forEach(page => {
console.log(`\n### ${page.name}`);
if (page.data.issues.length > 0) {
console.log('\nCritical Issues:');
page.data.issues.forEach(issue => console.log(` ${issue}`));
totalIssues += page.data.issues.length;
}
if (page.data.warnings.length > 0) {
console.log('\nWarnings:');
page.data.warnings.forEach(warning => console.log(` ${warning}`));
totalWarnings += page.data.warnings.length;
}
if (page.data.good.length > 0) {
console.log('\nWorking Well:');
page.data.good.forEach(good => console.log(` ${good}`));
}
});
// Overall assessment
console.log('\n' + '='.repeat(50));
console.log('OVERALL ASSESSMENT');
console.log('='.repeat(50));
if (totalIssues === 0 && totalWarnings < 3) {
console.log('\n✅ MOBILE-FRIENDLY: The site appears to be well-optimized for mobile devices!');
} else if (totalIssues === 0) {
console.log('\n⚠️ MOSTLY MOBILE-FRIENDLY: The site works on mobile but has some minor issues.');
} else {
console.log('\n❌ NEEDS IMPROVEMENT: The site has critical mobile usability issues.');
}
console.log(`\nTotal Critical Issues: ${totalIssues}`);
console.log(`Total Warnings: ${totalWarnings}`);
// Recommendations
console.log('\n' + '='.repeat(50));
console.log('RECOMMENDATIONS');
console.log('='.repeat(50));
const recommendations = [];
// Check for common issues across all results
const allIssues = [...results.home.issues, ...results.camera.issues, ...results.priceTracker.issues];
const allWarnings = [...results.home.warnings, ...results.camera.warnings, ...results.priceTracker.warnings];
if (allIssues.some(i => i.includes('font-size < 16px'))) {
recommendations.push('1. Set all input, textarea, and select elements to minimum 16px font-size to prevent iOS zoom');
}
if (allIssues.some(i => i.includes('Horizontal scroll'))) {
recommendations.push('2. Fix horizontal overflow issues - ensure all content fits within viewport width');
}
if (allWarnings.some(w => w.includes('touch targets < 44px'))) {
recommendations.push('3. Increase button/link sizes to at least 44x44px for better touch interaction');
}
if (allWarnings.some(w => w.includes('text elements < 14px'))) {
recommendations.push('4. Increase text size for better readability on mobile devices');
}
if (allIssues.some(i => i.includes('overflow viewport'))) {
recommendations.push('5. Ensure images and cards use responsive sizing (max-width: 100%)');
}
if (recommendations.length === 0) {
console.log('\n✅ No critical recommendations - the site is mobile-optimized!');
} else {
recommendations.forEach(rec => console.log(`\n${rec}`));
}
// Save detailed report
await fs.writeFile('mobile-analysis-report.json', JSON.stringify(results, null, 2));
console.log('\n📊 Detailed report saved to mobile-analysis-report.json');
console.log('📸 Screenshots saved in mobile-screenshots/');
}
analyzeMobileUI().catch(console.error);