← back to Dear Bubbe Nextjs

archived/tests/mobile-test-suite.js

689 lines

/**
 * Dear Bubbe Mobile Experience Test Suite
 * Tests all mobile functionality including voice chat, onboarding, sharing, and UI/UX
 */

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

// Test configuration
const TEST_URL = 'http://45.61.58.125:3011';
const MOBILE_DEVICES = [
  { name: 'iPhone 12', viewport: { width: 390, height: 844 }, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1' },
  { name: 'Samsung Galaxy S21', viewport: { width: 360, height: 800 }, userAgent: 'Mozilla/5.0 (Linux; Android 11; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36' },
  { name: 'iPhone SE', viewport: { width: 375, height: 667 }, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1' }
];

// Test results storage
const testResults = {
  timestamp: new Date().toISOString(),
  tests: [],
  summary: { passed: 0, failed: 0, warnings: 0 }
};

// Utility function to log test results
function logTest(category, test, status, details = '') {
  const result = { category, test, status, details, timestamp: new Date().toISOString() };
  testResults.tests.push(result);
  
  if (status === 'PASS') {
    testResults.summary.passed++;
    console.log(`✅ ${category} - ${test}: PASSED`);
  } else if (status === 'FAIL') {
    testResults.summary.failed++;
    console.error(`❌ ${category} - ${test}: FAILED - ${details}`);
  } else if (status === 'WARNING') {
    testResults.summary.warnings++;
    console.warn(`⚠️ ${category} - ${test}: WARNING - ${details}`);
  }
  
  if (details) console.log(`   Details: ${details}`);
}

// Test 1: Voice Chat Flow
async function testVoiceChatFlow(page, device, browser) {
  console.log(`\n📱 Testing Voice Chat Flow on ${device.name}...`);
  
  try {
    // Check if voice chat auto-starts
    await new Promise(r => setTimeout(r, 3000)); // Give time for auto-start
    
    // Check for Bubbe's initial greeting
    const bubbeMessages = await page.$$eval('.bg-blue-50', elements => 
      elements.map(el => el.textContent)
    );
    
    if (bubbeMessages.length > 0) {
      logTest('Voice Chat', `Auto-start on ${device.name}`, 'PASS', 'Bubbe started talking automatically');
      
      // Check if the greeting includes invasive questions
      const hasInvasiveQuestion = bubbeMessages.some(msg => 
        msg.includes('?') && (
          msg.includes('married') || 
          msg.includes('job') || 
          msg.includes('weight') ||
          msg.includes('money')
        )
      );
      
      if (hasInvasiveQuestion) {
        logTest('Voice Chat', `Invasive questions on ${device.name}`, 'PASS', 'Bubbe asks appropriate invasive questions');
      } else {
        logTest('Voice Chat', `Invasive questions on ${device.name}`, 'WARNING', 'No invasive questions detected in greeting');
      }
    } else {
      logTest('Voice Chat', `Auto-start on ${device.name}`, 'WARNING', 'No automatic Bubbe greeting detected');
    }
    
    // Check for TALK button visibility
    const talkButton = await page.$x('//button[contains(text(), "TALK")');
    const isTalkButtonVisible = talkButton ? await talkButton.isVisible() : false;
    
    if (!isTalkButtonVisible && bubbeMessages.length > 0) {
      logTest('Voice Chat', `No TALK button after response on ${device.name}`, 'PASS', 'Auto-listening continues correctly');
    } else if (isTalkButtonVisible) {
      logTest('Voice Chat', `No TALK button after response on ${device.name}`, 'FAIL', 'TALK button appeared when it shouldn\'t');
    }
    
    // Test microphone permissions request
    const context = browser.defaultBrowserContext();
    await context.overridePermissions(TEST_URL, ['microphone']);
    logTest('Voice Chat', `Microphone permissions on ${device.name}`, 'PASS', 'Permissions handled correctly');
    
    // Check for continuous conversation indicators
    const listeningIndicator = await page.$('.animate-pulse');
    if (listeningIndicator) {
      logTest('Voice Chat', `Listening indicator on ${device.name}`, 'PASS', 'Visual feedback for listening state');
    }
    
  } catch (error) {
    logTest('Voice Chat', `General flow on ${device.name}`, 'FAIL', error.message);
  }
}

// Test 2: User Onboarding
async function testUserOnboarding(page, device) {
  console.log(`\n📝 Testing User Onboarding on ${device.name}...`);
  
  try {
    // Check for profile/signup prompt
    const profileButton = await page.$x('//button[contains(text(), "Profile")');
    const signupPrompt = await page.$('[class*="signup"]');
    
    if (profileButton || signupPrompt) {
      logTest('Onboarding', `Profile access on ${device.name}`, 'PASS', 'Profile/signup options available');
      
      // Test profile form if available
      if (profileButton) {
        await profileButton.click();
        await new Promise(r => setTimeout(r,(1000);
        
        // Check for form fields
        const nameInput = await page.$('input[placeholder*="name" i]');
        const relationshipDropdown = await page.$('select');
        const locationInput = await page.$('input[placeholder*="location" i]');
        
        if (nameInput && relationshipDropdown && locationInput) {
          logTest('Onboarding', `Form fields on ${device.name}`, 'PASS', 'All required fields present');
          
          // Test relationship dropdown doesn't cut off Done button
          const doneButton = await page.$x('//button[contains(text(), "Done")');
          if (doneButton) {
            const isVisible = await doneButton.isIntersectingViewport();
            if (isVisible) {
              logTest('Onboarding', `Done button visibility on ${device.name}`, 'PASS', 'Done button not cut off');
            } else {
              logTest('Onboarding', `Done button visibility on ${device.name}`, 'FAIL', 'Done button may be cut off by dropdown');
            }
          }
          
          // Test Enter key navigation on mobile keyboard
          await nameInput.type('Test User');
          await page.keyboard.press('Enter');
          const focusedElement = await page.evaluate(() => document.activeElement?.tagName);
          logTest('Onboarding', `Enter key navigation on ${device.name}`, 'PASS', `Focus moved to: ${focusedElement}`);
          
          // Test location field with dynamic Bubbe comments
          await locationInput.type('New York');
          await new Promise(r => setTimeout(r,(500);
          
          const bubbeComment = await page.$('.text-blue-600');
          if (bubbeComment) {
            const comment = await bubbeComment.textContent();
            logTest('Onboarding', `Dynamic location comments on ${device.name}`, 'PASS', `Bubbe says: "${comment}"`);
          }
        } else {
          logTest('Onboarding', `Form fields on ${device.name}`, 'FAIL', 'Missing required form fields');
        }
      }
    } else {
      logTest('Onboarding', `Profile access on ${device.name}`, 'WARNING', 'No profile/signup options visible');
    }
    
  } catch (error) {
    logTest('Onboarding', `General flow on ${device.name}`, 'FAIL', error.message);
  }
}

// Test 3: Share Functionality
async function testShareFunctionality(page, device) {
  console.log(`\n🔗 Testing Share Functionality on ${device.name}...`);
  
  try {
    // Look for share button
    const shareButton = await page.$('button[aria-label*="share" i]');
    const shareIcon = await page.$('svg[class*="share"]');
    
    if (shareButton || shareIcon) {
      logTest('Share', `Share button present on ${device.name}`, 'PASS', 'Share option available');
      
      const clickableElement = shareButton || (await shareIcon.$('..'));
      if (clickableElement) {
        await clickableElement.click();
        await new Promise(r => setTimeout(r,(1000);
        
        // Check for share modal
        const shareModal = await page.$('[role="dialog"]');
        if (shareModal) {
          logTest('Share', `Share modal opens on ${device.name}`, 'PASS', 'Modal displayed correctly');
          
          // Test platform buttons
          const platforms = ['WhatsApp', 'Instagram', 'TikTok', 'X', 'Facebook', 'SMS', 'Email'];
          const foundPlatforms = [];
          
          for (const platform of platforms) {
            const button = await page.$(`button:has-text("${platform}")`);
            if (button) {
              foundPlatforms.push(platform);
              
              // Check if button is clickable
              const isClickable = await button.isEnabled();
              if (!isClickable) {
                logTest('Share', `${platform} button on ${device.name}`, 'WARNING', 'Button present but not clickable');
              }
            }
          }
          
          if (foundPlatforms.length >= 5) {
            logTest('Share', `Platform buttons on ${device.name}`, 'PASS', `Found: ${foundPlatforms.join(', ')}`);
          } else {
            logTest('Share', `Platform buttons on ${device.name}`, 'WARNING', `Only found ${foundPlatforms.length} platforms`);
          }
          
          // Test copy link functionality
          const copyButton = await page.$x('//button[contains(text(), "Copy")');
          if (copyButton) {
            await copyButton.click();
            await new Promise(r => setTimeout(r,(500);
            
            const copiedText = await page.$('text="Copied!"');
            if (copiedText) {
              logTest('Share', `Copy link on ${device.name}`, 'PASS', 'Copy functionality works');
            } else {
              logTest('Share', `Copy link on ${device.name}`, 'WARNING', 'Copy confirmation not shown');
            }
          }
          
          // Close modal
          const closeButton = await page.$('button[aria-label*="close" i]');
          if (closeButton) {
            await closeButton.click();
          }
        } else {
          logTest('Share', `Share modal opens on ${device.name}`, 'FAIL', 'Modal did not open');
        }
      }
    } else {
      logTest('Share', `Share button present on ${device.name}`, 'FAIL', 'No share button found');
    }
    
  } catch (error) {
    logTest('Share', `General functionality on ${device.name}`, 'FAIL', error.message);
  }
}

// Test 4: Response Quality
async function testResponseQuality(page, device) {
  console.log(`\n💬 Testing Response Quality on ${device.name}...`);
  
  try {
    // Send a test message
    const input = await page.$('textarea, input[type="text"]');
    if (input) {
      await input.type('Tell me about the weather');
      await page.keyboard.press('Enter');
      
      // Wait for response
      await new Promise(r => setTimeout(r,(3000);
      
      // Get the latest Bubbe response
      const responses = await page.$$eval('.bg-blue-50', elements => 
        elements.map(el => el.textContent)
      );
      
      const latestResponse = responses[responses.length - 1];
      
      if (latestResponse) {
        // Check if response is complete (ends with punctuation)
        const endsWithPunctuation = /[.!?]$/.test(latestResponse.trim());
        if (endsWithPunctuation) {
          logTest('Response Quality', `Complete sentences on ${device.name}`, 'PASS', 'Response ends properly');
        } else {
          logTest('Response Quality', `Complete sentences on ${device.name}`, 'FAIL', 'Response appears cut off');
        }
        
        // Check if response ends with a question
        const endsWithQuestion = latestResponse.trim().endsWith('?');
        if (endsWithQuestion) {
          logTest('Response Quality', `Ends with question on ${device.name}`, 'PASS', 'Conversational flow maintained');
        } else {
          logTest('Response Quality', `Ends with question on ${device.name}`, 'WARNING', 'No question at end');
        }
        
        // Check response length (should be under 320 chars for mobile)
        if (latestResponse.length <= 320) {
          logTest('Response Quality', `Mobile-optimized length on ${device.name}`, 'PASS', `Length: ${latestResponse.length} chars`);
        } else {
          logTest('Response Quality', `Mobile-optimized length on ${device.name}`, 'WARNING', `Too long: ${latestResponse.length} chars`);
        }
        
        // Check for Yiddish terms
        const yiddishTerms = ['oy', 'vey', 'meshuga', 'schmuck', 'putz', 'bubbeleh', 'feh'];
        const hasYiddish = yiddishTerms.some(term => 
          latestResponse.toLowerCase().includes(term)
        );
        
        if (hasYiddish) {
          logTest('Response Quality', `Yiddish terms on ${device.name}`, 'PASS', 'Authentic Bubbe personality');
        } else {
          logTest('Response Quality', `Yiddish terms on ${device.name}`, 'WARNING', 'No Yiddish detected');
        }
        
        // Check for invasive questions
        const invasivePatterns = [
          'married', 'single', 'job', 'money', 'weight', 
          'kids', 'pregnant', 'dating', 'salary', 'rent'
        ];
        const hasInvasive = invasivePatterns.some(pattern => 
          latestResponse.toLowerCase().includes(pattern)
        );
        
        if (hasInvasive) {
          logTest('Response Quality', `Invasive questions on ${device.name}`, 'PASS', 'True to character');
        }
        
      } else {
        logTest('Response Quality', `Response generation on ${device.name}`, 'FAIL', 'No response received');
      }
    } else {
      logTest('Response Quality', `Input field on ${device.name}`, 'FAIL', 'Cannot find input field');
    }
    
  } catch (error) {
    logTest('Response Quality', `General quality on ${device.name}`, 'FAIL', error.message);
  }
}

// Test 5: General Mobile UI/UX
async function testGeneralMobileUX(page, device) {
  console.log(`\n📱 Testing General Mobile UI/UX on ${device.name}...`);
  
  try {
    // Check viewport meta tag
    const viewportMeta = await page.$eval('meta[name="viewport"]', el => el.content);
    if (viewportMeta.includes('width=device-width')) {
      logTest('Mobile UI', `Viewport meta on ${device.name}`, 'PASS', viewportMeta);
    } else {
      logTest('Mobile UI', `Viewport meta on ${device.name}`, 'FAIL', 'Missing proper viewport meta');
    }
    
    // Check for hydration errors
    const consoleErrors = [];
    page.on('console', msg => {
      if (msg.type() === 'error') {
        consoleErrors.push(msg.text());
      }
    });
    
    await page.reload();
    await new Promise(r => setTimeout(r,(3000);
    
    const hydrationErrors = consoleErrors.filter(err => 
      err.includes('hydration') || err.includes('Hydration')
    );
    
    if (hydrationErrors.length === 0) {
      logTest('Mobile UI', `No hydration errors on ${device.name}`, 'PASS', 'Clean console');
    } else {
      logTest('Mobile UI', `No hydration errors on ${device.name}`, 'FAIL', `Found ${hydrationErrors.length} errors`);
    }
    
    // Test landscape orientation
    await page.setViewport({ 
      width: device.viewport.height, 
      height: device.viewport.width 
    });
    await new Promise(r => setTimeout(r,(1000);
    
    const landscapeLayout = await page.$('.container');
    if (landscapeLayout) {
      logTest('Mobile UI', `Landscape orientation on ${device.name}`, 'PASS', 'Layout adapts to landscape');
    }
    
    // Return to portrait
    await page.setViewport(device.viewport);
    
    // Check for scroll functionality
    await page.evaluate(() => {
      window.scrollTo(0, document.body.scrollHeight);
    });
    
    const scrolled = await page.evaluate(() => window.pageYOffset > 0);
    if (scrolled) {
      logTest('Mobile UI', `Scroll functionality on ${device.name}`, 'PASS', 'Page scrolls properly');
    }
    
    // Test nudging system (wait for 30 seconds - abbreviated for testing)
    console.log('   Waiting 5 seconds to test nudge system...');
    await new Promise(r => setTimeout(r,(5000);
    
    const nudgeMessage = await page.$('text=/still there|hello|anyone|waiting/i');
    if (nudgeMessage) {
      logTest('Mobile UI', `Nudging system on ${device.name}`, 'PASS', 'Nudge messages appear');
    } else {
      logTest('Mobile UI', `Nudging system on ${device.name}`, 'WARNING', 'No nudge detected in 5 seconds');
    }
    
    // Check mobile-specific styles
    const fontSize = await page.$eval('input, textarea', el => 
      window.getComputedStyle(el).fontSize
    );
    
    if (fontSize === '16px') {
      logTest('Mobile UI', `Input font size on ${device.name}`, 'PASS', 'Prevents iOS zoom');
    } else {
      logTest('Mobile UI', `Input font size on ${device.name}`, 'WARNING', `Font size is ${fontSize}`);
    }
    
    // Check for touch-friendly button sizes
    const buttons = await page.$$('button');
    let tooSmall = 0;
    
    for (const button of buttons.slice(0, 5)) { // Check first 5 buttons
      const box = await button.boundingBox();
      if (box && (box.width < 44 || box.height < 44)) {
        tooSmall++;
      }
    }
    
    if (tooSmall === 0) {
      logTest('Mobile UI', `Touch targets on ${device.name}`, 'PASS', 'All buttons are touch-friendly');
    } else {
      logTest('Mobile UI', `Touch targets on ${device.name}`, 'WARNING', `${tooSmall} buttons may be too small`);
    }
    
  } catch (error) {
    logTest('Mobile UI', `General UX on ${device.name}`, 'FAIL', error.message);
  }
}

// Test 6: Known Issues Verification
async function testKnownIssuesFixes(page, device) {
  console.log(`\n🔧 Testing Known Issues Fixes on ${device.name}...`);
  
  try {
    // Send multiple messages to test response completeness
    const input = await page.$('textarea, input[type="text"]');
    if (input) {
      // Test 1: Response completeness
      await input.type('What do you think about my life choices?');
      await page.keyboard.press('Enter');
      await new Promise(r => setTimeout(r,(3000);
      
      const responses = await page.$$eval('.bg-blue-50', elements => 
        elements.map(el => el.textContent)
      );
      const lastResponse = responses[responses.length - 1];
      
      if (lastResponse && !lastResponse.endsWith('...')) {
        logTest('Known Issues', `Response not cut off on ${device.name}`, 'PASS', 'Complete response');
      } else {
        logTest('Known Issues', `Response not cut off on ${device.name}`, 'FAIL', 'Response may be truncated');
      }
      
      // Test 2: TALK button doesn't appear after Bubbe speaks
      await new Promise(r => setTimeout(r,(2000);
      const talkButton = await page.$x('//button[contains(text(), "TALK")');
      
      if (!talkButton || !(await talkButton.isVisible())) {
        logTest('Known Issues', `No TALK button after response on ${device.name}`, 'PASS', 'Continuous conversation works');
      } else {
        logTest('Known Issues', `No TALK button after response on ${device.name}`, 'FAIL', 'TALK button incorrectly shown');
      }
      
      // Test 3: Conversation continues automatically
      const listeningState = await page.$('.animate-pulse, [aria-label*="listening"]');
      if (listeningState) {
        logTest('Known Issues', `Auto-continue conversation on ${device.name}`, 'PASS', 'Listening continues');
      } else {
        logTest('Known Issues', `Auto-continue conversation on ${device.name}`, 'WARNING', 'May not be auto-listening');
      }
      
      // Test 4: Profile saves correctly
      const profileButton = await page.$x('//button[contains(text(), "Profile")');
      if (profileButton) {
        await profileButton.click();
        await new Promise(r => setTimeout(r,(1000);
        
        const nameInput = await page.$('input[placeholder*="name" i]');
        if (nameInput) {
          await nameInput.fill('');
          await nameInput.type('Test User Mobile');
          
          const saveButton = await page.$x('//button[contains(text(), "Save"), button:has-text("Done")');
          if (saveButton) {
            await saveButton.click();
            await new Promise(r => setTimeout(r,(1000);
            
            // Reopen profile to check if saved
            await profileButton.click();
            await new Promise(r => setTimeout(r,(1000);
            
            const savedValue = await nameInput.inputValue();
            if (savedValue === 'Test User Mobile') {
              logTest('Known Issues', `Profile saves on ${device.name}`, 'PASS', 'Data persists correctly');
            } else {
              logTest('Known Issues', `Profile saves on ${device.name}`, 'FAIL', 'Data not saved properly');
            }
          }
        }
      }
    }
    
  } catch (error) {
    logTest('Known Issues', `Fix verification on ${device.name}`, 'FAIL', error.message);
  }
}

// Main test runner
async function runMobileTests() {
  console.log('🚀 Starting Dear Bubbe Mobile Experience Tests');
  console.log('=' .repeat(60));
  console.log(`URL: ${TEST_URL}`);
  console.log(`Time: ${new Date().toLocaleString()}`);
  console.log('=' .repeat(60));
  
  for (const device of MOBILE_DEVICES) {
    console.log(`\n${'='.repeat(60)}`);
    console.log(`📱 Testing on ${device.name}`);
    console.log(`Viewport: ${device.viewport.width}x${device.viewport.height}`);
    console.log('=' .repeat(60));
    
    const browser = await puppeteer.launch({
      headless: true,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-web-security',
        '--disable-features=site-per-process',
        '--use-fake-ui-for-media-stream',
        '--use-fake-device-for-media-stream'
      ]
    });
    
    try {
      const page = await browser.newPage();
      
      // Set mobile viewport and user agent
      await page.setViewport(device.viewport);
      await page.setUserAgent(device.userAgent);
      
      // Enable console logging
      page.on('console', msg => {
        if (msg.type() === 'error') {
          console.log(`   [Console Error]: ${msg.text()}`);
        }
      });
      
      // Navigate to the app
      await page.goto(TEST_URL, { waitUntil: 'networkidle2', timeout: 30000 });
      
      // Run all test suites
      await testVoiceChatFlow(page, device, browser);
      await testUserOnboarding(page, device);
      await testShareFunctionality(page, device);
      await testResponseQuality(page, device);
      await testGeneralMobileUX(page, device);
      await testKnownIssuesFixes(page, device);
      
      // Take screenshot for review
      const screenshotName = `mobile-test-${device.name.replace(/\s+/g, '-')}-${Date.now()}.png`;
      await page.screenshot({ 
        path: `/root/Projects/dear-bubbe-nextjs/test-screenshots/${screenshotName}`,
        fullPage: true 
      });
      console.log(`   📸 Screenshot saved: ${screenshotName}`);
      
    } catch (error) {
      console.error(`❌ Fatal error testing ${device.name}: ${error.message}`);
      logTest('System', `Device testing on ${device.name}`, 'FAIL', error.message);
    } finally {
      await browser.close();
    }
  }
  
  // Generate test report
  console.log('\n' + '='.repeat(60));
  console.log('📊 TEST RESULTS SUMMARY');
  console.log('='.repeat(60));
  console.log(`✅ Passed: ${testResults.summary.passed}`);
  console.log(`❌ Failed: ${testResults.summary.failed}`);
  console.log(`⚠️  Warnings: ${testResults.summary.warnings}`);
  console.log('='.repeat(60));
  
  // Save detailed report
  const reportPath = `/root/Projects/dear-bubbe-nextjs/mobile-test-report-${Date.now()}.json`;
  await fs.writeFile(reportPath, JSON.stringify(testResults, null, 2));
  console.log(`\n📄 Detailed report saved to: ${reportPath}`);
  
  // Generate HTML report
  const htmlReport = generateHTMLReport(testResults);
  const htmlPath = `/root/Projects/dear-bubbe-nextjs/mobile-test-report-${Date.now()}.html`;
  await fs.writeFile(htmlPath, htmlReport);
  console.log(`📄 HTML report saved to: ${htmlPath}`);
  
  // Return exit code based on failures
  return testResults.summary.failed > 0 ? 1 : 0;
}

// Generate HTML report
function generateHTMLReport(results) {
  const html = `
<!DOCTYPE html>
<html>
<head>
  <title>Dear Bubbe Mobile Test Report</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 20px; background: #f5f5f5; }
    .header { background: #2563eb; color: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
    .summary { display: flex; gap: 20px; margin-bottom: 20px; }
    .summary-card { background: white; padding: 15px; border-radius: 8px; flex: 1; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    .passed { color: #10b981; font-size: 24px; font-weight: bold; }
    .failed { color: #ef4444; font-size: 24px; font-weight: bold; }
    .warning { color: #f59e0b; font-size: 24px; font-weight: bold; }
    .test-category { background: white; margin-bottom: 15px; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    .category-header { background: #f3f4f6; padding: 10px 15px; font-weight: bold; }
    .test-item { padding: 10px 15px; border-bottom: 1px solid #e5e7eb; display: flex; align-items: center; }
    .test-item:last-child { border-bottom: none; }
    .test-name { flex: 1; }
    .test-status { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; }
    .status-pass { background: #d1fae5; color: #065f46; }
    .status-fail { background: #fee2e2; color: #991b1b; }
    .status-warning { background: #fed7aa; color: #92400e; }
    .test-details { color: #6b7280; font-size: 12px; margin-top: 4px; }
  </style>
</head>
<body>
  <div class="header">
    <h1>Dear Bubbe Mobile Test Report</h1>
    <p>Generated: ${results.timestamp}</p>
  </div>
  
  <div class="summary">
    <div class="summary-card">
      <div class="passed">${results.summary.passed}</div>
      <div>Tests Passed</div>
    </div>
    <div class="summary-card">
      <div class="failed">${results.summary.failed}</div>
      <div>Tests Failed</div>
    </div>
    <div class="summary-card">
      <div class="warning">${results.summary.warnings}</div>
      <div>Warnings</div>
    </div>
  </div>
  
  ${Object.entries(groupTestsByCategory(results.tests)).map(([category, tests]) => `
    <div class="test-category">
      <div class="category-header">${category}</div>
      ${tests.map(test => `
        <div class="test-item">
          <div class="test-name">
            ${test.test}
            ${test.details ? `<div class="test-details">${test.details}</div>` : ''}
          </div>
          <div class="test-status status-${test.status.toLowerCase()}">${test.status}</div>
        </div>
      `).join('')}
    </div>
  `).join('')}
</body>
</html>`;
  
  return html;
}

// Group tests by category for report
function groupTestsByCategory(tests) {
  const grouped = {};
  tests.forEach(test => {
    if (!grouped[test.category]) {
      grouped[test.category] = [];
    }
    grouped[test.category].push(test);
  });
  return grouped;
}

// Run the tests
runMobileTests()
  .then(exitCode => {
    console.log('\n✅ Mobile testing completed!');
    process.exit(exitCode);
  })
  .catch(error => {
    console.error('❌ Test suite failed:', error);
    process.exit(1);
  });