145 lines
5.2 KiB
JavaScript
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`);
|