Files
CompanySite/scripts/capture-theme-screenshots.js
Clintchiz 0614ae6f85
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
Deploy to Production / Build & Verify (push) Failing after 13s
Ping Search Engines / Notify Search Engines (push) Successful in 3s
Deploy to Production / Pre-Deploy Tests (push) Has been skipped
Deploy to Production / Deploy to Railway (push) Has been skipped
Deploy to Production / Deploy to Render (push) Has been skipped
Deploy to Production / Deploy to VPS (PM2) (push) Has been skipped
Deploy to Production / Deploy to Fly.io (push) Has been skipped
Deploy to Production / Post-Deploy Verification (push) Has been skipped
Deploy to Production / Notify on Failure (push) Successful in 1s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m36s
E2E Test Suite / Form Interaction Tests (push) Failing after 12m6s
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 11m46s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 9m31s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 11m5s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 15m24s
E2E Test Suite / Security Header Tests (push) Failing after 7m55s
E2E Test Suite / Test Report Summary (push) Failing after 6s
E2E Test Suite / Mobile Device Tests (push) Failing after 3h12m28s
Uptime Monitor / Health & Response Time (push) Successful in 5s
Uptime Monitor / SSL Certificate (push) Successful in 3s
Uptime Monitor / Send Alerts (push) Has been skipped
Uptime Monitor / Record Uptime Success (push) Successful in 2s
Latest Updated Pages
2026-03-22 14:37:17 +05:30

259 lines
8.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Theme Screenshot Capture Script
* WorkRoot IT Solutions — Dark/Light Mode Visual Reference
*
* Generates full-page screenshots of all major pages in both themes
* across desktop, tablet, and mobile viewports.
*
* Usage:
* 1. Start dev server: npm run dev
* 2. Run this script: node scripts/capture-theme-screenshots.js
*
* Output: .agents/frontend-specialist/screenshots/{light,dark}/{page}-{viewport}.png
*
* Prerequisites:
* npm install playwright
* npx playwright install chromium
*/
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');
// ─── Configuration ────────────────────────────────────────────────────────────
const BASE_URL = process.env.BASE_URL || 'http://localhost:4321';
const PAGES = [
{ name: 'home', path: '/' },
{ name: 'about', path: '/about' },
{ name: 'services', path: '/services' },
{ name: 'portfolio', path: '/portfolio' },
{ name: 'contact', path: '/contact' },
];
const VIEWPORTS = [
{ name: 'desktop', width: 1440, height: 900 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'mobile', width: 390, height: 844 },
];
const THEMES = ['light', 'dark'];
/** Milliseconds to wait after theme switch for transitions to settle */
const THEME_SETTLE_MS = 300;
/** Milliseconds to wait after page load for animations to complete */
const PAGE_SETTLE_MS = 500;
/** Output directory (relative to project root) */
const OUTPUT_DIR = path.join(
__dirname,
'..',
'.agents',
'frontend-specialist',
'screenshots'
);
// ─── Helpers ──────────────────────────────────────────────────────────────────
function ensureDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
function screenshotPath(theme, pageName, viewportName) {
return path.join(OUTPUT_DIR, theme, `${pageName}-${viewportName}.png`);
}
async function waitForPageReady(page) {
// Wait for network idle + animations to complete
await page.waitForLoadState('networkidle');
await page.waitForTimeout(PAGE_SETTLE_MS);
}
async function setTheme(page, theme) {
await page.evaluate((t) => {
if (t === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
try {
localStorage.setItem('theme', t);
} catch (_) {
// localStorage may be unavailable in some contexts
}
}, theme);
// Wait for CSS transitions to settle
await page.waitForTimeout(THEME_SETTLE_MS);
}
async function captureScreenshot(page, filePath, fullPage = true) {
await page.screenshot({
path: filePath,
fullPage,
animations: 'disabled', // Freeze CSS/JS animations for clean captures
});
const sizeKB = Math.round(fs.statSync(filePath).size / 1024);
console.log(` ✓ Saved: ${path.relative(process.cwd(), filePath)} (${sizeKB}KB)`);
}
// ─── Main ─────────────────────────────────────────────────────────────────────
async function main() {
console.log('');
console.log('═══════════════════════════════════════════════════════════');
console.log(' WorkRoot Theme Screenshot Capture');
console.log(` Base URL: ${BASE_URL}`);
console.log(` Output: ${OUTPUT_DIR}`);
console.log('═══════════════════════════════════════════════════════════');
console.log('');
// Create output directories
for (const theme of THEMES) {
ensureDir(path.join(OUTPUT_DIR, theme));
}
const browser = await chromium.launch({
headless: true,
});
let totalScreenshots = 0;
let errors = [];
try {
for (const viewport of VIEWPORTS) {
console.log(`\n── Viewport: ${viewport.name} (${viewport.width}×${viewport.height}) ──`);
const context = await browser.newContext({
viewport: { width: viewport.width, height: viewport.height },
deviceScaleFactor: 2, // Retina quality for crisp screenshots
colorScheme: 'light', // Start light; we override via JS
});
const page = await context.newPage();
// Disable animations globally for cleaner screenshots
await page.addInitScript(() => {
const style = document.createElement('style');
style.textContent = `
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
`;
document.head.appendChild(style);
});
for (const pageConfig of PAGES) {
const url = `${BASE_URL}${pageConfig.path}`;
console.log(`\n Page: ${pageConfig.name} (${url})`);
for (const theme of THEMES) {
try {
// Navigate to page
await page.goto(url, { waitUntil: 'domcontentloaded' });
await waitForPageReady(page);
// Set theme
await setTheme(page, theme);
// Capture screenshot
const filePath = screenshotPath(theme, pageConfig.name, viewport.name);
await captureScreenshot(page, filePath);
totalScreenshots++;
} catch (err) {
const label = `${theme}/${pageConfig.name}-${viewport.name}`;
errors.push({ label, error: err.message });
console.error(` ✗ Failed ${label}: ${err.message}`);
}
}
}
await context.close();
}
} finally {
await browser.close();
}
// Summary
console.log('');
console.log('═══════════════════════════════════════════════════════════');
console.log(` Done! Captured ${totalScreenshots} screenshots`);
if (errors.length > 0) {
console.log(` ⚠ ${errors.length} error(s):`);
for (const { label, error } of errors) {
console.log(` • ${label}: ${error}`);
}
}
console.log('═══════════════════════════════════════════════════════════');
console.log('');
// Generate index file listing all screenshots
generateIndex(totalScreenshots, errors);
}
// ─── Index Generator ──────────────────────────────────────────────────────────
function generateIndex(total, errors) {
const lines = [
'# Theme Screenshots Index',
'',
`Generated: ${new Date().toISOString()}`,
`Total: ${total} screenshots`,
errors.length > 0 ? `Errors: ${errors.length}` : 'All captures successful ✓',
'',
'## File Listing',
'',
];
for (const theme of THEMES) {
lines.push(`### ${theme.charAt(0).toUpperCase() + theme.slice(1)} Theme`);
lines.push('');
lines.push('| Page | Desktop | Tablet | Mobile |');
lines.push('|------|---------|--------|--------|');
for (const pageConfig of PAGES) {
const cells = VIEWPORTS.map((vp) => {
const file = `${pageConfig.name}-${vp.name}.png`;
const fullPath = path.join(OUTPUT_DIR, theme, file);
const exists = fs.existsSync(fullPath);
if (!exists) return '❌ missing';
const sizeKB = Math.round(fs.statSync(fullPath).size / 1024);
return `[${file}](./${theme}/${file}) (${sizeKB}KB)`;
});
lines.push(`| ${pageConfig.name} | ${cells.join(' | ')} |`);
}
lines.push('');
}
if (errors.length > 0) {
lines.push('## Errors');
lines.push('');
for (const { label, error } of errors) {
lines.push(`- **${label}**: ${error}`);
}
lines.push('');
}
lines.push('---');
lines.push('*Generated by `scripts/capture-theme-screenshots.js`*');
const indexPath = path.join(OUTPUT_DIR, 'INDEX.md');
fs.writeFileSync(indexPath, lines.join('\n'));
console.log(` Index written: ${path.relative(process.cwd(), indexPath)}`);
}
// ─── Entry Point ──────────────────────────────────────────────────────────────
main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});