191 lines
5.0 KiB
TypeScript
191 lines
5.0 KiB
TypeScript
/**
|
|
* YouTube Reference Screenshot Capture Script
|
|
*
|
|
* Captures reference screenshots from live YouTube for visual comparison testing.
|
|
* Run weekly to keep baselines current with YouTube's design changes.
|
|
*
|
|
* Usage: npx ts-node scripts/capture-youtube-reference.ts
|
|
*/
|
|
|
|
import { chromium, Browser, Page } from '@playwright/test';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
const OUTPUT_DIR = path.join(__dirname, '../specs/002-youtube-design-preview/reference');
|
|
const VIEWPORT = { width: 1920, height: 1080 };
|
|
|
|
interface CaptureConfig {
|
|
name: string;
|
|
url: string;
|
|
selector: string;
|
|
waitFor?: string;
|
|
theme?: 'light' | 'dark';
|
|
}
|
|
|
|
const CAPTURES: CaptureConfig[] = [
|
|
{
|
|
name: 'youtube-desktop-homepage',
|
|
url: 'https://www.youtube.com',
|
|
selector: 'ytd-rich-item-renderer',
|
|
waitFor: 'ytd-rich-item-renderer',
|
|
theme: 'light',
|
|
},
|
|
{
|
|
name: 'youtube-search-results',
|
|
url: 'https://www.youtube.com/results?search_query=tutorial',
|
|
selector: 'ytd-video-renderer',
|
|
waitFor: 'ytd-video-renderer',
|
|
theme: 'light',
|
|
},
|
|
{
|
|
name: 'youtube-watch-sidebar',
|
|
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
|
|
selector: 'ytd-compact-video-renderer, yt-lockup-view-model',
|
|
waitFor: 'ytd-watch-flexy',
|
|
theme: 'light',
|
|
},
|
|
{
|
|
name: 'youtube-trending',
|
|
url: 'https://www.youtube.com/feed/trending',
|
|
selector: 'ytd-video-renderer',
|
|
waitFor: 'ytd-video-renderer',
|
|
theme: 'light',
|
|
},
|
|
];
|
|
|
|
async function ensureOutputDir(): Promise<void> {
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
async function setTheme(page: Page, theme: 'light' | 'dark'): Promise<void> {
|
|
// YouTube uses document.documentElement attributes for theming
|
|
if (theme === 'dark') {
|
|
await page.evaluate(() => {
|
|
document.documentElement.setAttribute('dark', 'true');
|
|
});
|
|
}
|
|
// Light is default
|
|
}
|
|
|
|
async function captureScreenshot(
|
|
browser: Browser,
|
|
config: CaptureConfig
|
|
): Promise<string> {
|
|
const context = await browser.newContext({
|
|
viewport: VIEWPORT,
|
|
userAgent:
|
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
});
|
|
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
console.log(`Capturing: ${config.name}`);
|
|
console.log(` URL: ${config.url}`);
|
|
|
|
await page.goto(config.url, { waitUntil: 'networkidle' });
|
|
|
|
if (config.waitFor) {
|
|
await page.waitForSelector(config.waitFor, { timeout: 30000 });
|
|
}
|
|
|
|
if (config.theme) {
|
|
await setTheme(page, config.theme);
|
|
}
|
|
|
|
// Wait for images to load
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Generate timestamp for versioning
|
|
const timestamp = new Date().toISOString().split('T')[0];
|
|
const filename = `${config.name}-${timestamp}.png`;
|
|
const filepath = path.join(OUTPUT_DIR, filename);
|
|
|
|
// Full page screenshot
|
|
await page.screenshot({
|
|
path: filepath,
|
|
fullPage: false,
|
|
});
|
|
|
|
console.log(` Saved: ${filename}`);
|
|
|
|
// Also capture just the video card element if possible
|
|
try {
|
|
const element = await page.locator(config.selector).first();
|
|
if (await element.isVisible()) {
|
|
const elementFilename = `${config.name}-element-${timestamp}.png`;
|
|
await element.screenshot({
|
|
path: path.join(OUTPUT_DIR, elementFilename),
|
|
});
|
|
console.log(` Saved element: ${elementFilename}`);
|
|
}
|
|
} catch (e) {
|
|
console.log(` Could not capture element screenshot`);
|
|
}
|
|
|
|
return filepath;
|
|
} finally {
|
|
await context.close();
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
console.log('YouTube Reference Screenshot Capture');
|
|
console.log('====================================');
|
|
console.log(`Output directory: ${OUTPUT_DIR}`);
|
|
console.log(`Viewport: ${VIEWPORT.width}x${VIEWPORT.height}`);
|
|
console.log('');
|
|
|
|
await ensureOutputDir();
|
|
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
});
|
|
|
|
try {
|
|
for (const config of CAPTURES) {
|
|
try {
|
|
await captureScreenshot(browser, config);
|
|
} catch (error) {
|
|
console.error(`Error capturing ${config.name}:`, error);
|
|
}
|
|
}
|
|
|
|
// Also capture dark mode versions
|
|
console.log('\nCapturing dark mode versions...');
|
|
for (const config of CAPTURES.slice(0, 2)) {
|
|
try {
|
|
await captureScreenshot(browser, {
|
|
...config,
|
|
name: `${config.name}-dark`,
|
|
theme: 'dark',
|
|
});
|
|
} catch (error) {
|
|
console.error(`Error capturing dark mode ${config.name}:`, error);
|
|
}
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
|
|
console.log('\nCapture complete!');
|
|
console.log(`Screenshots saved to: ${OUTPUT_DIR}`);
|
|
|
|
// Generate manifest
|
|
const manifest = {
|
|
capturedAt: new Date().toISOString(),
|
|
viewport: VIEWPORT,
|
|
screenshots: fs.readdirSync(OUTPUT_DIR).filter((f) => f.endsWith('.png')),
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(OUTPUT_DIR, 'manifest.json'),
|
|
JSON.stringify(manifest, null, 2)
|
|
);
|
|
console.log('Manifest saved: manifest.json');
|
|
}
|
|
|
|
main().catch(console.error);
|