← back to Handbag Auth Nextjs

final-mobile-test.js

342 lines

const puppeteer = require('puppeteer');
const fs = require('fs').promises;

const SITE_URL = 'http://45.61.58.125:7991';

async function finalMobileTest() {
  console.log('='.repeat(60));
  console.log('MOBILE UI/UX TEST REPORT');
  console.log('='.repeat(60));
  console.log(`Site: ${SITE_URL}`);
  console.log(`Date: ${new Date().toISOString()}\n`);

  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
  });

  const page = await browser.newPage();

  // Test different mobile viewports
  const viewports = [
    { name: 'iPhone SE', width: 375, height: 667, device: 'Mobile' },
    { name: 'iPhone 12 Pro', width: 390, height: 844, device: 'Mobile' },
    { name: 'iPhone 14 Pro Max', width: 430, height: 932, device: 'Mobile' },
    { name: 'iPad', width: 768, height: 1024, device: 'Tablet' }
  ];

  const pages = [
    { name: 'Home', path: '/', description: 'Listings grid and search' },
    { name: 'Camera', path: '/camera', description: 'Handbag scanner' },
    { name: 'Price Tracker', path: '/price-tracker', description: 'Price tracking tool' }
  ];

  const issues = [];
  const recommendations = new Set();

  console.log('Testing each page across mobile viewports...\n');

  for (const viewport of viewports) {
    console.log(`\n${viewport.device} - ${viewport.name} (${viewport.width}x${viewport.height})`);
    console.log('-'.repeat(50));

    await page.setViewport({
      width: viewport.width,
      height: viewport.height,
      isMobile: true,
      hasTouch: true,
      deviceScaleFactor: 2
    });

    await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15');

    for (const pageInfo of pages) {
      try {
        console.log(`  Testing ${pageInfo.name}...`);

        await page.goto(`${SITE_URL}${pageInfo.path}`, {
          waitUntil: 'domcontentloaded',
          timeout: 10000
        });

        await page.waitForTimeout(2000);

        // Take screenshot
        const screenshotName = `final-${viewport.name.replace(/\s/g, '-')}-${pageInfo.name.toLowerCase()}.png`;
        await page.screenshot({
          path: `mobile-screenshots/${screenshotName}`,
          fullPage: false
        });

        // Run comprehensive tests
        const testResults = await page.evaluate(() => {
          const results = {
            passed: [],
            failed: [],
            warnings: []
          };

          // 1. Check viewport meta tag
          const viewportMeta = document.querySelector('meta[name="viewport"]');
          if (viewportMeta && viewportMeta.content.includes('width=device-width')) {
            results.passed.push('Viewport meta tag configured');
          } else {
            results.failed.push('Missing or incorrect viewport meta tag');
          }

          // 2. Check horizontal scrolling
          const hasHorizontalScroll = document.documentElement.scrollWidth > window.innerWidth;
          if (!hasHorizontalScroll) {
            results.passed.push('No horizontal overflow');
          } else {
            results.failed.push(`Horizontal scrolling detected (${document.documentElement.scrollWidth}px > ${window.innerWidth}px)`);
          }

          // 3. Check input font sizes (iOS zoom prevention)
          const inputs = document.querySelectorAll('input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]), textarea, select');
          let smallInputs = [];
          inputs.forEach(input => {
            const styles = window.getComputedStyle(input);
            const fontSize = parseInt(styles.fontSize);
            if (fontSize < 16) {
              const type = input.tagName.toLowerCase();
              const placeholder = input.getAttribute('placeholder') || '';
              smallInputs.push(`${type}: "${placeholder}" (${fontSize}px)`);
            }
          });

          if (smallInputs.length === 0 && inputs.length > 0) {
            results.passed.push(`All ${inputs.length} input fields have proper font size (≥16px)`);
          } else if (smallInputs.length > 0) {
            results.failed.push(`${smallInputs.length} input(s) with font < 16px will cause iOS zoom`);
            results.warnings.push(`Small inputs: ${smallInputs.join(', ')}`);
          }

          // 4. Check touch target sizes
          const touchTargets = document.querySelectorAll('button, a, [role="button"]');
          let smallTargets = 0;
          let tinyTargets = [];
          touchTargets.forEach(el => {
            const rect = el.getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0) {
              if (rect.width < 44 || rect.height < 44) {
                smallTargets++;
                if (rect.width < 30 || rect.height < 30) {
                  const text = el.textContent?.trim().substring(0, 20) || 'unnamed';
                  tinyTargets.push(`"${text}" (${Math.round(rect.width)}x${Math.round(rect.height)}px)`);
                }
              }
            }
          });

          if (smallTargets === 0 && touchTargets.length > 0) {
            results.passed.push('All touch targets meet 44px minimum');
          } else if (tinyTargets.length > 0) {
            results.failed.push(`${tinyTargets.length} critically small touch targets (<30px)`);
            results.warnings.push(`Tiny targets: ${tinyTargets.join(', ')}`);
          } else if (smallTargets > 0) {
            results.warnings.push(`${smallTargets} touch targets below 44px recommendation`);
          }

          // 5. Check mobile navigation
          const nav = document.querySelector('nav');
          const mobileMenuButton = document.querySelector('[aria-label*="menu" i], button:has(svg), [class*="burger"], [class*="menu-toggle"]');

          if (nav) {
            const navDisplay = window.getComputedStyle(nav).display;
            if (window.innerWidth < 768) {
              if (navDisplay === 'none' || navDisplay === 'hidden') {
                if (mobileMenuButton) {
                  results.passed.push('Mobile menu button present');
                } else {
                  results.warnings.push('Navigation hidden on mobile without menu button');
                }
              } else if (navDisplay === 'flex' || navDisplay === 'block') {
                results.passed.push('Navigation visible on mobile');
              }
            }
          }

          // 6. Check text readability
          const textElements = document.querySelectorAll('p, span:not(:empty), h1, h2, h3, h4, h5, h6, a, button');
          let unreadableText = 0;
          textElements.forEach(el => {
            const text = el.textContent?.trim();
            if (text && text.length > 0) {
              const styles = window.getComputedStyle(el);
              const fontSize = parseInt(styles.fontSize);
              if (fontSize > 0 && fontSize < 12) {
                unreadableText++;
              }
            }
          });

          if (unreadableText > 0) {
            results.warnings.push(`${unreadableText} text elements < 12px may be hard to read`);
          } else {
            results.passed.push('Text sizes are readable');
          }

          // 7. Check responsive images
          const images = document.querySelectorAll('img');
          let overflowingImages = 0;
          images.forEach(img => {
            const rect = img.getBoundingClientRect();
            if (rect.right > window.innerWidth + 1) {
              overflowingImages++;
            }
          });

          if (overflowingImages > 0) {
            results.failed.push(`${overflowingImages} image(s) overflow viewport`);
          } else if (images.length > 0) {
            results.passed.push('All images fit within viewport');
          }

          // 8. Check for responsive grid/flexbox
          const containers = document.querySelectorAll('[class*="grid"], [class*="flex"]');
          if (containers.length > 10) {
            results.passed.push(`Modern responsive layout (${containers.length} flex/grid containers)`);
          }

          return results;
        });

        // Process results
        if (testResults.failed.length > 0) {
          issues.push({
            viewport: viewport.name,
            page: pageInfo.name,
            failures: testResults.failed,
            warnings: testResults.warnings
          });

          console.log(`    ❌ ${testResults.failed.length} critical issue(s)`);
          testResults.failed.forEach(f => {
            console.log(`       - ${f}`);

            // Add recommendations based on failures
            if (f.includes('font < 16px')) {
              recommendations.add('Set all text inputs, textareas, and selects to font-size: 16px minimum');
            }
            if (f.includes('Horizontal scrolling')) {
              recommendations.add('Fix horizontal overflow by ensuring all content uses max-width: 100%');
            }
            if (f.includes('critically small touch targets')) {
              recommendations.add('Increase button and link sizes to minimum 44x44px for touch accessibility');
            }
          });
        } else {
          console.log(`    ✅ No critical issues`);
        }

        if (testResults.warnings.length > 0) {
          console.log(`    ⚠️  ${testResults.warnings.length} warning(s)`);
        }

        if (testResults.passed.length > 0) {
          console.log(`    ✓ ${testResults.passed.length} checks passed`);
        }

      } catch (error) {
        console.log(`    ❌ Error: ${error.message}`);
        issues.push({
          viewport: viewport.name,
          page: pageInfo.name,
          error: error.message
        });
      }
    }
  }

  await browser.close();

  // Final Report
  console.log('\n' + '='.repeat(60));
  console.log('SUMMARY');
  console.log('='.repeat(60));

  const totalIssues = issues.reduce((sum, issue) => {
    return sum + (issue.failures ? issue.failures.length : 1);
  }, 0);

  if (totalIssues === 0) {
    console.log('\n✅ EXCELLENT! The site is fully mobile-optimized!');
    console.log('   No critical issues found across all tested viewports.');
  } else if (totalIssues <= 2) {
    console.log('\n⚠️  GOOD - The site is mostly mobile-friendly with minor issues.');
  } else {
    console.log('\n❌ NEEDS IMPROVEMENT - Multiple mobile usability issues detected.');
  }

  console.log(`\nTotal Critical Issues: ${totalIssues}`);
  console.log(`Pages with Issues: ${issues.length}`);

  if (recommendations.size > 0) {
    console.log('\n' + '='.repeat(60));
    console.log('RECOMMENDATIONS');
    console.log('='.repeat(60));
    Array.from(recommendations).forEach((rec, i) => {
      console.log(`\n${i + 1}. ${rec}`);
    });
  }

  // Components needing fixes
  if (issues.length > 0) {
    console.log('\n' + '='.repeat(60));
    console.log('COMPONENTS NEEDING FIXES');
    console.log('='.repeat(60));

    const componentsToFix = new Set();

    issues.forEach(issue => {
      if (issue.failures) {
        issue.failures.forEach(failure => {
          if (failure.includes('input')) {
            componentsToFix.add('Input fields in forms');
          }
          if (failure.includes('touch targets')) {
            componentsToFix.add('Buttons and links (increase size)');
          }
          if (failure.includes('Horizontal')) {
            componentsToFix.add('Page layout containers');
          }
        });
      }
    });

    if (componentsToFix.size > 0) {
      console.log('\nFiles likely needing updates:');
      componentsToFix.forEach(comp => {
        console.log(`  - ${comp}`);
      });
    }
  }

  console.log('\n' + '='.repeat(60));
  console.log('MOBILE NAVIGATION');
  console.log('='.repeat(60));
  console.log('\n⚠️  Note: The site navigation is hidden on mobile devices');
  console.log('   but there is no visible hamburger menu button.');
  console.log('   Users can still navigate using the "Scan Handbag" button');
  console.log('   and links within pages, but adding a mobile menu would');
  console.log('   improve navigation accessibility.\n');

  // Save report
  const report = {
    timestamp: new Date().toISOString(),
    site: SITE_URL,
    totalIssues,
    issues,
    recommendations: Array.from(recommendations),
    mobileReadiness: totalIssues === 0 ? 'Excellent' : totalIssues <= 2 ? 'Good' : 'Needs Improvement'
  };

  await fs.writeFile('final-mobile-report.json', JSON.stringify(report, null, 2));

  console.log('📊 Full report saved to: final-mobile-report.json');
  console.log('📸 Screenshots saved to: mobile-screenshots/');
  console.log('\n✅ Mobile UI/UX testing complete!');
}

finalMobileTest().catch(console.error);