feat: add Puppeteer integration for AI agent development (#6736)
Add Puppeteer utilities and scripts to enable AI agents to interactively test and debug the documentation site during development. Dependencies: puppeteer, pixelmatch, pngjs Scripts: debug:browser, debug:screenshot, debug:inspect Tools: 20+ helper functions, example scripts, comprehensive documentation Enables AI agents to visually debug pages, test components, monitor performance, and detect issues during development. Co-authored-by: Claude <noreply@anthropic.com>pull/6718/head
parent
925a26e580
commit
784956a31c
|
|
@ -28,10 +28,13 @@
|
|||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"globals": "^15.14.0",
|
||||
"hugo-extended": ">=0.101.0",
|
||||
"pixelmatch": "^6.0.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": ">=8.4.31",
|
||||
"postcss-cli": ">=9.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-sql": "^0.18.0",
|
||||
"puppeteer": "^23.11.1",
|
||||
"remark": "^15.0.1",
|
||||
"remark-frontmatter": "^5.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
|
@ -87,7 +90,10 @@
|
|||
"test:shortcode-examples": "node cypress/support/run-e2e-specs.js --spec \"cypress/e2e/content/index.cy.js\" content/example.md",
|
||||
"sync-plugins": "cd helper-scripts/influxdb3-plugins && node port_to_docs.js",
|
||||
"sync-plugins:dry-run": "cd helper-scripts/influxdb3-plugins && node port_to_docs.js --dry-run",
|
||||
"validate-plugin-config": "cd helper-scripts/influxdb3-plugins && node port_to_docs.js --validate"
|
||||
"validate-plugin-config": "cd helper-scripts/influxdb3-plugins && node port_to_docs.js --validate",
|
||||
"debug:browser": "node scripts/puppeteer/debug-browser.js",
|
||||
"debug:screenshot": "node scripts/puppeteer/screenshot.js",
|
||||
"debug:inspect": "node scripts/puppeteer/inspect-page.js"
|
||||
},
|
||||
"type": "module",
|
||||
"browserslist": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# Debug output
|
||||
debug-output/
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
*.pdf
|
||||
|
||||
# Test outputs
|
||||
test-screenshot.png
|
||||
screenshot-*.png
|
||||
format-selector-*.png
|
||||
issues-detected-*.png
|
||||
inspect-*.png
|
||||
reports/
|
||||
|
||||
# Node modules (if someone runs npm install here)
|
||||
node_modules/
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
# Puppeteer Quick Reference
|
||||
|
||||
One-page reference for AI agents using Puppeteer with docs-v2.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
# 1. Hugo server must be running
|
||||
npx hugo server
|
||||
|
||||
# 2. Packages installed (one-time)
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
```
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Take Screenshot
|
||||
```bash
|
||||
yarn debug:screenshot <path> # Basic screenshot
|
||||
yarn debug:screenshot <path> --full-page # Full scrollable page
|
||||
yarn debug:screenshot <path> --selector .class # Specific element
|
||||
yarn debug:screenshot <path> --viewport 375x667 # Mobile size
|
||||
```
|
||||
|
||||
### Inspect Page
|
||||
```bash
|
||||
yarn debug:inspect <path> # Full analysis
|
||||
yarn debug:inspect <path> --output report.json # Save report
|
||||
yarn debug:inspect <path> --screenshot # Include screenshot
|
||||
```
|
||||
|
||||
### Open Browser
|
||||
```bash
|
||||
yarn debug:browser <path> # Interactive mode
|
||||
yarn debug:browser <path> --devtools # With DevTools
|
||||
yarn debug:browser <path> --slow-mo 500 # Slow motion
|
||||
```
|
||||
|
||||
## Quick Workflows
|
||||
|
||||
### User Reports Visual Issue
|
||||
```bash
|
||||
yarn debug:screenshot /path/to/page/ --full-page
|
||||
yarn debug:inspect /path/to/page/
|
||||
# Review screenshot and inspection report
|
||||
```
|
||||
|
||||
### Testing Component Change
|
||||
```bash
|
||||
# Before
|
||||
yarn debug:screenshot /example/ --output before.png
|
||||
|
||||
# Make changes, restart Hugo
|
||||
|
||||
# After
|
||||
yarn debug:screenshot /example/ --output after.png
|
||||
```
|
||||
|
||||
### Debugging JavaScript Error
|
||||
```bash
|
||||
yarn debug:inspect /path/ # Check errors section
|
||||
yarn debug:browser /path/ --devtools # Debug in browser
|
||||
```
|
||||
|
||||
### Performance Check
|
||||
```bash
|
||||
yarn debug:inspect /path/ --output perf.json
|
||||
# Check perf.json → performance.performance.loadComplete
|
||||
# Should be < 3000ms
|
||||
```
|
||||
|
||||
### Automated Issue Detection
|
||||
```bash
|
||||
node scripts/puppeteer/examples/detect-issues.js /path/
|
||||
```
|
||||
|
||||
## Programmatic Usage
|
||||
|
||||
```javascript
|
||||
import {
|
||||
launchBrowser,
|
||||
navigateToPage,
|
||||
takeScreenshot,
|
||||
elementExists,
|
||||
getPageMetrics,
|
||||
} from './utils/puppeteer-helpers.js';
|
||||
|
||||
const browser = await launchBrowser();
|
||||
const page = await navigateToPage(browser, '/influxdb3/core/');
|
||||
|
||||
// Check element
|
||||
const hasComponent = await elementExists(page, '[data-component="format-selector"]');
|
||||
|
||||
// Screenshot
|
||||
await takeScreenshot(page, 'debug.png');
|
||||
|
||||
// Performance
|
||||
const metrics = await getPageMetrics(page);
|
||||
console.log('Load time:', metrics.performance.loadComplete);
|
||||
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
## Common Flags
|
||||
|
||||
```bash
|
||||
--chrome PATH # Use system Chrome
|
||||
--base-url URL # Different base URL
|
||||
--viewport WxH # Viewport size
|
||||
--output PATH # Output file path
|
||||
--full-page # Full page screenshot
|
||||
--selector CSS # Element selector
|
||||
--screenshot # Include screenshot
|
||||
--devtools # Open DevTools
|
||||
--slow-mo NUM # Slow down actions
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Browser not found
|
||||
```bash
|
||||
# Find Chrome
|
||||
which google-chrome
|
||||
|
||||
# Use with flag
|
||||
yarn debug:browser /path/ --chrome "$(which google-chrome)"
|
||||
```
|
||||
|
||||
### Hugo not running
|
||||
```bash
|
||||
# Check if running
|
||||
curl -s http://localhost:1313/
|
||||
|
||||
# Start if needed
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
### Network restrictions
|
||||
```bash
|
||||
# Install without downloading Chrome
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
### Browser & Navigation
|
||||
- `launchBrowser(options)` - Launch browser
|
||||
- `navigateToPage(browser, path, options)` - Navigate
|
||||
- `clickAndNavigate(page, selector)` - Click & wait
|
||||
|
||||
### Screenshots
|
||||
- `takeScreenshot(page, path, options)` - Capture screenshot
|
||||
- `compareScreenshots(baseline, current, diff)` - Compare images
|
||||
- `testResponsive(page, viewports, testFn)` - Multi-viewport
|
||||
|
||||
### Elements
|
||||
- `elementExists(page, selector)` - Check exists
|
||||
- `waitForElement(page, selector, timeout)` - Wait
|
||||
- `getElementText(page, selector)` - Get text
|
||||
|
||||
### Analysis
|
||||
- `getPageMetrics(page)` - Performance data
|
||||
- `getPageLinks(page)` - All links
|
||||
- `getComputedStyles(page, selector)` - CSS values
|
||||
|
||||
### Debugging
|
||||
- `debugPage(page, name)` - Save HTML + screenshot + logs
|
||||
- `captureConsoleLogs(page)` - Capture console
|
||||
|
||||
## What to Check
|
||||
|
||||
### Shortcode Remnants
|
||||
Look for: `{{<`, `{{%`, `{{.`
|
||||
```bash
|
||||
yarn debug:inspect /path/
|
||||
# Check: report.shortcodeRemnants
|
||||
```
|
||||
|
||||
### JavaScript Errors
|
||||
```bash
|
||||
yarn debug:inspect /path/
|
||||
# Check: report.errors
|
||||
```
|
||||
|
||||
### Performance
|
||||
```bash
|
||||
yarn debug:inspect /path/
|
||||
# Check: report.performance.performance.loadComplete < 3000ms
|
||||
# Check: report.performance.performance.firstContentfulPaint < 1500ms
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
```bash
|
||||
yarn debug:inspect /path/
|
||||
# Check: report.accessibility
|
||||
# - hasMainLandmark
|
||||
# - hasH1
|
||||
# - imagesWithoutAlt
|
||||
# - linksWithoutText
|
||||
```
|
||||
|
||||
### Components
|
||||
```bash
|
||||
yarn debug:inspect /path/
|
||||
# Check: report.components
|
||||
# Lists all [data-component] elements
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
All in `scripts/puppeteer/examples/`:
|
||||
|
||||
- `test-format-selector.js` - Test interactive component
|
||||
- `detect-issues.js` - Automated issue detection
|
||||
|
||||
Run with:
|
||||
```bash
|
||||
node scripts/puppeteer/examples/detect-issues.js /path/
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Full Guide**: `scripts/puppeteer/README.md`
|
||||
- **Setup**: `scripts/puppeteer/SETUP.md`
|
||||
- **This Reference**: `scripts/puppeteer/QUICK-REFERENCE.md`
|
||||
|
||||
## Emergency Debug
|
||||
|
||||
When something's broken and you need full context:
|
||||
|
||||
```bash
|
||||
yarn debug:inspect /path/ --output emergency.json --screenshot
|
||||
yarn debug:screenshot /path/ --full-page --output emergency-full.png
|
||||
yarn debug:browser /path/ --devtools
|
||||
```
|
||||
|
||||
This gives you:
|
||||
1. JSON report with all page data
|
||||
2. Full page screenshot
|
||||
3. Interactive browser with DevTools
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Always start Hugo server first! (`npx hugo server`)
|
||||
|
|
@ -0,0 +1,562 @@
|
|||
# Puppeteer Integration for AI Agent Development
|
||||
|
||||
This directory contains Puppeteer utilities designed to help AI agents (like Claude) test and debug the InfluxData documentation site during development.
|
||||
|
||||
## Purpose
|
||||
|
||||
Puppeteer enables AI agents to:
|
||||
|
||||
- **See what's happening** - Take screenshots to visually inspect pages
|
||||
- **Debug interactively** - Launch a browser to manually test features
|
||||
- **Gather context** - Inspect page metadata, performance, errors, and structure
|
||||
- **Test components** - Verify JavaScript components are working correctly
|
||||
- **Validate content** - Check for shortcode remnants, broken links, and accessibility issues
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Install dependencies
|
||||
|
||||
Due to network restrictions, install Puppeteer without downloading the browser binary:
|
||||
|
||||
```bash
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
```
|
||||
|
||||
### Step 2: Configure Chrome path (if needed)
|
||||
|
||||
If you're using system Chrome instead of Puppeteer's bundled browser, set the path in your scripts:
|
||||
|
||||
**Common Chrome paths:**
|
||||
- macOS: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`
|
||||
- Linux: `/usr/bin/google-chrome`
|
||||
- Windows: `C:\Program Files\Google\Chrome\Application\chrome.exe`
|
||||
|
||||
You can pass the Chrome path using the `--chrome` flag:
|
||||
|
||||
```bash
|
||||
yarn debug:browser /influxdb3/core/ --chrome "/usr/bin/google-chrome"
|
||||
```
|
||||
|
||||
### Step 3: Start Hugo server
|
||||
|
||||
Before using Puppeteer tools, make sure the Hugo development server is running:
|
||||
|
||||
```bash
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
The server should be accessible at `http://localhost:1313`.
|
||||
|
||||
## Quick Start for AI Agents
|
||||
|
||||
### Common Debugging Workflow
|
||||
|
||||
When a user reports an issue or you need to debug something:
|
||||
|
||||
1. **Start Hugo server** (if not already running)
|
||||
```bash
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
2. **Inspect the page** to gather information
|
||||
```bash
|
||||
yarn debug:inspect /influxdb3/core/get-started/
|
||||
```
|
||||
|
||||
3. **Take a screenshot** to see visual issues
|
||||
```bash
|
||||
yarn debug:screenshot /influxdb3/core/get-started/ --full-page
|
||||
```
|
||||
|
||||
4. **Open browser interactively** if you need to test manually
|
||||
```bash
|
||||
yarn debug:browser /influxdb3/core/get-started/ --devtools
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
### 1. Page Inspector (`yarn debug:inspect`)
|
||||
|
||||
Gather comprehensive information about a page:
|
||||
|
||||
```bash
|
||||
yarn debug:inspect <url-path> [options]
|
||||
```
|
||||
|
||||
**What it reports:**
|
||||
- Page metadata (title, description, language)
|
||||
- Performance metrics (load time, FCP, etc.)
|
||||
- Console errors and warnings
|
||||
- Links analysis (internal/external counts)
|
||||
- Detected components (`data-component` attributes)
|
||||
- Shortcode remnants (Hugo shortcodes that didn't render)
|
||||
- Basic accessibility checks
|
||||
- Content structure (headings, code blocks)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Inspect a page
|
||||
yarn debug:inspect /influxdb3/core/
|
||||
|
||||
# Save report to JSON
|
||||
yarn debug:inspect /influxdb3/core/ --output report.json
|
||||
|
||||
# Also capture a screenshot
|
||||
yarn debug:inspect /influxdb3/core/ --screenshot
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- User reports a page isn't loading correctly
|
||||
- Need to check if a page has JavaScript errors
|
||||
- Want to verify shortcodes are rendering properly
|
||||
- Need performance metrics for optimization
|
||||
|
||||
### 2. Screenshot Tool (`yarn debug:screenshot`)
|
||||
|
||||
Capture screenshots of pages or specific elements:
|
||||
|
||||
```bash
|
||||
yarn debug:screenshot <url-path> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--output PATH` - Save to specific file
|
||||
- `--full-page` - Capture entire scrollable page
|
||||
- `--selector CSS` - Capture specific element
|
||||
- `--viewport WxH` - Set viewport size (e.g., `375x667` for mobile)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Basic screenshot
|
||||
yarn debug:screenshot /influxdb3/core/
|
||||
|
||||
# Full page screenshot
|
||||
yarn debug:screenshot /influxdb3/core/ --full-page
|
||||
|
||||
# Screenshot of specific element
|
||||
yarn debug:screenshot /influxdb3/core/ --selector .article--content
|
||||
|
||||
# Mobile viewport screenshot
|
||||
yarn debug:screenshot /influxdb3/core/ --viewport 375x667
|
||||
|
||||
# Custom output path
|
||||
yarn debug:screenshot /influxdb3/core/ --output debug/home-page.png
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- User reports visual issue ("the button is cut off")
|
||||
- Need to see how page looks at different viewport sizes
|
||||
- Want to capture a specific component for documentation
|
||||
- Need before/after images for PR review
|
||||
|
||||
### 3. Interactive Browser (`yarn debug:browser`)
|
||||
|
||||
Launch a browser window for manual testing:
|
||||
|
||||
```bash
|
||||
yarn debug:browser <url-path> [options]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--devtools` - Open Chrome DevTools automatically
|
||||
- `--slow-mo NUM` - Slow down actions by NUM milliseconds
|
||||
- `--viewport WxH` - Set viewport size
|
||||
- `--base-url URL` - Use different base URL
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Open page in browser
|
||||
yarn debug:browser /influxdb3/core/
|
||||
|
||||
# Open with DevTools
|
||||
yarn debug:browser /influxdb3/core/ --devtools
|
||||
|
||||
# Slow down for debugging
|
||||
yarn debug:browser /influxdb3/core/ --slow-mo 500
|
||||
|
||||
# Mobile viewport
|
||||
yarn debug:browser /influxdb3/core/ --viewport 375x667
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Need to manually click through a workflow
|
||||
- Want to use Chrome DevTools to debug JavaScript
|
||||
- Testing responsive design breakpoints
|
||||
- Verifying interactive component behavior
|
||||
|
||||
## Programmatic Usage
|
||||
|
||||
AI agents can also use the helper functions directly in custom scripts:
|
||||
|
||||
```javascript
|
||||
import {
|
||||
launchBrowser,
|
||||
navigateToPage,
|
||||
takeScreenshot,
|
||||
getPageMetrics,
|
||||
elementExists,
|
||||
getElementText,
|
||||
clickAndNavigate,
|
||||
testComponent,
|
||||
} from './utils/puppeteer-helpers.js';
|
||||
|
||||
// Launch browser
|
||||
const browser = await launchBrowser({ headless: true });
|
||||
|
||||
// Navigate to page
|
||||
const page = await navigateToPage(browser, '/influxdb3/core/');
|
||||
|
||||
// Check if element exists
|
||||
const hasFormatSelector = await elementExists(page, '[data-component="format-selector"]');
|
||||
console.log('Format selector present:', hasFormatSelector);
|
||||
|
||||
// Get text content
|
||||
const title = await getElementText(page, 'h1');
|
||||
console.log('Page title:', title);
|
||||
|
||||
// Take screenshot
|
||||
await takeScreenshot(page, 'debug.png');
|
||||
|
||||
// Get performance metrics
|
||||
const metrics = await getPageMetrics(page);
|
||||
console.log('Load time:', metrics.performance.loadComplete);
|
||||
|
||||
// Close browser
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Scenario 1: User reports "shortcodes are showing as raw text"
|
||||
|
||||
```bash
|
||||
# Inspect the page for shortcode remnants
|
||||
yarn debug:inspect /path/to/page/
|
||||
|
||||
# Take a screenshot to see the issue
|
||||
yarn debug:screenshot /path/to/page/ --full-page --output shortcode-issue.png
|
||||
```
|
||||
|
||||
**What to look for in the report:**
|
||||
- `shortcodeRemnants` section will show any `{{<` or `{{%` patterns
|
||||
- Screenshot will show visual rendering
|
||||
|
||||
### Scenario 2: User reports "page is loading slowly"
|
||||
|
||||
```bash
|
||||
# Inspect page for performance metrics
|
||||
yarn debug:inspect /path/to/page/ --output performance-report.json
|
||||
|
||||
# Check the report for:
|
||||
# - performance.performance.loadComplete (should be < 3000ms)
|
||||
# - performance.performance.firstContentfulPaint (should be < 1500ms)
|
||||
```
|
||||
|
||||
### Scenario 3: User reports "JavaScript error in console"
|
||||
|
||||
```bash
|
||||
# Inspect page for console errors
|
||||
yarn debug:inspect /path/to/page/
|
||||
|
||||
# Open browser with DevTools to see detailed error
|
||||
yarn debug:browser /path/to/page/ --devtools
|
||||
```
|
||||
|
||||
**What to look for:**
|
||||
- `errors` section in inspection report
|
||||
- Red error messages in DevTools console
|
||||
- Stack traces showing which file/line caused the error
|
||||
|
||||
### Scenario 4: User reports "component not working on mobile"
|
||||
|
||||
```bash
|
||||
# Take screenshot at mobile viewport
|
||||
yarn debug:screenshot /path/to/page/ --viewport 375x667 --output mobile-view.png
|
||||
|
||||
# Open browser at mobile viewport for testing
|
||||
yarn debug:browser /path/to/page/ --viewport 375x667 --devtools
|
||||
```
|
||||
|
||||
### Scenario 5: Testing a Hugo shortcode implementation
|
||||
|
||||
```bash
|
||||
# 1. Inspect test page for components
|
||||
yarn debug:inspect /example/ --screenshot
|
||||
|
||||
# 2. Take screenshots of different states
|
||||
yarn debug:screenshot /example/ --selector '[data-component="tabs-wrapper"]'
|
||||
|
||||
# 3. Open browser to test interactivity
|
||||
yarn debug:browser /example/ --devtools
|
||||
```
|
||||
|
||||
### Scenario 6: Validating responsive design
|
||||
|
||||
```javascript
|
||||
// Create a custom script: test-responsive.js
|
||||
import { launchBrowser, navigateToPage, takeScreenshot, testResponsive } from './utils/puppeteer-helpers.js';
|
||||
|
||||
const browser = await launchBrowser();
|
||||
const page = await navigateToPage(browser, '/influxdb3/core/');
|
||||
|
||||
const viewports = [
|
||||
{ width: 375, height: 667, name: 'iPhone SE' },
|
||||
{ width: 768, height: 1024, name: 'iPad' },
|
||||
{ width: 1280, height: 720, name: 'Desktop' },
|
||||
{ width: 1920, height: 1080, name: 'Desktop HD' },
|
||||
];
|
||||
|
||||
const results = await testResponsive(page, viewports, async (page, viewport) => {
|
||||
await takeScreenshot(page, `responsive-${viewport.name}.png`);
|
||||
const hasNav = await elementExists(page, '[data-component="mobile-nav"]');
|
||||
return { hasNav };
|
||||
});
|
||||
|
||||
console.log('Responsive test results:', results);
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
```bash
|
||||
node scripts/puppeteer/test-responsive.js
|
||||
```
|
||||
|
||||
## Helper Functions Reference
|
||||
|
||||
See `utils/puppeteer-helpers.js` for complete documentation. Key functions:
|
||||
|
||||
### Browser & Navigation
|
||||
- `launchBrowser(options)` - Launch browser instance
|
||||
- `navigateToPage(browser, urlPath, options)` - Navigate to page
|
||||
- `clickAndNavigate(page, selector)` - Click and wait for navigation
|
||||
|
||||
### Elements
|
||||
- `elementExists(page, selector)` - Check if element exists
|
||||
- `waitForElement(page, selector, timeout)` - Wait for element
|
||||
- `getElementText(page, selector)` - Get element text content
|
||||
- `getComputedStyles(page, selector, properties)` - Get CSS styles
|
||||
|
||||
### Screenshots & Visual
|
||||
- `takeScreenshot(page, path, options)` - Capture screenshot
|
||||
- `compareScreenshots(baseline, current, diff)` - Compare images
|
||||
- `testResponsive(page, viewports, testFn)` - Test at different sizes
|
||||
|
||||
### Analysis
|
||||
- `getPageMetrics(page)` - Get performance metrics
|
||||
- `getPageLinks(page)` - Get all links on page
|
||||
- `captureConsoleLogs(page)` - Capture console output
|
||||
- `debugPage(page, name)` - Save HTML + screenshot for debugging
|
||||
|
||||
### Testing
|
||||
- `testComponent(page, selector, testFn)` - Test component behavior
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Failed to launch browser"
|
||||
|
||||
**Problem:** Puppeteer can't find Chrome executable
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Use system Chrome:**
|
||||
```bash
|
||||
yarn debug:browser /path/ --chrome "/usr/bin/google-chrome"
|
||||
```
|
||||
|
||||
2. **Install Puppeteer browser:**
|
||||
```bash
|
||||
npx puppeteer browsers install chrome
|
||||
```
|
||||
|
||||
3. **Check common Chrome paths:**
|
||||
- macOS: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`
|
||||
- Linux: `/usr/bin/google-chrome` or `/usr/bin/chromium`
|
||||
- Windows: `C:\Program Files\Google\Chrome\Application\chrome.exe`
|
||||
|
||||
### Error: "Failed to navigate to http://localhost:1313"
|
||||
|
||||
**Problem:** Hugo server is not running
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# In a separate terminal
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
### Error: "Element not found: .selector"
|
||||
|
||||
**Problem:** Element doesn't exist on page or page hasn't finished loading
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Wait for element:**
|
||||
```javascript
|
||||
await waitForElement(page, '.selector', 10000); // 10 second timeout
|
||||
```
|
||||
|
||||
2. **Check if element exists first:**
|
||||
```javascript
|
||||
if (await elementExists(page, '.selector')) {
|
||||
// Element exists, safe to interact
|
||||
}
|
||||
```
|
||||
|
||||
3. **Take screenshot to debug:**
|
||||
```bash
|
||||
yarn debug:screenshot /path/ --output debug.png
|
||||
```
|
||||
|
||||
### Network restrictions blocking browser download
|
||||
|
||||
**Problem:** Cannot download Puppeteer's bundled Chrome due to network restrictions
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Install without browser binary
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
|
||||
# Use system Chrome with --chrome flag
|
||||
yarn debug:browser /path/ --chrome "/usr/bin/google-chrome"
|
||||
```
|
||||
|
||||
## Best Practices for AI Agents
|
||||
|
||||
### 1. Always check if Hugo server is running first
|
||||
|
||||
```bash
|
||||
# Check if server is responding
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:1313/
|
||||
```
|
||||
|
||||
If it returns `000` or connection refused, start Hugo:
|
||||
```bash
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
### 2. Use inspection before screenshots
|
||||
|
||||
Inspection is faster and provides more context:
|
||||
```bash
|
||||
# First, inspect to understand the issue
|
||||
yarn debug:inspect /path/
|
||||
|
||||
# Then take targeted screenshots if needed
|
||||
yarn debug:screenshot /path/ --selector .problem-component
|
||||
```
|
||||
|
||||
### 3. Prefer headless for automated checks
|
||||
|
||||
Headless mode is faster and doesn't require display:
|
||||
```javascript
|
||||
const browser = await launchBrowser({ headless: true });
|
||||
```
|
||||
|
||||
Only use non-headless (`headless: false`) when you need to visually debug.
|
||||
|
||||
### 4. Clean up resources
|
||||
|
||||
Always close the browser when done:
|
||||
```javascript
|
||||
try {
|
||||
const browser = await launchBrowser();
|
||||
const page = await navigateToPage(browser, '/path/');
|
||||
// ... do work
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Use meaningful screenshot names
|
||||
|
||||
```bash
|
||||
# Bad
|
||||
yarn debug:screenshot /path/ --output screenshot.png
|
||||
|
||||
# Good
|
||||
yarn debug:screenshot /influxdb3/core/ --output debug/influxdb3-core-home-issue-123.png
|
||||
```
|
||||
|
||||
### 6. Capture full context for bug reports
|
||||
|
||||
When a user reports an issue, gather comprehensive context:
|
||||
```bash
|
||||
# 1. Inspection report
|
||||
yarn debug:inspect /path/to/issue/ --output reports/issue-123-inspect.json --screenshot
|
||||
|
||||
# 2. Full page screenshot
|
||||
yarn debug:screenshot /path/to/issue/ --full-page --output reports/issue-123-full.png
|
||||
|
||||
# 3. Element screenshot if specific
|
||||
yarn debug:screenshot /path/to/issue/ --selector .problem-area --output reports/issue-123-element.png
|
||||
```
|
||||
|
||||
## Integration with Development Workflow
|
||||
|
||||
### Use in PR Reviews
|
||||
|
||||
```bash
|
||||
# Before changes
|
||||
yarn debug:screenshot /path/ --output before.png
|
||||
|
||||
# Make changes to code
|
||||
|
||||
# After changes (restart Hugo if needed)
|
||||
yarn debug:screenshot /path/ --output after.png
|
||||
|
||||
# Compare visually
|
||||
```
|
||||
|
||||
### Use for Component Development
|
||||
|
||||
When developing a new component:
|
||||
|
||||
```bash
|
||||
# 1. Open browser to test interactively
|
||||
yarn debug:browser /example/ --devtools
|
||||
|
||||
# 2. Inspect for errors and components
|
||||
yarn debug:inspect /example/
|
||||
|
||||
# 3. Take screenshots for documentation
|
||||
yarn debug:screenshot /example/ --selector '[data-component="new-component"]'
|
||||
```
|
||||
|
||||
### Use for Regression Testing
|
||||
|
||||
```bash
|
||||
# Create baseline screenshots
|
||||
yarn debug:screenshot /influxdb3/core/ --output baselines/core-home.png
|
||||
yarn debug:screenshot /influxdb3/core/get-started/ --output baselines/core-get-started.png
|
||||
|
||||
# After changes, compare
|
||||
yarn debug:screenshot /influxdb3/core/ --output current/core-home.png
|
||||
|
||||
# Visual comparison (manual for now, can be automated with pixelmatch)
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Install dependencies** when you have network access:
|
||||
```bash
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
```
|
||||
|
||||
2. **Configure Chrome path** if needed (see [Troubleshooting](#troubleshooting))
|
||||
|
||||
3. **Test the setup** with a simple example:
|
||||
```bash
|
||||
npx hugo server # In one terminal
|
||||
yarn debug:screenshot / # In another terminal
|
||||
```
|
||||
|
||||
4. **Start using for development** - See [Common Scenarios](#common-scenarios)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Puppeteer API Documentation](https://pptr.dev/)
|
||||
- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)
|
||||
- [Hugo Documentation](https://gohugo.io/documentation/)
|
||||
- [Testing Guide](../../DOCS-TESTING.md)
|
||||
- [Contributing Guide](../../DOCS-CONTRIBUTING.md)
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
# Puppeteer Setup Guide
|
||||
|
||||
Quick setup guide for AI agents using Puppeteer with docs-v2.
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: Use System Chrome (Recommended for network-restricted environments)
|
||||
|
||||
```bash
|
||||
# Install Puppeteer without downloading Chrome
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
```
|
||||
|
||||
Then use the `--chrome` flag to point to your system Chrome:
|
||||
|
||||
```bash
|
||||
yarn debug:browser /influxdb3/core/ --chrome "/usr/bin/google-chrome"
|
||||
```
|
||||
|
||||
**Find your Chrome path:**
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
which google-chrome-stable
|
||||
# Usually: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
||||
|
||||
# Linux
|
||||
which google-chrome
|
||||
# Usually: /usr/bin/google-chrome or /usr/bin/chromium
|
||||
|
||||
# Windows (PowerShell)
|
||||
Get-Command chrome
|
||||
# Usually: C:\Program Files\Google\Chrome\Application\chrome.exe
|
||||
```
|
||||
|
||||
### Option 2: Install Puppeteer's Bundled Chrome (Requires network access)
|
||||
|
||||
```bash
|
||||
# Regular installation (downloads Chrome)
|
||||
yarn install
|
||||
|
||||
# Or install Puppeteer browser separately
|
||||
npx puppeteer browsers install chrome
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Step 1: Check dependencies
|
||||
|
||||
```bash
|
||||
# Check if Puppeteer is in package.json
|
||||
grep puppeteer package.json
|
||||
```
|
||||
|
||||
Should show:
|
||||
```
|
||||
"puppeteer": "^23.11.1",
|
||||
```
|
||||
|
||||
### Step 2: Start Hugo server
|
||||
|
||||
```bash
|
||||
# Start Hugo development server
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
Should show:
|
||||
```
|
||||
Web Server is available at http://localhost:1313/
|
||||
```
|
||||
|
||||
### Step 3: Test Puppeteer
|
||||
|
||||
```bash
|
||||
# Test screenshot tool
|
||||
yarn debug:screenshot / --output test-screenshot.png
|
||||
```
|
||||
|
||||
If successful, you'll see:
|
||||
```
|
||||
📸 Screenshot Utility
|
||||
=====================
|
||||
|
||||
URL: http://localhost:1313/
|
||||
Viewport: 1280x720
|
||||
Output: test-screenshot.png
|
||||
|
||||
Navigating to: http://localhost:1313/
|
||||
✓ Page loaded successfully
|
||||
✓ Screenshot saved: test-screenshot.png
|
||||
|
||||
✓ Screenshot captured successfully
|
||||
```
|
||||
|
||||
### Step 4: Verify screenshot was created
|
||||
|
||||
```bash
|
||||
ls -lh test-screenshot.png
|
||||
```
|
||||
|
||||
Should show a PNG file (typically 100-500KB).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "Failed to launch browser"
|
||||
|
||||
**Error message:**
|
||||
```
|
||||
Failed to launch browser: Could not find Chrome
|
||||
```
|
||||
|
||||
**Solution:** Use system Chrome with `--chrome` flag:
|
||||
|
||||
```bash
|
||||
# Find Chrome path
|
||||
which google-chrome
|
||||
|
||||
# Use with Puppeteer
|
||||
yarn debug:browser / --chrome "$(which google-chrome)"
|
||||
```
|
||||
|
||||
### Issue: "Failed to navigate to http://localhost:1313"
|
||||
|
||||
**Error message:**
|
||||
```
|
||||
Failed to navigate to http://localhost:1313/: net::ERR_CONNECTION_REFUSED
|
||||
```
|
||||
|
||||
**Solution:** Start Hugo server:
|
||||
|
||||
```bash
|
||||
npx hugo server
|
||||
```
|
||||
|
||||
### Issue: "PUPPETEER_SKIP_DOWNLOAD not working"
|
||||
|
||||
**Error message:**
|
||||
```
|
||||
ERROR: Failed to set up chrome-headless-shell
|
||||
```
|
||||
|
||||
**Solution:** Set environment variable before yarn command:
|
||||
|
||||
```bash
|
||||
# Correct
|
||||
PUPPETEER_SKIP_DOWNLOAD=true yarn install
|
||||
|
||||
# Won't work
|
||||
yarn install PUPPETEER_SKIP_DOWNLOAD=true
|
||||
```
|
||||
|
||||
### Issue: "Command not found: yarn"
|
||||
|
||||
**Solution:** Install Yarn or use npm:
|
||||
|
||||
```bash
|
||||
# Install Yarn
|
||||
npm install -g yarn
|
||||
|
||||
# Or use npm instead
|
||||
npm run debug:screenshot -- / --output test.png
|
||||
```
|
||||
|
||||
## Quick Test Script
|
||||
|
||||
Save this as `test-puppeteer-setup.js` and run with `node test-puppeteer-setup.js`:
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Quick test to verify Puppeteer setup
|
||||
*/
|
||||
|
||||
import { launchBrowser, navigateToPage, takeScreenshot } from './utils/puppeteer-helpers.js';
|
||||
|
||||
async function test() {
|
||||
console.log('\n🧪 Testing Puppeteer Setup\n');
|
||||
|
||||
let browser;
|
||||
try {
|
||||
// 1. Launch browser
|
||||
console.log('1. Launching browser...');
|
||||
browser = await launchBrowser({ headless: true });
|
||||
console.log(' ✓ Browser launched\n');
|
||||
|
||||
// 2. Navigate to home page
|
||||
console.log('2. Navigating to home page...');
|
||||
const page = await navigateToPage(browser, '/');
|
||||
console.log(' ✓ Page loaded\n');
|
||||
|
||||
// 3. Take screenshot
|
||||
console.log('3. Taking screenshot...');
|
||||
await takeScreenshot(page, 'test-screenshot.png');
|
||||
console.log(' ✓ Screenshot saved\n');
|
||||
|
||||
// 4. Get page title
|
||||
console.log('4. Getting page title...');
|
||||
const title = await page.title();
|
||||
console.log(` ✓ Title: "${title}"\n`);
|
||||
|
||||
console.log('✅ All tests passed!\n');
|
||||
console.log('Puppeteer is set up correctly and ready to use.\n');
|
||||
} catch (error) {
|
||||
console.error('\n❌ Test failed:', error.message);
|
||||
console.error('\nSee SETUP.md for troubleshooting steps.\n');
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
```
|
||||
|
||||
Run the test:
|
||||
|
||||
```bash
|
||||
cd scripts/puppeteer
|
||||
node test-puppeteer-setup.js
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
You can set these environment variables to customize Puppeteer behavior:
|
||||
|
||||
```bash
|
||||
# Skip downloading Puppeteer's bundled Chrome
|
||||
export PUPPETEER_SKIP_DOWNLOAD=true
|
||||
|
||||
# Use custom Chrome path
|
||||
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome
|
||||
|
||||
# Disable headless mode by default
|
||||
export PUPPETEER_HEADLESS=false
|
||||
```
|
||||
|
||||
Add to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.) to make permanent.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once setup is verified:
|
||||
|
||||
1. **Read the main README**: [README.md](README.md)
|
||||
2. **Try the debugging tools**:
|
||||
```bash
|
||||
yarn debug:inspect /influxdb3/core/
|
||||
yarn debug:screenshot /influxdb3/core/ --full-page
|
||||
yarn debug:browser /influxdb3/core/ --devtools
|
||||
```
|
||||
3. **Create custom scripts** using the helper functions
|
||||
4. **Integrate into your workflow** for testing and debugging
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Main documentation**: [README.md](README.md)
|
||||
- **Helper functions**: [utils/puppeteer-helpers.js](utils/puppeteer-helpers.js)
|
||||
- **Puppeteer docs**: https://pptr.dev/
|
||||
- **Report issues**: Create a GitHub issue in docs-v2
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Interactive Browser Debugger for AI Agents
|
||||
*
|
||||
* This script launches a browser in non-headless mode so AI agents can
|
||||
* visually debug issues during development.
|
||||
*
|
||||
* Usage:
|
||||
* yarn debug:browser [url-path] [options]
|
||||
*
|
||||
* Examples:
|
||||
* yarn debug:browser /influxdb3/core/
|
||||
* yarn debug:browser /influxdb3/core/ --devtools
|
||||
* yarn debug:browser /influxdb3/core/ --slow-mo 100
|
||||
*
|
||||
* Options:
|
||||
* --devtools Open Chrome DevTools
|
||||
* --slow-mo NUM Slow down by NUM milliseconds
|
||||
* --viewport WxH Set viewport size (default: 1280x720)
|
||||
* --base-url URL Set base URL (default: http://localhost:1313)
|
||||
* --chrome PATH Path to Chrome executable
|
||||
*/
|
||||
|
||||
import {
|
||||
launchBrowser,
|
||||
navigateToPage,
|
||||
debugPage,
|
||||
} from './utils/puppeteer-helpers.js';
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Parse arguments
|
||||
const urlPath = args.find((arg) => !arg.startsWith('--')) || '/';
|
||||
const devtools = args.includes('--devtools');
|
||||
const slowMo = parseInt(
|
||||
args.find((arg) => arg.startsWith('--slow-mo'))?.split('=')[1] || '0',
|
||||
10
|
||||
);
|
||||
const viewport =
|
||||
args.find((arg) => arg.startsWith('--viewport'))?.split('=')[1] ||
|
||||
'1280x720';
|
||||
const baseUrl =
|
||||
args.find((arg) => arg.startsWith('--base-url'))?.split('=')[1] ||
|
||||
'http://localhost:1313';
|
||||
const chromePath = args
|
||||
.find((arg) => arg.startsWith('--chrome'))
|
||||
?.split('=')[1];
|
||||
|
||||
const [width, height] = viewport.split('x').map(Number);
|
||||
|
||||
console.log('\n🔍 Interactive Browser Debugger');
|
||||
console.log('================================\n');
|
||||
|
||||
let browser;
|
||||
try {
|
||||
// Launch browser
|
||||
console.log('Launching browser...');
|
||||
browser = await launchBrowser({
|
||||
headless: false,
|
||||
devtools,
|
||||
slowMo,
|
||||
executablePath: chromePath,
|
||||
});
|
||||
|
||||
// Navigate to page
|
||||
const page = await navigateToPage(browser, urlPath, { baseUrl });
|
||||
|
||||
// Set viewport
|
||||
await page.setViewport({ width, height });
|
||||
console.log(`Viewport set to: ${width}x${height}`);
|
||||
|
||||
// Enable console logging
|
||||
page.on('console', (msg) => {
|
||||
console.log(`[Browser Console ${msg.type()}]:`, msg.text());
|
||||
});
|
||||
|
||||
page.on('pageerror', (error) => {
|
||||
console.error('[Page Error]:', error.message);
|
||||
});
|
||||
|
||||
console.log('\n✓ Browser ready for debugging');
|
||||
console.log('\nThe browser will remain open for manual inspection.');
|
||||
console.log('Press Ctrl+C to close the browser and exit.\n');
|
||||
|
||||
// Keep the browser open until user interrupts
|
||||
await new Promise((resolve) => {
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nClosing browser...');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
console.log('✓ Browser closed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Example: Detect Common Issues
|
||||
*
|
||||
* This script demonstrates how to use Puppeteer to detect common issues
|
||||
* in documentation pages:
|
||||
* - Shortcode remnants (Hugo shortcodes that didn't render)
|
||||
* - Broken images
|
||||
* - Missing alt text
|
||||
* - JavaScript errors
|
||||
* - Slow page load
|
||||
*
|
||||
* Run with: node scripts/puppeteer/examples/detect-issues.js <url-path>
|
||||
*/
|
||||
|
||||
import {
|
||||
launchBrowser,
|
||||
navigateToPage,
|
||||
takeScreenshot,
|
||||
getPageMetrics,
|
||||
} from '../utils/puppeteer-helpers.js';
|
||||
|
||||
async function detectIssues(urlPath) {
|
||||
console.log('\n🔍 Detecting Common Issues\n');
|
||||
console.log(`Page: ${urlPath}\n`);
|
||||
|
||||
const issues = [];
|
||||
let browser;
|
||||
|
||||
try {
|
||||
// Launch browser
|
||||
browser = await launchBrowser({ headless: true });
|
||||
const page = await navigateToPage(browser, urlPath);
|
||||
|
||||
// 1. Check for shortcode remnants
|
||||
console.log('1. Checking for shortcode remnants...');
|
||||
const shortcodeRemnants = await page.evaluate(() => {
|
||||
const html = document.documentElement.outerHTML;
|
||||
const patterns = [
|
||||
{ name: 'Hugo shortcode open', regex: /\{\{<[^>]+>\}\}/g },
|
||||
{ name: 'Hugo shortcode percent', regex: /\{\{%[^%]+%\}\}/g },
|
||||
{ name: 'Hugo variable', regex: /\{\{\s*\.[^\s}]+\s*\}\}/g },
|
||||
];
|
||||
|
||||
const findings = [];
|
||||
patterns.forEach(({ name, regex }) => {
|
||||
const matches = html.match(regex);
|
||||
if (matches) {
|
||||
findings.push({
|
||||
type: name,
|
||||
count: matches.length,
|
||||
samples: matches.slice(0, 3),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return findings;
|
||||
});
|
||||
|
||||
if (shortcodeRemnants.length > 0) {
|
||||
shortcodeRemnants.forEach((finding) => {
|
||||
issues.push({
|
||||
severity: 'high',
|
||||
type: 'shortcode-remnant',
|
||||
message: `Found ${finding.count} instances of ${finding.type}`,
|
||||
samples: finding.samples,
|
||||
});
|
||||
});
|
||||
console.log(
|
||||
` ⚠️ Found ${shortcodeRemnants.length} types of shortcode remnants`
|
||||
);
|
||||
} else {
|
||||
console.log(' ✓ No shortcode remnants detected');
|
||||
}
|
||||
|
||||
// 2. Check for broken images
|
||||
console.log('\n2. Checking for broken images...');
|
||||
const brokenImages = await page.evaluate(() => {
|
||||
const images = Array.from(document.querySelectorAll('img'));
|
||||
return images
|
||||
.filter((img) => !img.complete || img.naturalWidth === 0)
|
||||
.map((img) => ({
|
||||
src: img.src,
|
||||
alt: img.alt,
|
||||
}));
|
||||
});
|
||||
|
||||
if (brokenImages.length > 0) {
|
||||
issues.push({
|
||||
severity: 'high',
|
||||
type: 'broken-images',
|
||||
message: `Found ${brokenImages.length} broken images`,
|
||||
details: brokenImages,
|
||||
});
|
||||
console.log(` ⚠️ Found ${brokenImages.length} broken images`);
|
||||
} else {
|
||||
console.log(' ✓ All images loaded successfully');
|
||||
}
|
||||
|
||||
// 3. Check for images without alt text
|
||||
console.log('\n3. Checking for accessibility issues...');
|
||||
const missingAltText = await page.evaluate(() => {
|
||||
const images = Array.from(document.querySelectorAll('img:not([alt])'));
|
||||
return images.map((img) => img.src);
|
||||
});
|
||||
|
||||
if (missingAltText.length > 0) {
|
||||
issues.push({
|
||||
severity: 'medium',
|
||||
type: 'missing-alt-text',
|
||||
message: `Found ${missingAltText.length} images without alt text`,
|
||||
details: missingAltText,
|
||||
});
|
||||
console.log(
|
||||
` ⚠️ Found ${missingAltText.length} images without alt text`
|
||||
);
|
||||
} else {
|
||||
console.log(' ✓ All images have alt text');
|
||||
}
|
||||
|
||||
// 4. Check for empty links
|
||||
const emptyLinks = await page.evaluate(() => {
|
||||
const links = Array.from(
|
||||
document.querySelectorAll('a:not([aria-label])')
|
||||
);
|
||||
return links
|
||||
.filter((link) => !link.textContent.trim())
|
||||
.map((link) => link.href);
|
||||
});
|
||||
|
||||
if (emptyLinks.length > 0) {
|
||||
issues.push({
|
||||
severity: 'medium',
|
||||
type: 'empty-links',
|
||||
message: `Found ${emptyLinks.length} links without text`,
|
||||
details: emptyLinks,
|
||||
});
|
||||
console.log(` ⚠️ Found ${emptyLinks.length} links without text`);
|
||||
} else {
|
||||
console.log(' ✓ All links have text or aria-label');
|
||||
}
|
||||
|
||||
// 5. Check for JavaScript errors
|
||||
console.log('\n4. Checking for JavaScript errors...');
|
||||
const jsErrors = [];
|
||||
page.on('pageerror', (error) => {
|
||||
jsErrors.push(error.message);
|
||||
});
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
jsErrors.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a bit for errors to accumulate
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
if (jsErrors.length > 0) {
|
||||
issues.push({
|
||||
severity: 'high',
|
||||
type: 'javascript-errors',
|
||||
message: `Found ${jsErrors.length} JavaScript errors`,
|
||||
details: jsErrors,
|
||||
});
|
||||
console.log(` ⚠️ Found ${jsErrors.length} JavaScript errors`);
|
||||
} else {
|
||||
console.log(' ✓ No JavaScript errors detected');
|
||||
}
|
||||
|
||||
// 6. Check page performance
|
||||
console.log('\n5. Checking page performance...');
|
||||
const metrics = await getPageMetrics(page);
|
||||
const loadTime = metrics.performance?.loadComplete || 0;
|
||||
const fcp = metrics.performance?.firstContentfulPaint || 0;
|
||||
|
||||
if (loadTime > 3000) {
|
||||
issues.push({
|
||||
severity: 'medium',
|
||||
type: 'slow-load',
|
||||
message: `Page load time is ${loadTime.toFixed(0)}ms (> 3000ms)`,
|
||||
});
|
||||
console.log(` ⚠️ Slow page load: ${loadTime.toFixed(0)}ms`);
|
||||
} else {
|
||||
console.log(` ✓ Page load time: ${loadTime.toFixed(0)}ms`);
|
||||
}
|
||||
|
||||
if (fcp > 1500) {
|
||||
issues.push({
|
||||
severity: 'low',
|
||||
type: 'slow-fcp',
|
||||
message: `First Contentful Paint is ${fcp.toFixed(0)}ms (> 1500ms)`,
|
||||
});
|
||||
console.log(` ⚠️ Slow FCP: ${fcp.toFixed(0)}ms`);
|
||||
} else {
|
||||
console.log(` ✓ First Contentful Paint: ${fcp.toFixed(0)}ms`);
|
||||
}
|
||||
|
||||
// 7. Check for missing heading hierarchy
|
||||
console.log('\n6. Checking heading structure...');
|
||||
const headingIssues = await page.evaluate(() => {
|
||||
const headings = Array.from(
|
||||
document.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||
);
|
||||
const issues = [];
|
||||
|
||||
// Check for multiple h1s
|
||||
const h1s = headings.filter((h) => h.tagName === 'H1');
|
||||
if (h1s.length === 0) {
|
||||
issues.push('No h1 found');
|
||||
} else if (h1s.length > 1) {
|
||||
issues.push(`Multiple h1s found (${h1s.length})`);
|
||||
}
|
||||
|
||||
// Check for skipped heading levels
|
||||
let prevLevel = 0;
|
||||
headings.forEach((heading) => {
|
||||
const level = parseInt(heading.tagName.substring(1));
|
||||
if (prevLevel > 0 && level > prevLevel + 1) {
|
||||
issues.push(`Skipped from h${prevLevel} to h${level}`);
|
||||
}
|
||||
prevLevel = level;
|
||||
});
|
||||
|
||||
return issues;
|
||||
});
|
||||
|
||||
if (headingIssues.length > 0) {
|
||||
issues.push({
|
||||
severity: 'low',
|
||||
type: 'heading-structure',
|
||||
message: 'Heading structure issues detected',
|
||||
details: headingIssues,
|
||||
});
|
||||
console.log(' ⚠️ Heading structure issues:');
|
||||
headingIssues.forEach((issue) => console.log(` - ${issue}`));
|
||||
} else {
|
||||
console.log(' ✓ Heading structure is correct');
|
||||
}
|
||||
|
||||
// Take screenshot if issues found
|
||||
if (issues.length > 0) {
|
||||
console.log('\n📸 Taking screenshot for reference...');
|
||||
await takeScreenshot(page, `issues-detected-${Date.now()}.png`, {
|
||||
fullPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log('SUMMARY');
|
||||
console.log('='.repeat(50) + '\n');
|
||||
|
||||
if (issues.length === 0) {
|
||||
console.log('✅ No issues detected!\n');
|
||||
} else {
|
||||
const high = issues.filter((i) => i.severity === 'high').length;
|
||||
const medium = issues.filter((i) => i.severity === 'medium').length;
|
||||
const low = issues.filter((i) => i.severity === 'low').length;
|
||||
|
||||
console.log(`Found ${issues.length} issue(s):\n`);
|
||||
console.log(` High: ${high}`);
|
||||
console.log(` Medium: ${medium}`);
|
||||
console.log(` Low: ${low}\n`);
|
||||
|
||||
console.log('Details:\n');
|
||||
issues.forEach((issue, index) => {
|
||||
const icon =
|
||||
issue.severity === 'high'
|
||||
? '🔴'
|
||||
: issue.severity === 'medium'
|
||||
? '🟡'
|
||||
: '🔵';
|
||||
console.log(
|
||||
`${index + 1}. ${icon} [${issue.severity.toUpperCase()}] ${issue.message}`
|
||||
);
|
||||
|
||||
if (issue.samples && issue.samples.length > 0) {
|
||||
console.log(' Samples:');
|
||||
issue.samples.forEach((sample) => {
|
||||
console.log(` - "${sample}"`);
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
issue.details &&
|
||||
issue.details.length > 0 &&
|
||||
issue.details.length <= 5
|
||||
) {
|
||||
console.log(' Details:');
|
||||
issue.details.forEach((detail) => {
|
||||
const str =
|
||||
typeof detail === 'string' ? detail : JSON.stringify(detail);
|
||||
console.log(` - ${str}`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('');
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('\n❌ Error:', error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
// CLI
|
||||
const urlPath = process.argv[2] || '/';
|
||||
detectIssues(urlPath).catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Example: Test Format Selector Component
|
||||
*
|
||||
* This example demonstrates how to use Puppeteer to test an interactive
|
||||
* component on the documentation site.
|
||||
*
|
||||
* Run with: node scripts/puppeteer/examples/test-format-selector.js
|
||||
*/
|
||||
|
||||
import {
|
||||
launchBrowser,
|
||||
navigateToPage,
|
||||
takeScreenshot,
|
||||
elementExists,
|
||||
waitForElement,
|
||||
clickAndNavigate,
|
||||
debugPage,
|
||||
} from '../utils/puppeteer-helpers.js';
|
||||
|
||||
async function testFormatSelector() {
|
||||
console.log('\n🧪 Testing Format Selector Component\n');
|
||||
|
||||
let browser;
|
||||
try {
|
||||
// Launch browser
|
||||
console.log('Launching browser...');
|
||||
browser = await launchBrowser({ headless: true });
|
||||
|
||||
// Navigate to a page with format selector
|
||||
console.log('Navigating to page...');
|
||||
const page = await navigateToPage(browser, '/influxdb3/core/get-started/');
|
||||
|
||||
// Check if format selector exists
|
||||
console.log('\n1. Checking if format selector exists...');
|
||||
const hasFormatSelector = await elementExists(
|
||||
page,
|
||||
'[data-component="format-selector"]'
|
||||
);
|
||||
|
||||
if (!hasFormatSelector) {
|
||||
console.log(' ⚠️ Format selector not found on this page');
|
||||
console.log(
|
||||
" This is expected if the page doesn't have multiple formats"
|
||||
);
|
||||
return;
|
||||
}
|
||||
console.log(' ✓ Format selector found');
|
||||
|
||||
// Take initial screenshot
|
||||
console.log('\n2. Capturing initial state...');
|
||||
await takeScreenshot(page, 'format-selector-initial.png', {
|
||||
selector: '[data-component="format-selector"]',
|
||||
});
|
||||
console.log(' ✓ Screenshot saved');
|
||||
|
||||
// Click the format selector button
|
||||
console.log('\n3. Testing dropdown interaction...');
|
||||
const buttonExists = await elementExists(
|
||||
page,
|
||||
'[data-component="format-selector"] button'
|
||||
);
|
||||
|
||||
if (buttonExists) {
|
||||
// Click button to open dropdown
|
||||
await page.click('[data-component="format-selector"] button');
|
||||
console.log(' ✓ Clicked format selector button');
|
||||
|
||||
// Wait for dropdown menu to appear
|
||||
await waitForElement(
|
||||
page,
|
||||
'[data-component="format-selector"] [role="menu"]',
|
||||
3000
|
||||
);
|
||||
console.log(' ✓ Dropdown menu appeared');
|
||||
|
||||
// Take screenshot of open dropdown
|
||||
await takeScreenshot(page, 'format-selector-open.png', {
|
||||
selector: '[data-component="format-selector"]',
|
||||
});
|
||||
console.log(' ✓ Screenshot of open dropdown saved');
|
||||
|
||||
// Get all format options
|
||||
const options = await page.$$eval(
|
||||
'[data-component="format-selector"] [role="menuitem"]',
|
||||
(items) => items.map((item) => item.textContent.trim())
|
||||
);
|
||||
console.log(` ✓ Found ${options.length} format options:`, options);
|
||||
|
||||
// Test clicking each option
|
||||
console.log('\n4. Testing format options...');
|
||||
const menuItems = await page.$$(
|
||||
'[data-component="format-selector"] [role="menuitem"]'
|
||||
);
|
||||
|
||||
for (let i = 0; i < Math.min(menuItems.length, 3); i++) {
|
||||
const option = options[i];
|
||||
console.log(` Testing option: ${option}`);
|
||||
|
||||
// Click the format selector button again (it closes after selection)
|
||||
await page.click('[data-component="format-selector"] button');
|
||||
await page.waitForTimeout(300); // Wait for animation
|
||||
|
||||
// Click the option
|
||||
await page.click(
|
||||
`[data-component="format-selector"] [role="menuitem"]:nth-child(${i + 1})`
|
||||
);
|
||||
await page.waitForTimeout(500); // Wait for content to update
|
||||
|
||||
// Take screenshot of result
|
||||
await takeScreenshot(
|
||||
page,
|
||||
`format-selector-${option.toLowerCase().replace(/\s+/g, '-')}.png`
|
||||
);
|
||||
console.log(` ✓ Tested ${option} format`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for JavaScript errors
|
||||
console.log('\n5. Checking for JavaScript errors...');
|
||||
const errors = await page.evaluate(() => {
|
||||
// Check if any errors were logged
|
||||
return window.__errors || [];
|
||||
});
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.log(' ⚠️ Found JavaScript errors:');
|
||||
errors.forEach((err) => console.log(` - ${err}`));
|
||||
} else {
|
||||
console.log(' ✓ No JavaScript errors detected');
|
||||
}
|
||||
|
||||
// Get computed styles
|
||||
console.log('\n6. Checking component styles...');
|
||||
const styles = await page.evaluate(() => {
|
||||
const selector = document.querySelector(
|
||||
'[data-component="format-selector"]'
|
||||
);
|
||||
if (!selector) return null;
|
||||
|
||||
const computed = window.getComputedStyle(selector);
|
||||
return {
|
||||
display: computed.display,
|
||||
visibility: computed.visibility,
|
||||
opacity: computed.opacity,
|
||||
};
|
||||
});
|
||||
|
||||
if (styles) {
|
||||
console.log(' Component styles:', styles);
|
||||
console.log(' ✓ Component is visible');
|
||||
}
|
||||
|
||||
console.log('\n✅ Format selector tests completed successfully!\n');
|
||||
} catch (error) {
|
||||
console.error('\n❌ Test failed:', error.message);
|
||||
|
||||
// Save debug output
|
||||
if (browser) {
|
||||
const pages = await browser.pages();
|
||||
if (pages.length > 0) {
|
||||
await debugPage(pages[0], 'format-selector-error');
|
||||
console.log('\n💾 Debug information saved to debug-output/');
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testFormatSelector().catch((error) => {
|
||||
console.error('\nFatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
#!/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 page.waitForTimeout(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();
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Screenshot Utility for AI Agents
|
||||
*
|
||||
* This script takes screenshots of documentation pages for debugging and validation.
|
||||
*
|
||||
* Usage:
|
||||
* yarn debug:screenshot <url-path> [options]
|
||||
*
|
||||
* Examples:
|
||||
* yarn debug:screenshot /influxdb3/core/
|
||||
* yarn debug:screenshot /influxdb3/core/ --output debug.png
|
||||
* yarn debug:screenshot /influxdb3/core/ --full-page
|
||||
* yarn debug:screenshot /influxdb3/core/ --selector .article--content
|
||||
* yarn debug:screenshot /influxdb3/core/ --viewport 375x667
|
||||
*
|
||||
* Options:
|
||||
* --output PATH Output file path (default: screenshot-{timestamp}.png)
|
||||
* --full-page Capture full page scroll
|
||||
* --selector SELECTOR Capture specific element
|
||||
* --viewport WxH Set viewport size (default: 1280x720)
|
||||
* --base-url URL Set base URL (default: http://localhost:1313)
|
||||
* --chrome PATH Path to Chrome executable
|
||||
*/
|
||||
|
||||
import {
|
||||
launchBrowser,
|
||||
navigateToPage,
|
||||
takeScreenshot,
|
||||
} from './utils/puppeteer-helpers.js';
|
||||
import path from 'path';
|
||||
|
||||
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:screenshot <url-path> [options]');
|
||||
console.log(
|
||||
'\nExample: yarn debug:screenshot /influxdb3/core/ --full-page'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
const urlPath = args.find((arg) => !arg.startsWith('--'));
|
||||
const output =
|
||||
args.find((arg) => arg.startsWith('--output'))?.split('=')[1] ||
|
||||
`screenshot-${new Date().toISOString().replace(/[:.]/g, '-')}.png`;
|
||||
const fullPage = args.includes('--full-page');
|
||||
const selector = args
|
||||
.find((arg) => arg.startsWith('--selector'))
|
||||
?.split('=')[1];
|
||||
const viewport =
|
||||
args.find((arg) => arg.startsWith('--viewport'))?.split('=')[1] ||
|
||||
'1280x720';
|
||||
const baseUrl =
|
||||
args.find((arg) => arg.startsWith('--base-url'))?.split('=')[1] ||
|
||||
'http://localhost:1313';
|
||||
const chromePath = args
|
||||
.find((arg) => arg.startsWith('--chrome'))
|
||||
?.split('=')[1];
|
||||
|
||||
const [width, height] = viewport.split('x').map(Number);
|
||||
|
||||
console.log('\n📸 Screenshot Utility');
|
||||
console.log('=====================\n');
|
||||
console.log(`URL: ${baseUrl}${urlPath}`);
|
||||
console.log(`Viewport: ${width}x${height}`);
|
||||
console.log(`Output: ${output}`);
|
||||
if (fullPage) console.log('Mode: Full page');
|
||||
if (selector) console.log(`Element: ${selector}`);
|
||||
console.log('');
|
||||
|
||||
let browser;
|
||||
try {
|
||||
// Launch browser
|
||||
browser = await launchBrowser({
|
||||
headless: true,
|
||||
executablePath: chromePath,
|
||||
});
|
||||
|
||||
// Navigate to page
|
||||
const page = await navigateToPage(browser, urlPath, { baseUrl });
|
||||
|
||||
// Set viewport
|
||||
await page.setViewport({ width, height });
|
||||
|
||||
// Take screenshot
|
||||
await takeScreenshot(page, output, { fullPage, selector });
|
||||
|
||||
console.log('\n✓ Screenshot captured successfully\n');
|
||||
} catch (error) {
|
||||
console.error('\nError:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
/**
|
||||
* Puppeteer Helper Utilities for AI Agent Development
|
||||
*
|
||||
* This module provides reusable functions for AI agents to debug and test
|
||||
* the documentation site during development.
|
||||
*
|
||||
* Usage:
|
||||
* import { launchBrowser, navigateToPage, takeScreenshot } from './utils/puppeteer-helpers.js';
|
||||
*
|
||||
* const browser = await launchBrowser();
|
||||
* const page = await navigateToPage(browser, '/influxdb3/core/');
|
||||
* await takeScreenshot(page, 'debug-screenshot.png');
|
||||
* await browser.close();
|
||||
*/
|
||||
|
||||
import puppeteer from 'puppeteer';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Launch a browser instance
|
||||
*
|
||||
* @param {Object} options - Browser launch options
|
||||
* @param {boolean} options.headless - Run in headless mode (default: true)
|
||||
* @param {boolean} options.devtools - Open DevTools (default: false)
|
||||
* @param {number} options.slowMo - Slow down operations by ms (default: 0)
|
||||
* @returns {Promise<Browser>} Puppeteer browser instance
|
||||
*/
|
||||
export async function launchBrowser(options = {}) {
|
||||
const {
|
||||
headless = true,
|
||||
devtools = false,
|
||||
slowMo = 0,
|
||||
executablePath = null,
|
||||
} = options;
|
||||
|
||||
const launchOptions = {
|
||||
headless: headless ? 'new' : false,
|
||||
devtools,
|
||||
slowMo,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-web-security', // Allow cross-origin requests for local development
|
||||
],
|
||||
};
|
||||
|
||||
// Use system Chrome if available (useful when PUPPETEER_SKIP_DOWNLOAD was used)
|
||||
if (executablePath) {
|
||||
launchOptions.executablePath = executablePath;
|
||||
}
|
||||
|
||||
try {
|
||||
return await puppeteer.launch(launchOptions);
|
||||
} catch (error) {
|
||||
console.error('Failed to launch browser:', error.message);
|
||||
console.log('\nTroubleshooting:');
|
||||
console.log(
|
||||
'1. If Puppeteer browser not installed, set executablePath to system Chrome'
|
||||
);
|
||||
console.log('2. Common paths:');
|
||||
console.log(
|
||||
' - macOS: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
||||
);
|
||||
console.log(' - Linux: /usr/bin/google-chrome');
|
||||
console.log(
|
||||
' - Windows: C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
|
||||
);
|
||||
console.log(
|
||||
'3. Or install Puppeteer browser: PUPPETEER_SKIP_DOWNLOAD=false yarn add puppeteer'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a page on the local Hugo server
|
||||
*
|
||||
* @param {Browser} browser - Puppeteer browser instance
|
||||
* @param {string} urlPath - URL path (e.g., '/influxdb3/core/')
|
||||
* @param {Object} options - Navigation options
|
||||
* @param {string} options.baseUrl - Base URL (default: 'http://localhost:1313')
|
||||
* @param {number} options.timeout - Navigation timeout in ms (default: 30000)
|
||||
* @param {string} options.waitUntil - When to consider navigation succeeded (default: 'networkidle2')
|
||||
* @returns {Promise<Page>} Puppeteer page instance
|
||||
*/
|
||||
export async function navigateToPage(browser, urlPath, options = {}) {
|
||||
const {
|
||||
baseUrl = 'http://localhost:1313',
|
||||
timeout = 30000,
|
||||
waitUntil = 'networkidle2',
|
||||
} = options;
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Set viewport size
|
||||
await page.setViewport({ width: 1280, height: 720 });
|
||||
|
||||
// Enable console logging from the page
|
||||
page.on('console', (msg) => {
|
||||
const type = msg.type();
|
||||
if (type === 'error' || type === 'warning') {
|
||||
console.log(`[Browser ${type}]:`, msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
// Log page errors
|
||||
page.on('pageerror', (error) => {
|
||||
console.error('[Page Error]:', error.message);
|
||||
});
|
||||
|
||||
const fullUrl = `${baseUrl}${urlPath}`;
|
||||
console.log(`Navigating to: ${fullUrl}`);
|
||||
|
||||
try {
|
||||
await page.goto(fullUrl, { waitUntil, timeout });
|
||||
console.log('✓ Page loaded successfully');
|
||||
return page;
|
||||
} catch (error) {
|
||||
console.error(`Failed to navigate to ${fullUrl}:`, error.message);
|
||||
console.log('\nTroubleshooting:');
|
||||
console.log('1. Make sure Hugo server is running: yarn hugo server');
|
||||
console.log('2. Check if the URL path is correct');
|
||||
console.log('3. Try increasing timeout if page is slow to load');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a screenshot of the page or a specific element
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} outputPath - Output file path
|
||||
* @param {Object} options - Screenshot options
|
||||
* @param {string} options.selector - CSS selector to screenshot specific element
|
||||
* @param {boolean} options.fullPage - Capture full page (default: false)
|
||||
* @param {Object} options.clip - Clip region {x, y, width, height}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function takeScreenshot(page, outputPath, options = {}) {
|
||||
const { selector, fullPage = false, clip } = options;
|
||||
|
||||
// Ensure output directory exists
|
||||
const dir = path.dirname(outputPath);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
|
||||
const screenshotOptions = {
|
||||
path: outputPath,
|
||||
fullPage,
|
||||
};
|
||||
|
||||
if (clip) {
|
||||
screenshotOptions.clip = clip;
|
||||
}
|
||||
|
||||
try {
|
||||
if (selector) {
|
||||
const element = await page.$(selector);
|
||||
if (!element) {
|
||||
throw new Error(`Element not found: ${selector}`);
|
||||
}
|
||||
await element.screenshot({ path: outputPath });
|
||||
console.log(`✓ Screenshot saved (element: ${selector}): ${outputPath}`);
|
||||
} else {
|
||||
await page.screenshot(screenshotOptions);
|
||||
console.log(`✓ Screenshot saved: ${outputPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to take screenshot:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page metrics and performance data
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @returns {Promise<Object>} Performance metrics
|
||||
*/
|
||||
export async function getPageMetrics(page) {
|
||||
const metrics = await page.metrics();
|
||||
|
||||
const performanceData = await page.evaluate(() => {
|
||||
const perfData = window.performance.getEntriesByType('navigation')[0];
|
||||
return {
|
||||
domContentLoaded:
|
||||
perfData?.domContentLoadedEventEnd -
|
||||
perfData?.domContentLoadedEventStart,
|
||||
loadComplete: perfData?.loadEventEnd - perfData?.loadEventStart,
|
||||
firstPaint: performance.getEntriesByType('paint')[0]?.startTime,
|
||||
firstContentfulPaint: performance.getEntriesByType('paint')[1]?.startTime,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...metrics,
|
||||
performance: performanceData,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for JavaScript errors on the page
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @returns {Promise<Array>} Array of error messages
|
||||
*/
|
||||
export async function getPageErrors(page) {
|
||||
const errors = [];
|
||||
|
||||
page.on('pageerror', (error) => {
|
||||
errors.push(error.message);
|
||||
});
|
||||
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
errors.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all links on the page
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @returns {Promise<Array>} Array of link objects {href, text}
|
||||
*/
|
||||
export async function getPageLinks(page) {
|
||||
return await page.evaluate(() => {
|
||||
const links = Array.from(document.querySelectorAll('a'));
|
||||
return links.map((link) => ({
|
||||
href: link.href,
|
||||
text: link.textContent.trim(),
|
||||
isExternal: !link.href.startsWith(window.location.origin),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element exists on the page
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} selector - CSS selector
|
||||
* @returns {Promise<boolean>} True if element exists
|
||||
*/
|
||||
export async function elementExists(page, selector) {
|
||||
return (await page.$(selector)) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an element to appear on the page
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} selector - CSS selector
|
||||
* @param {number} timeout - Timeout in ms (default: 5000)
|
||||
* @returns {Promise<ElementHandle>} Element handle
|
||||
*/
|
||||
export async function waitForElement(page, selector, timeout = 5000) {
|
||||
try {
|
||||
return await page.waitForSelector(selector, { timeout });
|
||||
} catch (error) {
|
||||
console.error(`Element not found within ${timeout}ms: ${selector}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text content of an element
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} selector - CSS selector
|
||||
* @returns {Promise<string>} Text content
|
||||
*/
|
||||
export async function getElementText(page, selector) {
|
||||
const element = await page.$(selector);
|
||||
if (!element) {
|
||||
throw new Error(`Element not found: ${selector}`);
|
||||
}
|
||||
return await page.evaluate((el) => el.textContent, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click an element and wait for navigation
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} selector - CSS selector
|
||||
* @param {Object} options - Click options
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function clickAndNavigate(page, selector, options = {}) {
|
||||
const { waitUntil = 'networkidle2' } = options;
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation({ waitUntil }),
|
||||
page.click(selector),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a component's interactive behavior
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} componentSelector - Component selector
|
||||
* @param {Function} testFn - Test function to run
|
||||
* @returns {Promise<any>} Test function result
|
||||
*/
|
||||
export async function testComponent(page, componentSelector, testFn) {
|
||||
const component = await page.$(componentSelector);
|
||||
if (!component) {
|
||||
throw new Error(`Component not found: ${componentSelector}`);
|
||||
}
|
||||
|
||||
console.log(`Testing component: ${componentSelector}`);
|
||||
return await testFn(page, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture console logs from the page
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @returns {Array} Array to store console logs
|
||||
*/
|
||||
export function captureConsoleLogs(page) {
|
||||
const logs = [];
|
||||
|
||||
page.on('console', (msg) => {
|
||||
logs.push({
|
||||
type: msg.type(),
|
||||
text: msg.text(),
|
||||
location: msg.location(),
|
||||
});
|
||||
});
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get computed styles for an element
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} selector - CSS selector
|
||||
* @param {Array<string>} properties - CSS properties to get
|
||||
* @returns {Promise<Object>} Computed styles object
|
||||
*/
|
||||
export async function getComputedStyles(page, selector, properties = []) {
|
||||
return await page.evaluate(
|
||||
(sel, props) => {
|
||||
const element = document.querySelector(sel);
|
||||
if (!element) return null;
|
||||
|
||||
const styles = window.getComputedStyle(element);
|
||||
if (props.length === 0) {
|
||||
return Object.fromEntries(
|
||||
Array.from(styles).map((prop) => [
|
||||
prop,
|
||||
styles.getPropertyValue(prop),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
props.map((prop) => [prop, styles.getPropertyValue(prop)])
|
||||
);
|
||||
},
|
||||
selector,
|
||||
properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check responsive design at different viewports
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {Array<Object>} viewports - Array of {width, height, name} objects
|
||||
* @param {Function} testFn - Test function to run at each viewport
|
||||
* @returns {Promise<Array>} Test results for each viewport
|
||||
*/
|
||||
export async function testResponsive(page, viewports, testFn) {
|
||||
const results = [];
|
||||
|
||||
for (const viewport of viewports) {
|
||||
console.log(
|
||||
`Testing at ${viewport.name || `${viewport.width}x${viewport.height}`}`
|
||||
);
|
||||
await page.setViewport({ width: viewport.width, height: viewport.height });
|
||||
const result = await testFn(page, viewport);
|
||||
results.push({ viewport, result });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two screenshots for visual regression
|
||||
*
|
||||
* @param {string} baselinePath - Path to baseline screenshot
|
||||
* @param {string} currentPath - Path to current screenshot
|
||||
* @param {string} diffPath - Path to save diff image
|
||||
* @param {Object} options - Comparison options
|
||||
* @returns {Promise<Object>} Comparison result {match, diffPixels, diffPercentage}
|
||||
*/
|
||||
export async function compareScreenshots(
|
||||
baselinePath,
|
||||
currentPath,
|
||||
diffPath,
|
||||
options = {}
|
||||
) {
|
||||
const { threshold = 0.1 } = options;
|
||||
|
||||
// This function requires pixelmatch - will be implemented when pixelmatch is available
|
||||
try {
|
||||
const { default: pixelmatch } = await import('pixelmatch');
|
||||
const { PNG } = await import('pngjs');
|
||||
|
||||
const baseline = PNG.sync.read(await fs.readFile(baselinePath));
|
||||
const current = PNG.sync.read(await fs.readFile(currentPath));
|
||||
const { width, height } = baseline;
|
||||
const diff = new PNG({ width, height });
|
||||
|
||||
const diffPixels = pixelmatch(
|
||||
baseline.data,
|
||||
current.data,
|
||||
diff.data,
|
||||
width,
|
||||
height,
|
||||
{ threshold }
|
||||
);
|
||||
|
||||
const diffPercentage = (diffPixels / (width * height)) * 100;
|
||||
|
||||
if (diffPath) {
|
||||
await fs.writeFile(diffPath, PNG.sync.write(diff));
|
||||
}
|
||||
|
||||
return {
|
||||
match: diffPixels === 0,
|
||||
diffPixels,
|
||||
diffPercentage,
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Screenshot comparison requires pixelmatch and pngjs packages'
|
||||
);
|
||||
console.warn(
|
||||
'Install with: PUPPETEER_SKIP_DOWNLOAD=true yarn add -D pixelmatch pngjs'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug helper: Save page HTML and screenshot
|
||||
*
|
||||
* @param {Page} page - Puppeteer page instance
|
||||
* @param {string} debugName - Debug session name
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function debugPage(page, debugName = 'debug') {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const debugDir = `debug-output/${debugName}-${timestamp}`;
|
||||
|
||||
await fs.mkdir(debugDir, { recursive: true });
|
||||
|
||||
// Save HTML
|
||||
const html = await page.content();
|
||||
await fs.writeFile(`${debugDir}/page.html`, html);
|
||||
|
||||
// Save screenshot
|
||||
await takeScreenshot(page, `${debugDir}/screenshot.png`, { fullPage: true });
|
||||
|
||||
// Save console logs if captured
|
||||
const consoleLogs = await page.evaluate(() => {
|
||||
return window.__consoleLogs || [];
|
||||
});
|
||||
await fs.writeFile(
|
||||
`${debugDir}/console-logs.json`,
|
||||
JSON.stringify(consoleLogs, null, 2)
|
||||
);
|
||||
|
||||
console.log(`✓ Debug output saved to: ${debugDir}`);
|
||||
console.log(` - page.html`);
|
||||
console.log(` - screenshot.png`);
|
||||
console.log(` - console-logs.json`);
|
||||
}
|
||||
Loading…
Reference in New Issue