feat: youtube preview
This commit is contained in:
190
scripts/capture-youtube-reference.ts
Normal file
190
scripts/capture-youtube-reference.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user