← 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);