2025-11-05 12:15:43 +00:00
|
|
|
/**
|
|
|
|
|
* Visual Comparison Test Suite
|
2025-11-06 09:19:44 +00:00
|
|
|
* Compares new Go + HTMX CV (localhost:1999) vs old React CV (localhost:3000)
|
2025-11-05 12:15:43 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
});
|
|
|
|
|
});
|