Latest Updated Pages
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
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
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
Reference in New Issue
Block a user