const puppeteer = require('puppeteer'); const fs = require('fs'); const testFiles = [ { name: 'AGENTS.md', status: 'M', expectPreview: true, expectPDF: true, expectGitDiff: true }, { name: 'FEATURE_PDF_DOWNLOAD.md', status: '??', expectPreview: true, expectPDF: true, expectGitDiff: false }, { name: 'TOOLS.md', status: 'M', expectPreview: true, expectPDF: true, expectGitDiff: true }, { name: 'dashboard/api.py', status: 'M', expectPreview: false, expectPDF: false, expectGitDiff: true }, { name: 'memory/2026-02-05.md', status: '??', expectPreview: true, expectPDF: true, expectGitDiff: false } ]; (async () => { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); await page.setViewport({ width: 375, height: 667 }); const results = []; const consoleErrors = []; // Capture console errors page.on('console', msg => { if (msg.type() === 'error') { consoleErrors.push(`[${msg.type()}] ${msg.text()}`); } }); page.on('pageerror', error => { consoleErrors.push(`[pageerror] ${error.message}`); }); for (const file of testFiles) { console.log(`\n========================================`); console.log(`Testing: ${file.name} (${file.status})`); console.log(`========================================`); try { // Navigate to the file await page.goto(`http://localhost:8000/files.html#${file.name}`, { waitUntil: 'networkidle2' }); await page.waitForTimeout(1500); // Screenshot the header const screenshotPath = `/home/moltbot/clawd/test-mobile-${file.name.replace(/\//g, '-')}.png`; await page.screenshot({ path: screenshotPath, fullPage: false }); console.log(`Screenshot saved: ${screenshotPath}`); // Check viewport const viewport = page.viewport(); console.log(`Viewport: ${viewport.width}x${viewport.height}`); // Check what's visible in the header before clicking menu const headerState = await page.evaluate(() => { const result = { hamburgerMenu: false, previewButton: false, pdfButton: false, gitDiffButton: false, menuButtonSelector: null }; // Check for hamburger menu (three dots) const selectors = [ '.file-actions-menu button', 'button[aria-label*="menu"]', '.actions-menu button', 'button.menu-trigger', '.header button:has-text("⋮")', 'button:contains("⋮")' ]; // Try to find the menu button for (const sel of selectors) { try { const el = document.querySelector(sel); if (el && window.getComputedStyle(el).display !== 'none') { result.hamburgerMenu = true; result.menuButtonSelector = sel; break; } } catch (e) {} } // If we can't find by those selectors, look for any button with ⋮ text if (!result.hamburgerMenu) { const buttons = Array.from(document.querySelectorAll('button')); const menuBtn = buttons.find(btn => btn.textContent.includes('⋮')); if (menuBtn && window.getComputedStyle(menuBtn).display !== 'none') { result.hamburgerMenu = true; result.menuButtonSelector = 'button (contains ⋮)'; } } // Check for other buttons in header const allButtons = Array.from(document.querySelectorAll('button')); allButtons.forEach(btn => { const text = btn.textContent.toLowerCase(); const style = window.getComputedStyle(btn); if (style.display !== 'none' && style.visibility !== 'hidden') { if (text.includes('preview')) result.previewButton = true; if (text.includes('pdf')) result.pdfButton = true; if (text.includes('git') || text.includes('diff')) result.gitDiffButton = true; } }); return result; }); console.log('\nHeader buttons visible:'); console.log(` Hamburger Menu: ${headerState.hamburgerMenu} (selector: ${headerState.menuButtonSelector})`); console.log(` Preview: ${headerState.previewButton}`); console.log(` PDF: ${headerState.pdfButton}`); console.log(` Git Diff: ${headerState.gitDiffButton}`); // Try to click hamburger menu and check menu items let menuClicked = false; let menuItems = {}; if (headerState.hamburgerMenu) { try { // Try to find and click the menu button const menuButtonClicked = await page.evaluate(() => { // Look for the menu button const buttons = Array.from(document.querySelectorAll('button')); const menuBtn = buttons.find(btn => btn.textContent.includes('⋮') || btn.classList.contains('menu-trigger') || btn.getAttribute('aria-label')?.toLowerCase().includes('menu') ); if (menuBtn) { menuBtn.click(); return true; } return false; }); if (menuButtonClicked) { await page.waitForTimeout(500); menuClicked = true; // Check what's in the menu menuItems = await page.evaluate(() => { const items = { preview: false, downloadPDF: false, gitDiff: false }; // Look for menu items (they might be in a dropdown/popup) const allElements = Array.from(document.querySelectorAll('*')); allElements.forEach(el => { const text = el.textContent?.toLowerCase() || ''; const style = window.getComputedStyle(el); // Only count visible elements if (style.display !== 'none' && style.visibility !== 'hidden') { if (text === 'preview' || el.textContent?.trim() === 'Preview') items.preview = true; if (text === 'download pdf' || el.textContent?.trim() === 'Download PDF') items.downloadPDF = true; if (text === 'git diff' || el.textContent?.trim() === 'Git Diff') items.gitDiff = true; } }); return items; }); console.log('\nMenu items visible:'); console.log(` Preview: ${menuItems.preview}`); console.log(` Download PDF: ${menuItems.downloadPDF}`); console.log(` Git Diff: ${menuItems.gitDiff}`); } } catch (error) { console.log(`\n⚠️ Error clicking menu: ${error.message}`); } } else { console.log('\n⚠️ Could not find hamburger menu'); } // Verify expectations const issues = []; if (!headerState.hamburgerMenu) { issues.push('❌ Hamburger menu NOT visible on mobile'); } if (menuClicked) { if (file.expectPreview && !menuItems.preview) { issues.push('❌ Preview should be in menu but is NOT'); } if (!file.expectPreview && menuItems.preview) { issues.push('❌ Preview should NOT be in menu but IS'); } if (file.expectPDF && !menuItems.downloadPDF) { issues.push('❌ Download PDF should be in menu but is NOT'); } if (!file.expectPDF && menuItems.downloadPDF) { issues.push('❌ Download PDF should NOT be in menu but IS'); } if (file.expectGitDiff && !menuItems.gitDiff) { issues.push('❌ Git Diff should be in menu but is NOT'); } if (!file.expectGitDiff && menuItems.gitDiff) { issues.push('❌ Git Diff should NOT be in menu but IS'); } } else if (headerState.hamburgerMenu) { issues.push('⚠️ Hamburger menu found but could not click it'); } if (issues.length > 0) { console.log('\n🔴 ISSUES FOUND:'); issues.forEach(issue => console.log(` ${issue}`)); } else { console.log('\n✅ All checks passed'); } results.push({ file: file.name, status: file.status, hamburgerVisible: headerState.hamburgerMenu, menuClicked, menuItems, expected: { preview: file.expectPreview, pdf: file.expectPDF, gitDiff: file.expectGitDiff }, issues }); } catch (error) { console.error(`\n❌ Error testing ${file.name}:`, error.message); results.push({ file: file.name, error: error.message }); } } await browser.close(); // Print summary console.log('\n\n========================================'); console.log('SUMMARY'); console.log('========================================\n'); const filesWithIssues = results.filter(r => r.issues && r.issues.length > 0); if (filesWithIssues.length === 0) { console.log('✅ All files passed mobile menu tests!'); } else { console.log('🔴 Files with issues:\n'); filesWithIssues.forEach(r => { console.log(`${r.file}:`); r.issues.forEach(issue => console.log(` ${issue}`)); console.log(''); }); } if (consoleErrors.length > 0) { console.log('\n⚠️ Console Errors:'); consoleErrors.forEach(err => console.log(` ${err}`)); } else { console.log('\n✅ No console errors detected'); } // Write results to JSON fs.writeFileSync('/home/moltbot/clawd/mobile-test-results.json', JSON.stringify(results, null, 2)); console.log('\n📄 Full results saved to: mobile-test-results.json'); })();