/** * Console-error crawler using Playwright. * Logs in, visits all sidebar nav links, reports JS errors/exceptions. * * Usage: * npm install -D @playwright/test && npx playwright install chromium * node scripts/console-check.mjs */ import { chromium } from '@playwright/test'; const BASE = process.env.APP_URL ?? 'https://app.nimuli.com'; const EMAIL = 'nexxo@nimuli.com'; const PASS = 'NimuliDev2026!'; const errors = []; const warnings = []; console.log(`\n${'═'.repeat(45)}`); console.log(' Nimuli — Console Error Check'); console.log(` ${new Date().toISOString()}`); console.log(` Base: ${BASE}`); console.log(`${'═'.repeat(45)}\n`); const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1280, height: 800 }, }); const page = await ctx.newPage(); page.on('console', msg => { if (msg.type() === 'error') { errors.push({ url: page.url(), text: msg.text() }); } else if (msg.type() === 'warning') { warnings.push({ url: page.url(), text: msg.text() }); } }); page.on('pageerror', err => { errors.push({ url: page.url(), text: `PageError: ${err.message}` }); }); // ── Login ──────────────────────────────────── console.log('[ LOGIN ]'); await page.goto(`${BASE}/login`, { waitUntil: 'networkidle' }); await page.fill('input[name=email]', EMAIL); await page.fill('input[name=password]', PASS); await page.click('button[type=submit]'); await page.waitForLoadState('networkidle'); console.log(` ✓ Logged in: ${page.url()}`); // ── Collect nav links ───────────────────────── const navLocators = page.locator('[data-nav-link]'); const navCount = await navLocators.count(); const navLinks = []; for (let i = 0; i < navCount; i++) { const href = await navLocators.nth(i).getAttribute('href'); if (href && !href.endsWith('#') && !navLinks.includes(href)) { navLinks.push(href); } } console.log(`\n[ CRAWLING ${navLinks.length} NAV LINKS ]`); for (const link of navLinks) { const pre = errors.length; await page.goto(link, { waitUntil: 'networkidle' }); await page.waitForTimeout(400); const newErrs = errors.length - pre; const status = newErrs > 0 ? `✗ ${newErrs} new error(s)` : '✓ clean'; let path = link; try { path = new URL(link).pathname; } catch { /* keep full link */ } console.log(` ${status}: ${path}`); } // ── Profile pages ───────────────────────────── console.log('\n[ PROFILE PAGES ]'); for (const path of ['/profile', '/profile/personal', '/profile/preferences', '/profile/security']) { const pre = errors.length; await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle' }); await page.waitForTimeout(300); const newErrs = errors.length - pre; console.log(` ${newErrs > 0 ? '✗' : '✓'} ${path}`); } await browser.close(); // ── Report ──────────────────────────────────── console.log(`\n${'═'.repeat(45)}`); if (errors.length > 0) { console.error(` ❌ ${errors.length} console error(s):`); for (const e of errors) { let path = e.url; try { path = new URL(e.url).pathname; } catch { /* keep full */ } console.error(` ${path}: ${e.text.slice(0, 140)}`); } console.error(`${'═'.repeat(45)}\n`); process.exit(1); } if (warnings.length > 0) { console.warn(` ⚠ ${warnings.length} console warning(s) (non-blocking)`); } console.log(' ✅ No console errors'); console.log(`${'═'.repeat(45)}\n`);