nimuli/scripts/console-check.mjs

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