nimuli/scripts/console-check.mjs

104 lines
3.7 KiB
JavaScript

/**
* 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`);