Skip to content

Commit 7e7032c

Browse files
committed
feat(tooling): add reusable screenshot capture script for PR visual reviews
Add flexible Playwright-based screenshot tool for capturing before/after visual comparisons with command-line arguments. Features: - CLI-based with flexible argument parsing - Multiple viewport presets (mobile, tablet, desktop, wide) - Element-specific screenshots using CSS selectors - Full-page or viewport-only capture modes - Customizable output directory - Automatic fallback when selectors not found Usage: bun scripts/screenshot-prs.ts --url /docs/overview --name sidebar bun scripts/screenshot-prs.ts --url / --viewport mobile --name landing bun scripts/screenshot-prs.ts --selector "aside" --name sidebar-detail This replaces the previous PR-specific configuration with a generic, reusable tool that works for any PR visual review needs.
1 parent 3960b51 commit 7e7032c

File tree

1 file changed

+156
-0
lines changed

1 file changed

+156
-0
lines changed

scripts/screenshot-prs.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Screenshot script for PR visual comparisons
4+
* Generic tool for capturing before/after screenshots
5+
*
6+
* Usage:
7+
* bun scripts/screenshot-prs.ts --url /docs/overview --name sidebar
8+
* bun scripts/screenshot-prs.ts --url / --name landing --viewport mobile
9+
* bun scripts/screenshot-prs.ts --url /docs/overview --selector "aside[class*='sidebar']" --name sidebar-detail
10+
*/
11+
12+
import { chromium } from 'playwright';
13+
import { mkdir } from 'node:fs/promises';
14+
import { join } from 'node:path';
15+
16+
const SCREENSHOTS_DIR = join(process.cwd(), 'screenshots');
17+
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
18+
19+
const VIEWPORTS = {
20+
mobile: { width: 375, height: 667 },
21+
tablet: { width: 768, height: 1024 },
22+
desktop: { width: 1280, height: 800 },
23+
wide: { width: 1920, height: 1080 },
24+
};
25+
26+
interface ScreenshotOptions {
27+
url: string;
28+
name: string;
29+
viewport?: keyof typeof VIEWPORTS | { width: number; height: number };
30+
selector?: string;
31+
fullPage?: boolean;
32+
output?: string;
33+
}
34+
35+
async function takeScreenshot(options: ScreenshotOptions) {
36+
const browser = await chromium.launch({ channel: 'chrome' });
37+
const context = await browser.newContext();
38+
const page = await context.newPage();
39+
40+
try {
41+
// Set viewport
42+
const viewport = typeof options.viewport === 'string'
43+
? VIEWPORTS[options.viewport]
44+
: options.viewport || VIEWPORTS.desktop;
45+
46+
await page.setViewportSize(viewport);
47+
48+
// Navigate
49+
const fullUrl = `${BASE_URL}${options.url}`;
50+
console.log(`📸 Navigating to ${fullUrl}`);
51+
await page.goto(fullUrl, { waitUntil: 'networkidle' });
52+
await page.waitForTimeout(1000); // Wait for animations
53+
54+
// Determine output path
55+
const outputDir = options.output || SCREENSHOTS_DIR;
56+
await mkdir(outputDir, { recursive: true });
57+
const screenshotPath = join(outputDir, `${options.name}.png`);
58+
59+
// Take screenshot
60+
if (options.selector) {
61+
const element = await page.locator(options.selector).first();
62+
if (await element.count() > 0) {
63+
await element.screenshot({ path: screenshotPath });
64+
console.log(`✅ Saved element screenshot: ${screenshotPath}`);
65+
} else {
66+
console.log(`⚠️ Selector not found, taking full page: ${options.selector}`);
67+
await page.screenshot({ path: screenshotPath, fullPage: options.fullPage || false });
68+
console.log(`✅ Saved fallback screenshot: ${screenshotPath}`);
69+
}
70+
} else {
71+
await page.screenshot({ path: screenshotPath, fullPage: options.fullPage || false });
72+
console.log(`✅ Saved screenshot: ${screenshotPath}`);
73+
}
74+
75+
return screenshotPath;
76+
} finally {
77+
await browser.close();
78+
}
79+
}
80+
81+
async function main() {
82+
const args = process.argv.slice(2);
83+
84+
// Parse arguments
85+
const options: ScreenshotOptions = {
86+
url: '/',
87+
name: 'screenshot',
88+
};
89+
90+
for (let i = 0; i < args.length; i++) {
91+
switch (args[i]) {
92+
case '--url':
93+
options.url = args[++i];
94+
break;
95+
case '--name':
96+
options.name = args[++i];
97+
break;
98+
case '--viewport':
99+
const vp = args[++i];
100+
options.viewport = vp in VIEWPORTS ? vp as keyof typeof VIEWPORTS : VIEWPORTS.desktop;
101+
break;
102+
case '--selector':
103+
options.selector = args[++i];
104+
break;
105+
case '--full-page':
106+
options.fullPage = true;
107+
break;
108+
case '--output':
109+
options.output = args[++i];
110+
break;
111+
case '--help':
112+
console.log(`
113+
Screenshot Capture Tool for PR Visual Reviews
114+
115+
Usage:
116+
bun scripts/screenshot-prs.ts [options]
117+
118+
Options:
119+
--url <path> URL path to screenshot (default: /)
120+
--name <name> Output filename without extension (default: screenshot)
121+
--viewport <preset> Viewport preset: mobile|tablet|desktop|wide (default: desktop)
122+
--selector <css> CSS selector for specific element screenshot
123+
--full-page Capture full page instead of viewport
124+
--output <dir> Output directory (default: ./screenshots)
125+
--help Show this help message
126+
127+
Examples:
128+
# Basic page screenshot
129+
bun scripts/screenshot-prs.ts --url /docs/overview --name overview
130+
131+
# Mobile viewport
132+
bun scripts/screenshot-prs.ts --url / --name landing-mobile --viewport mobile
133+
134+
# Specific element
135+
bun scripts/screenshot-prs.ts --url /docs/overview --selector "aside[class*='sidebar']" --name sidebar
136+
137+
# Full page screenshot
138+
bun scripts/screenshot-prs.ts --url /docs --name docs-full --full-page
139+
`);
140+
process.exit(0);
141+
}
142+
}
143+
144+
console.log(`\n🎬 Screenshot Capture Starting`);
145+
console.log(` Base URL: ${BASE_URL}`);
146+
console.log(` Output: ${options.output || SCREENSHOTS_DIR}\n`);
147+
148+
await takeScreenshot(options);
149+
150+
console.log(`\n✨ Screenshot complete!`);
151+
}
152+
153+
main().catch((error) => {
154+
console.error('❌ Error:', error.message);
155+
process.exit(1);
156+
});

0 commit comments

Comments
 (0)