/** * 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 */ // Self-re-exec with playwright system deps on Linux if needed if (process.platform === 'linux' && !process.env.__PW_ENV_OK) { const { existsSync } = await import('fs'); const depsDir = `${process.env.HOME}/.local/lib/playwright-deps`; if (existsSync(depsDir)) { const { spawnSync } = await import('child_process'); const ld = process.env.LD_LIBRARY_PATH ?? ''; const result = spawnSync(process.execPath, process.argv.slice(1), { env: { ...process.env, LD_LIBRARY_PATH: ld ? `${depsDir}:${ld}` : depsDir, __PW_ENV_OK: '1' }, stdio: 'inherit', }); process.exit(result.status ?? 1); } } import { chromium } from '@playwright/test'; import { readFileSync } from 'fs'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dir = dirname(fileURLToPath(import.meta.url)); const projectRoot = resolve(__dir, '..'); function readEnv() { try { return Object.fromEntries( readFileSync(resolve(projectRoot, '.env'), 'utf8') .split('\n') .filter(l => l && !l.startsWith('#') && l.includes('=')) .map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; }) ); } catch { return {}; } } const env = readEnv(); const BASE = process.env.APP_URL ?? env.APP_URL ?? 'https://app.nimuli.com'; const EMAIL = process.env.TEST_EMAIL ?? 'nexxo@nimuli.com'; const PASS = process.env.TEST_PASSWORD ?? '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, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--in-process-gpu', '--disable-features=VizDisplayCompositor', ], }); 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`);