← back to Wine Finder Next
test-mobile.js
197 lines
const { chromium, devices } = require('playwright');
async function testWineFinderMobile() {
const browser = await chromium.launch({ headless: true });
const iPhone = devices['iPhone 13 Pro'];
const context = await browser.newContext({
...iPhone,
locale: 'en-US',
timezoneId: 'America/New_York',
});
const page = await context.newPage();
const results = {
tests: [],
passed: 0,
failed: 0,
timestamp: new Date().toISOString()
};
console.log('\n🧪 WINE FINDER NEXT.JS - MOBILE TEST SUITE\n');
console.log('Device: iPhone 13 Pro (390x844)');
console.log('URL: http://45.61.58.125:7201\n');
console.log('═'.repeat(60));
try {
// Test 1: Homepage loads
console.log('\n✓ Test 1: Homepage Loading...');
await page.goto('http://45.61.58.125:7201', { waitUntil: 'networkidle', timeout: 30000 });
const title = await page.title();
const test1 = title.includes('Red Thunder Wine Tracker');
results.tests.push({ name: 'Homepage loads', passed: test1, details: `Title: ${title}` });
if (test1) results.passed++; else results.failed++;
console.log(test1 ? ' ✅ PASSED - Title correct' : ' ❌ FAILED - Title incorrect');
// Test 2: Mobile viewport
console.log('\n✓ Test 2: Mobile Viewport...');
const viewport = page.viewportSize();
const test2 = viewport.width === 390 && viewport.height === 844;
results.tests.push({ name: 'Mobile viewport', passed: test2, details: `${viewport.width}x${viewport.height}` });
if (test2) results.passed++; else results.failed++;
console.log(test2 ? ` ✅ PASSED - ${viewport.width}x${viewport.height}` : ' ❌ FAILED - Viewport incorrect');
// Test 3: Hero section visible
console.log('\n✓ Test 3: Hero Section Visibility...');
const heroVisible = await page.locator('h1:has-text("Red Thunder Wine Tracker")').isVisible();
const test3 = heroVisible;
results.tests.push({ name: 'Hero section visible', passed: test3 });
if (test3) results.passed++; else results.failed++;
console.log(test3 ? ' ✅ PASSED - Hero visible' : ' ❌ FAILED - Hero not visible');
// Test 4: Search bar renders
console.log('\n✓ Test 4: Search Bar...');
const searchInput = await page.locator('input[placeholder*="Search for wines"]').isVisible();
const test4 = searchInput;
results.tests.push({ name: 'Search bar renders', passed: test4 });
if (test4) results.passed++; else results.failed++;
console.log(test4 ? ' ✅ PASSED - Search bar visible' : ' ❌ FAILED - Search bar missing');
// Test 5: White text in search (mobile fix)
console.log('\n✓ Test 5: Search Text Color...');
const searchColor = await page.locator('input[placeholder*="Search for wines"]').evaluate(el => {
return window.getComputedStyle(el).color;
});
const test5 = searchColor.includes('255, 255, 255') || searchColor.includes('rgb(255, 255, 255)');
results.tests.push({ name: 'Search text is white', passed: test5, details: `Color: ${searchColor}` });
if (test5) results.passed++; else results.failed++;
console.log(test5 ? ' ✅ PASSED - Text is white' : ` ⚠️ WARNING - Text color: ${searchColor}`);
// Test 6: Wine cards load
console.log('\n✓ Test 6: Wine Cards Loading...');
await page.waitForTimeout(3000); // Wait for API
const wineCards = await page.locator('.wine-card').count();
const test6 = wineCards > 0;
results.tests.push({ name: 'Wine cards load', passed: test6, details: `${wineCards} cards found` });
if (test6) results.passed++; else results.failed++;
console.log(test6 ? ` ✅ PASSED - ${wineCards} wine cards loaded` : ' ❌ FAILED - No cards found');
// Test 7: Mobile responsive grid
console.log('\n✓ Test 7: Responsive Grid...');
const gridCols = await page.locator('.grid').first().evaluate(el => {
return window.getComputedStyle(el).gridTemplateColumns;
});
const test7 = gridCols.split(' ').length <= 2; // Should be 1-2 columns on mobile
results.tests.push({ name: 'Mobile responsive grid', passed: test7, details: `Columns: ${gridCols}` });
if (test7) results.passed++; else results.failed++;
console.log(test7 ? ' ✅ PASSED - Grid is mobile-optimized' : ' ❌ FAILED - Too many columns');
// Test 8: Touch interactions
console.log('\n✓ Test 8: Touch Interactions...');
if (wineCards > 0) {
const firstCard = page.locator('.wine-card').first();
await firstCard.scrollIntoViewIfNeeded();
// Try to click price history button
const priceButton = firstCard.locator('button:has-text("Show Price History")');
const buttonVisible = await priceButton.isVisible();
if (buttonVisible) {
await priceButton.click();
await page.waitForTimeout(2000);
const chartVisible = await firstCard.locator('.animate-fadeIn').isVisible();
const test8 = chartVisible;
results.tests.push({ name: 'Touch interactions work', passed: test8, details: 'Price chart opens on tap' });
if (test8) results.passed++; else results.failed++;
console.log(test8 ? ' ✅ PASSED - Chart opens on tap' : ' ❌ FAILED - Chart didnt open');
} else {
results.tests.push({ name: 'Touch interactions work', passed: false, details: 'Button not found' });
results.failed++;
console.log(' ❌ FAILED - Price history button not found');
}
}
// Test 9: Font size prevents iOS zoom
console.log('\n✓ Test 9: iOS Zoom Prevention...');
const inputFontSize = await page.locator('input[placeholder*="Search"]').evaluate(el => {
return parseInt(window.getComputedStyle(el).fontSize);
});
const test9 = inputFontSize >= 16;
results.tests.push({ name: 'iOS zoom prevention', passed: test9, details: `Font size: ${inputFontSize}px` });
if (test9) results.passed++; else results.failed++;
console.log(test9 ? ` ✅ PASSED - ${inputFontSize}px (prevents zoom)` : ` ❌ FAILED - ${inputFontSize}px (too small)`);
// Test 10: Wine details display
console.log('\n✓ Test 10: Wine Details Display...');
if (wineCards > 0) {
const firstCard = page.locator('.wine-card').first();
const hasWinery = await firstCard.locator('text=/🏛️/').isVisible();
const hasRegion = await firstCard.locator('text=/📍/').isVisible();
const hasPrice = await firstCard.locator('text=/\\$/').first().isVisible();
const test10 = hasWinery || hasRegion || hasPrice;
results.tests.push({
name: 'Wine details display',
passed: test10,
details: `Winery: ${hasWinery}, Region: ${hasRegion}, Price: ${hasPrice}`
});
if (test10) results.passed++; else results.failed++;
console.log(test10 ? ' ✅ PASSED - Details visible' : ' ❌ FAILED - Details missing');
}
// Test 11: Performance check
console.log('\n✓ Test 11: Performance...');
const performanceMetrics = await page.evaluate(() => JSON.stringify(window.performance.timing));
const timing = JSON.parse(performanceMetrics);
const loadTime = timing.loadEventEnd - timing.navigationStart;
const test11 = loadTime < 5000;
results.tests.push({ name: 'Page load performance', passed: test11, details: `${loadTime}ms` });
if (test11) results.passed++; else results.failed++;
console.log(test11 ? ` ✅ PASSED - ${loadTime}ms` : ` ⚠️ SLOW - ${loadTime}ms`);
// Test 12: Screenshot
console.log('\n✓ Test 12: Taking Screenshots...');
await page.screenshot({ path: '/tmp/wine-finder-mobile.png', fullPage: true });
console.log(' 📸 Screenshot saved: /tmp/wine-finder-mobile.png');
// Summary
console.log('\n' + '═'.repeat(60));
console.log('\n📊 TEST SUMMARY\n');
console.log(`Total Tests: ${results.tests.length}`);
console.log(`✅ Passed: ${results.passed}`);
console.log(`❌ Failed: ${results.failed}`);
console.log(`Success Rate: ${((results.passed / results.tests.length) * 100).toFixed(1)}%`);
console.log('\n' + '═'.repeat(60) + '\n');
// Detailed results
if (results.failed > 0) {
console.log('❌ FAILED TESTS:\n');
results.tests.filter(t => !t.passed).forEach(t => {
console.log(` • ${t.name}`);
if (t.details) console.log(` ${t.details}`);
});
console.log('');
}
console.log('✅ PASSED TESTS:\n');
results.tests.filter(t => t.passed).forEach(t => {
console.log(` • ${t.name}`);
if (t.details) console.log(` ${t.details}`);
});
} catch (error) {
console.error('\n❌ TEST ERROR:', error.message);
results.tests.push({ name: 'Test execution', passed: false, details: error.message });
results.failed++;
} finally {
await browser.close();
}
return results;
}
testWineFinderMobile().then(results => {
process.exit(results.failed > 0 ? 1 : 0);
}).catch(err => {
console.error(err);
process.exit(1);
});