/** * Visual Comparison Test Suite * Compares new Go + HTMX CV (localhost:1999) vs old React CV (localhost:3000) */ const { test, expect } = require('@playwright/test'); const fs = require('fs'); const path = require('path'); const OLD_SITE = 'http://localhost:3000'; const NEW_SITE = 'http://localhost:1999'; const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots'); // Ensure screenshots directory exists if (!fs.existsSync(SCREENSHOTS_DIR)) { fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); } test.describe('Visual Comparison: New vs Old CV', () => { test('Full page screenshots', async ({ browser }) => { const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const pageOld = await contextOld.newPage(); const pageNew = await contextNew.newPage(); // Load both sites await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); // Take full page screenshots await pageOld.screenshot({ path: path.join(SCREENSHOTS_DIR, 'old-fullpage.png'), fullPage: true }); await pageNew.screenshot({ path: path.join(SCREENSHOTS_DIR, 'new-fullpage.png'), fullPage: true }); console.log('✓ Full page screenshots saved'); await contextOld.close(); await contextNew.close(); }); test('Header section comparison', async ({ browser }) => { const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const pageOld = await contextOld.newPage(); const pageNew = await contextNew.newPage(); await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); // Screenshot header sections const headerOld = await pageOld.locator('.cv-title-badges-header, [class*="header"]').first(); const headerNew = await pageNew.locator('.cv-title-badges-header').first(); if (await headerOld.count() > 0) { await headerOld.screenshot({ path: path.join(SCREENSHOTS_DIR, 'old-header.png') }); } if (await headerNew.count() > 0) { await headerNew.screenshot({ path: path.join(SCREENSHOTS_DIR, 'new-header.png') }); } console.log('✓ Header screenshots saved'); await contextOld.close(); await contextNew.close(); }); test('Badge measurements comparison', async ({ browser }) => { const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const pageOld = await contextOld.newPage(); const pageNew = await contextNew.newPage(); await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); // Measure badge elements const measurements = { old: {}, new: {} }; // New site badge measurements const badgeNew = pageNew.locator('.title-badge').first(); if (await badgeNew.count() > 0) { const box = await badgeNew.boundingBox(); const styles = await badgeNew.evaluate(el => { const computed = window.getComputedStyle(el); return { height: computed.height, padding: computed.padding, fontSize: computed.fontSize, fontWeight: computed.fontWeight, color: computed.color, backgroundColor: computed.backgroundColor, borderRadius: computed.borderRadius, display: computed.display, alignItems: computed.alignItems }; }); measurements.new.badge = { box, styles }; } // Old site badge measurements const badgeOld = pageOld.locator('.title-badge, [class*="badge"]').first(); if (await badgeOld.count() > 0) { const box = await badgeOld.boundingBox(); const styles = await badgeOld.evaluate(el => { const computed = window.getComputedStyle(el); return { height: computed.height, padding: computed.padding, fontSize: computed.fontSize, fontWeight: computed.fontWeight, color: computed.color, backgroundColor: computed.backgroundColor, borderRadius: computed.borderRadius, display: computed.display, alignItems: computed.alignItems }; }); measurements.old.badge = { box, styles }; } // Save measurements fs.writeFileSync( path.join(SCREENSHOTS_DIR, 'badge-measurements.json'), JSON.stringify(measurements, null, 2) ); console.log('✓ Badge measurements saved'); console.log('\nBadge Comparison:'); console.log('OLD:', JSON.stringify(measurements.old.badge?.styles, null, 2)); console.log('NEW:', JSON.stringify(measurements.new.badge?.styles, null, 2)); await contextOld.close(); await contextNew.close(); }); test('Typography comparison', async ({ browser }) => { const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const pageOld = await contextOld.newPage(); const pageNew = await contextNew.newPage(); await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); const typography = { old: {}, new: {} }; // Selectors to compare const selectors = { name: '.cv-name', sidebarTitle: '.sidebar-title', sectionTitle: '.section-title', badge: '.title-badge' }; // Measure new site typography for (const [key, selector] of Object.entries(selectors)) { const element = pageNew.locator(selector).first(); if (await element.count() > 0) { typography.new[key] = await element.evaluate(el => { const computed = window.getComputedStyle(el); return { fontFamily: computed.fontFamily, fontSize: computed.fontSize, fontWeight: computed.fontWeight, lineHeight: computed.lineHeight, color: computed.color, letterSpacing: computed.letterSpacing }; }); } } // Measure old site typography for (const [key, selector] of Object.entries(selectors)) { const element = pageOld.locator(selector).first(); if (await element.count() > 0) { typography.old[key] = await element.evaluate(el => { const computed = window.getComputedStyle(el); return { fontFamily: computed.fontFamily, fontSize: computed.fontSize, fontWeight: computed.fontWeight, lineHeight: computed.lineHeight, color: computed.color, letterSpacing: computed.letterSpacing }; }); } } // Save typography comparison fs.writeFileSync( path.join(SCREENSHOTS_DIR, 'typography-comparison.json'), JSON.stringify(typography, null, 2) ); console.log('✓ Typography comparison saved'); console.log('\nTypography Comparison:'); console.log('OLD:', JSON.stringify(typography.old, null, 2)); console.log('NEW:', JSON.stringify(typography.new, null, 2)); await contextOld.close(); await contextNew.close(); }); test('Sidebar comparison', async ({ browser }) => { const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const pageOld = await contextOld.newPage(); const pageNew = await contextNew.newPage(); await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); // Screenshot sidebars const sidebarOld = pageOld.locator('.cv-sidebar, [class*="sidebar"]').first(); const sidebarNew = pageNew.locator('.cv-sidebar').first(); if (await sidebarOld.count() > 0) { await sidebarOld.screenshot({ path: path.join(SCREENSHOTS_DIR, 'old-sidebar.png') }); } if (await sidebarNew.count() > 0) { await sidebarNew.screenshot({ path: path.join(SCREENSHOTS_DIR, 'new-sidebar.png') }); } // Measure sidebar styles const sidebarComparison = { old: {}, new: {} }; if (await sidebarNew.count() > 0) { sidebarComparison.new = await sidebarNew.evaluate(el => { const computed = window.getComputedStyle(el); return { backgroundColor: computed.backgroundColor, padding: computed.padding, width: computed.width, minWidth: computed.minWidth }; }); } if (await sidebarOld.count() > 0) { sidebarComparison.old = await sidebarOld.evaluate(el => { const computed = window.getComputedStyle(el); return { backgroundColor: computed.backgroundColor, padding: computed.padding, width: computed.width, minWidth: computed.minWidth }; }); } fs.writeFileSync( path.join(SCREENSHOTS_DIR, 'sidebar-comparison.json'), JSON.stringify(sidebarComparison, null, 2) ); console.log('✓ Sidebar comparison saved'); console.log('\nSidebar Comparison:'); console.log('OLD:', JSON.stringify(sidebarComparison.old, null, 2)); console.log('NEW:', JSON.stringify(sidebarComparison.new, null, 2)); await contextOld.close(); await contextNew.close(); }); test('Critical elements style extraction', async ({ browser }) => { const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const pageOld = await contextOld.newPage(); const pageNew = await contextNew.newPage(); await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); const criticalElements = [ '.cv-title-badges-header', '.title-badge', '.badge-separator', '.sidebar-title', '.section-title', '.cv-name' ]; const styleComparison = { old: {}, new: {} }; // Extract from new site for (const selector of criticalElements) { const element = pageNew.locator(selector).first(); if (await element.count() > 0) { styleComparison.new[selector] = await element.evaluate(el => { const computed = window.getComputedStyle(el); const styles = {}; for (let i = 0; i < computed.length; i++) { const prop = computed[i]; styles[prop] = computed.getPropertyValue(prop); } return styles; }); } } // Extract from old site for (const selector of criticalElements) { const element = pageOld.locator(selector).first(); if (await element.count() > 0) { styleComparison.old[selector] = await element.evaluate(el => { const computed = window.getComputedStyle(el); const styles = {}; for (let i = 0; i < computed.length; i++) { const prop = computed[i]; styles[prop] = computed.getPropertyValue(prop); } return styles; }); } } fs.writeFileSync( path.join(SCREENSHOTS_DIR, 'critical-elements-full-styles.json'), JSON.stringify(styleComparison, null, 2) ); console.log('✓ Critical elements styles extracted'); await contextOld.close(); await contextNew.close(); }); });