← back to Dear Bubbe Nextjs

archived/tests/mobile-voice-test.js

292 lines

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

async function comprehensiveMobileTest() {
  const browser = await puppeteer.launch({ 
    headless: true,
    args: [
      '--no-sandbox', 
      '--disable-setuid-sandbox',
      '--use-fake-ui-for-media-stream',
      '--use-fake-device-for-media-stream',
      '--allow-running-insecure-content'
    ]
  });
  
  const results = {
    timestamp: new Date().toISOString(),
    tests: [],
    issues: [],
    recommendations: [],
    screenshots: []
  };

  try {
    // Test different mobile viewports
    const viewports = [
      { name: 'iPhone SE', width: 375, height: 667, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)' },
      { name: 'iPhone 12', width: 390, height: 844, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)' },
      { name: 'Samsung Galaxy S21', width: 360, height: 800, userAgent: 'Mozilla/5.0 (Linux; Android 12; SM-G991B)' },
      { name: 'iPad Mini', width: 768, height: 1024, userAgent: 'Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X)' }
    ];

    for (const viewport of viewports) {
      console.log(`\n📱 Testing ${viewport.name} (${viewport.width}x${viewport.height})`);
      
      const page = await browser.newPage();
      await page.setViewport({ width: viewport.width, height: viewport.height, isMobile: true });
      await page.setUserAgent(viewport.userAgent);
      
      await page.goto('http://45.61.58.125:3011', { waitUntil: 'networkidle0', timeout: 30000 });
      
      // Take screenshot
      const screenshotName = `mobile-${viewport.name.toLowerCase().replace(' ', '-')}.png`;
      await page.screenshot({ path: screenshotName, fullPage: true });
      results.screenshots.push(screenshotName);
      
      // Test 1: Basic Page Load
      const title = await page.title();
      results.tests.push({
        device: viewport.name,
        test: 'Page Load',
        pass: title.includes('Bubbe'),
        details: `Title: "${title}"`
      });
      
      // Test 2: Viewport handling
      const viewportMeta = await page.$('meta[name="viewport"]');
      const viewportContent = viewportMeta ? await page.evaluate(el => el.getAttribute('content'), viewportMeta) : null;
      const hasViewport = viewportContent && viewportContent.includes('width=device-width');
      results.tests.push({
        device: viewport.name,
        test: 'Viewport Meta Tag',
        pass: hasViewport,
        details: viewportContent || 'Not found'
      });
      
      // Test 3: Input field iOS auto-zoom prevention
      const textarea = await page.$('textarea');
      if (textarea) {
        const fontSize = await page.evaluate(el => window.getComputedStyle(el).fontSize, textarea);
        const fontSizeNum = parseInt(fontSize);
        const preventsZoom = fontSizeNum >= 16;
        results.tests.push({
          device: viewport.name,
          test: 'Input Font Size (iOS Auto-zoom)',
          pass: preventsZoom,
          details: `${fontSize} (needs 16px+)`
        });
        
        if (!preventsZoom && viewport.name.includes('iPhone')) {
          results.issues.push(`${viewport.name}: Input field will trigger iOS auto-zoom (${fontSize})`);
          results.recommendations.push(`${viewport.name}: Set input font-size to 16px minimum`);
        }
      }
      
      // Test 4: Touch target sizes
      const buttons = await page.$$('button');
      let touchTargetsPassed = 0;
      let touchTargetsTotal = 0;
      
      for (const button of buttons) {
        const box = await button.boundingBox();
        if (box && box.width > 0 && box.height > 0) {
          touchTargetsTotal++;
          const minSize = viewport.name.includes('Android') ? 48 : 44; // Android vs iOS standards
          if (box.width >= minSize && box.height >= minSize) {
            touchTargetsPassed++;
          }
        }
      }
      
      const touchTargetsPass = touchTargetsTotal === 0 || (touchTargetsPassed / touchTargetsTotal) >= 0.8;
      results.tests.push({
        device: viewport.name,
        test: 'Touch Target Sizes',
        pass: touchTargetsPass,
        details: `${touchTargetsPassed}/${touchTargetsTotal} buttons meet minimum size requirements`
      });
      
      // Test 5: Content fits viewport
      const body = await page.$('body');
      if (body) {
        const bodyBox = await body.boundingBox();
        const fitsViewport = bodyBox && bodyBox.width <= viewport.width + 20; // 20px tolerance
        results.tests.push({
          device: viewport.name,
          test: 'Content Fits Viewport',
          pass: fitsViewport,
          details: bodyBox ? `Content width: ${Math.round(bodyBox.width)}px, Viewport: ${viewport.width}px` : 'Could not measure'
        });
        
        if (!fitsViewport) {
          results.issues.push(`${viewport.name}: Content overflows viewport horizontally`);
          results.recommendations.push(`${viewport.name}: Check for fixed width elements or missing responsive classes`);
        }
      }
      
      // Test 6: Speech Recognition API Support (simulation)
      const speechRecognitionSupport = await page.evaluate(() => {
        return !!(window.SpeechRecognition || window.webkitSpeechRecognition);
      });
      
      results.tests.push({
        device: viewport.name,
        test: 'Speech Recognition Support',
        pass: speechRecognitionSupport,
        details: speechRecognitionSupport ? 'API available' : 'API not supported'
      });
      
      // Test 7: Web Audio API Support
      const webAudioSupport = await page.evaluate(() => {
        return !!(window.AudioContext || window.webkitAudioContext);
      });
      
      results.tests.push({
        device: viewport.name,
        test: 'Web Audio API Support',
        pass: webAudioSupport,
        details: webAudioSupport ? 'API available' : 'API not supported'
      });
      
      // Test 8: Media Devices API Support (for microphone)
      const mediaDevicesSupport = await page.evaluate(() => {
        return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
      });
      
      results.tests.push({
        device: viewport.name,
        test: 'Media Devices API Support',
        pass: mediaDevicesSupport,
        details: mediaDevicesSupport ? 'getUserMedia available' : 'getUserMedia not supported'
      });
      
      // Test 9: Touch event handling
      const touchSupport = await page.evaluate(() => {
        return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
      });
      
      results.tests.push({
        device: viewport.name,
        test: 'Touch Event Support',
        pass: touchSupport,
        details: touchSupport ? 'Touch events supported' : 'Touch events not supported'
      });
      
      // Test 10: Performance considerations
      const performanceMetrics = await page.evaluate(() => {
        const navigation = performance.getEntriesByType('navigation')[0];
        return {
          loadTime: Math.round(navigation.loadEventEnd - navigation.loadEventStart),
          domContentLoaded: Math.round(navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart),
          firstByte: Math.round(navigation.responseStart - navigation.requestStart)
        };
      });
      
      const fastLoad = performanceMetrics.loadTime < 3000;
      results.tests.push({
        device: viewport.name,
        test: 'Page Load Performance',
        pass: fastLoad,
        details: `Load: ${performanceMetrics.loadTime}ms, DOM: ${performanceMetrics.domContentLoaded}ms, TTFB: ${performanceMetrics.firstByte}ms`
      });
      
      if (!fastLoad) {
        results.issues.push(`${viewport.name}: Slow page load (${performanceMetrics.loadTime}ms)`);
        results.recommendations.push(`${viewport.name}: Optimize images, minify CSS/JS, enable compression`);
      }
      
      await page.close();
      console.log(`✅ ${viewport.name} testing complete`);
    }
    
  } catch (error) {
    console.error('❌ Testing error:', error);
    results.tests.push({
      device: 'Global',
      test: 'Mobile Testing Framework',
      pass: false,
      details: `Error: ${error.message}`
    });
  } finally {
    await browser.close();
  }
  
  // Generate comprehensive report
  console.log('\n📱 COMPREHENSIVE MOBILE TEST REPORT');
  console.log('====================================');
  
  // Group results by device
  const deviceResults = {};
  results.tests.forEach(test => {
    if (!deviceResults[test.device]) {
      deviceResults[test.device] = [];
    }
    deviceResults[test.device].push(test);
  });
  
  Object.keys(deviceResults).forEach(device => {
    const deviceTests = deviceResults[device];
    const passed = deviceTests.filter(t => t.pass).length;
    const total = deviceTests.length;
    const score = Math.round((passed / total) * 100);
    
    console.log(`\n📱 ${device}: ${score}% (${passed}/${total} tests passed)`);
    deviceTests.forEach(test => {
      const emoji = test.pass ? '✅' : '❌';
      console.log(`  ${emoji} ${test.test}: ${test.pass ? 'PASS' : 'FAIL'}`);
      if (test.details && !test.pass) {
        console.log(`     Details: ${test.details}`);
      }
    });
  });
  
  // Overall summary
  const totalTests = results.tests.length;
  const totalPassed = results.tests.filter(t => t.pass).length;
  const overallScore = Math.round((totalPassed / totalTests) * 100);
  
  console.log(`\n🎯 OVERALL SCORE: ${overallScore}% (${totalPassed}/${totalTests} tests passed)`);
  
  if (results.issues.length > 0) {
    console.log('\n🔴 ISSUES IDENTIFIED:');
    results.issues.forEach((issue, i) => console.log(`${i + 1}. ${issue}`));
  }
  
  if (results.recommendations.length > 0) {
    console.log('\n💡 RECOMMENDATIONS:');
    results.recommendations.forEach((rec, i) => console.log(`${i + 1}. ${rec}`));
  }
  
  if (results.screenshots.length > 0) {
    console.log('\n📸 SCREENSHOTS CAPTURED:');
    results.screenshots.forEach(screenshot => console.log(`- ${screenshot}`));
  }
  
  // Save detailed JSON report
  const reportData = {
    ...results,
    summary: {
      totalTests,
      totalPassed,
      overallScore,
      deviceScores: Object.keys(deviceResults).map(device => {
        const tests = deviceResults[device];
        const passed = tests.filter(t => t.pass).length;
        return {
          device,
          score: Math.round((passed / tests.length) * 100),
          passed,
          total: tests.length
        };
      })
    }
  };
  
  fs.writeFileSync('mobile-voice-test-report.json', JSON.stringify(reportData, null, 2));
  console.log('\n📄 Detailed report saved to mobile-voice-test-report.json');
  
  return reportData;
}

comprehensiveMobileTest().catch(console.error);