docs-v2/scripts/puppeteer/inspect-page.js

329 lines
9.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Page Inspector for AI Agents
*
* This script inspects a page and provides detailed information for debugging:
* - Page metadata
* - Performance metrics
* - Console errors
* - Links analysis
* - Component detection
*
* Usage:
* yarn debug:inspect <url-path> [options]
*
* Examples:
* yarn debug:inspect /influxdb3/core/
* yarn debug:inspect /influxdb3/core/ --output report.json
* yarn debug:inspect /influxdb3/core/ --screenshot
*
* Options:
* --output PATH Save report to JSON file
* --screenshot Also capture a screenshot
* --base-url URL Set base URL (default: http://localhost:1313)
* --chrome PATH Path to Chrome executable
*/
import {
launchBrowser,
navigateToPage,
takeScreenshot,
getPageMetrics,
getPageLinks,
elementExists,
getElementText,
} from './utils/puppeteer-helpers.js';
import fs from 'fs/promises';
async function inspectPage(page) {
console.log('Inspecting page...\n');
const report = {};
// 1. Page metadata
console.log('1. Gathering page metadata...');
report.metadata = await page.evaluate(() => ({
title: document.title,
url: window.location.href,
description: document.querySelector('meta[name="description"]')?.content,
viewport: document.querySelector('meta[name="viewport"]')?.content,
lang: document.documentElement.lang,
}));
// 2. Performance metrics
console.log('2. Collecting performance metrics...');
report.performance = await getPageMetrics(page);
// 3. Console errors
console.log('3. Checking for console errors...');
const errors = [];
page.on('pageerror', (error) => {
errors.push({ type: 'pageerror', message: error.message });
});
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push({ type: 'console', message: msg.text() });
}
});
// Wait a bit for errors to accumulate
await new Promise((resolve) => setTimeout(resolve, 1000));
report.errors = errors;
// 4. Links analysis
console.log('4. Analyzing links...');
const links = await getPageLinks(page);
report.links = {
total: links.length,
internal: links.filter((l) => !l.isExternal).length,
external: links.filter((l) => l.isExternal).length,
list: links,
};
// 5. Component detection
console.log('5. Detecting components...');
const components = await page.evaluate(() => {
const componentElements = document.querySelectorAll('[data-component]');
return Array.from(componentElements).map((el) => ({
type: el.getAttribute('data-component'),
id: el.id,
classes: Array.from(el.classList),
}));
});
report.components = components;
// 6. Hugo shortcode detection
console.log('6. Checking for shortcode remnants...');
const shortcodeRemnants = await page.evaluate(() => {
const html = document.documentElement.outerHTML;
const patterns = [
/\{\{<[^>]+>\}\}/g,
/\{\{%[^%]+%\}\}/g,
/\{\{-?[^}]+-?\}\}/g,
];
const findings = [];
patterns.forEach((pattern, index) => {
const matches = html.match(pattern);
if (matches) {
findings.push({
pattern: pattern.toString(),
count: matches.length,
samples: matches.slice(0, 3),
});
}
});
return findings;
});
report.shortcodeRemnants = shortcodeRemnants;
// 7. Accessibility quick check
console.log('7. Running basic accessibility checks...');
report.accessibility = await page.evaluate(() => {
return {
hasMainLandmark: !!document.querySelector('main'),
hasH1: !!document.querySelector('h1'),
h1Text: document.querySelector('h1')?.textContent,
imagesWithoutAlt: Array.from(document.querySelectorAll('img:not([alt])'))
.length,
linksWithoutText: Array.from(
document.querySelectorAll('a:not([aria-label])')
).filter((a) => !a.textContent.trim()).length,
};
});
// 8. Content structure
console.log('8. Analyzing content structure...');
report.contentStructure = await page.evaluate(() => {
const headings = Array.from(
document.querySelectorAll('h1, h2, h3, h4, h5, h6')
).map((h) => ({
level: parseInt(h.tagName.substring(1)),
text: h.textContent.trim(),
}));
const codeBlocks = Array.from(document.querySelectorAll('pre code')).map(
(block) => ({
language: Array.from(block.classList)
.find((c) => c.startsWith('language-'))
?.substring(9),
lines: block.textContent.split('\n').length,
})
);
return {
headings,
codeBlocks: {
total: codeBlocks.length,
byLanguage: codeBlocks.reduce((acc, block) => {
const lang = block.language || 'unknown';
acc[lang] = (acc[lang] || 0) + 1;
return acc;
}, {}),
},
};
});
return report;
}
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args[0].startsWith('--')) {
console.error('Error: URL path is required');
console.log('\nUsage: yarn debug:inspect <url-path> [options]');
console.log('\nExample: yarn debug:inspect /influxdb3/core/ --screenshot');
process.exit(1);
}
// Parse arguments
const urlPath = args.find((arg) => !arg.startsWith('--'));
const outputPath = args
.find((arg) => arg.startsWith('--output'))
?.split('=')[1];
const takeScreenshotFlag = args.includes('--screenshot');
const baseUrl =
args.find((arg) => arg.startsWith('--base-url'))?.split('=')[1] ||
'http://localhost:1313';
const chromePath = args
.find((arg) => arg.startsWith('--chrome'))
?.split('=')[1];
console.log('\n🔍 Page Inspector');
console.log('=================\n');
console.log(`Inspecting: ${baseUrl}${urlPath}\n`);
let browser;
try {
// Launch browser
browser = await launchBrowser({
headless: true,
executablePath: chromePath,
});
// Navigate to page
const page = await navigateToPage(browser, urlPath, { baseUrl });
// Inspect page
const report = await inspectPage(page);
// Take screenshot if requested
if (takeScreenshotFlag) {
const screenshotPath = outputPath
? outputPath.replace('.json', '.png')
: `inspect-${new Date().toISOString().replace(/[:.]/g, '-')}.png`;
await takeScreenshot(page, screenshotPath);
report.screenshot = screenshotPath;
}
// Display report
console.log('\n📊 Inspection Report');
console.log('===================\n');
console.log('Metadata:');
console.log(` Title: ${report.metadata.title}`);
console.log(` URL: ${report.metadata.url}`);
console.log(` Description: ${report.metadata.description || 'N/A'}\n`);
console.log('Performance:');
console.log(
` DOM Content Loaded: ${report.performance.performance?.domContentLoaded?.toFixed(2) || 'N/A'}ms`
);
console.log(
` Load Complete: ${report.performance.performance?.loadComplete?.toFixed(2) || 'N/A'}ms`
);
console.log(
` First Paint: ${report.performance.performance?.firstPaint?.toFixed(2) || 'N/A'}ms`
);
console.log(
` FCP: ${report.performance.performance?.firstContentfulPaint?.toFixed(2) || 'N/A'}ms\n`
);
console.log('Errors:');
if (report.errors.length > 0) {
report.errors.forEach((err) => {
console.log(` ❌ [${err.type}] ${err.message}`);
});
} else {
console.log(' ✓ No errors detected');
}
console.log('');
console.log('Links:');
console.log(` Total: ${report.links.total}`);
console.log(` Internal: ${report.links.internal}`);
console.log(` External: ${report.links.external}\n`);
console.log('Components:');
if (report.components.length > 0) {
report.components.forEach((comp) => {
console.log(` - ${comp.type}${comp.id ? ` (id: ${comp.id})` : ''}`);
});
} else {
console.log(' None detected');
}
console.log('');
console.log('Shortcode Remnants:');
if (report.shortcodeRemnants.length > 0) {
console.log(' ⚠️ Found shortcode remnants:');
report.shortcodeRemnants.forEach((finding) => {
console.log(` - ${finding.count} matches for ${finding.pattern}`);
finding.samples.forEach((sample) => {
console.log(` "${sample}"`);
});
});
} else {
console.log(' ✓ No shortcode remnants detected');
}
console.log('');
console.log('Accessibility:');
console.log(
` Main landmark: ${report.accessibility.hasMainLandmark ? '✓' : '❌'}`
);
console.log(` H1 present: ${report.accessibility.hasH1 ? '✓' : '❌'}`);
if (report.accessibility.h1Text) {
console.log(` H1 text: "${report.accessibility.h1Text}"`);
}
console.log(
` Images without alt: ${report.accessibility.imagesWithoutAlt}`
);
console.log(
` Links without text: ${report.accessibility.linksWithoutText}\n`
);
console.log('Content Structure:');
console.log(` Headings: ${report.contentStructure.headings.length}`);
console.log(` Code blocks: ${report.contentStructure.codeBlocks.total}`);
if (Object.keys(report.contentStructure.codeBlocks.byLanguage).length > 0) {
console.log(' Languages:');
Object.entries(report.contentStructure.codeBlocks.byLanguage).forEach(
([lang, count]) => {
console.log(` - ${lang}: ${count}`);
}
);
}
console.log('');
// Save report if requested
if (outputPath) {
await fs.writeFile(outputPath, JSON.stringify(report, null, 2));
console.log(`\n✓ Report saved to: ${outputPath}`);
}
console.log('');
} catch (error) {
console.error('\nError:', error.message);
process.exit(1);
} finally {
if (browser) {
await browser.close();
}
}
}
main();