mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-18 18:32:28 +08:00
feat: screenshot element/region clipping (v0.3.7) (#56)
* feat: screenshot element/region clipping (--clip, --viewport, CSS/@ref)
Add element crop (CSS selector or @ref), region clip (--clip x,y,w,h),
and viewport-only (--viewport) modes to the screenshot command. Uses
Playwright's native locator.screenshot() and page.screenshot({ clip }).
Full page remains the default. Includes 10 new tests covering all modes
and error paths.
* chore: bump version and changelog (v0.3.7)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add screenshot modes to BROWSER.md command reference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,11 +106,63 @@ export async function handleMetaCommand(
|
||||
|
||||
// ─── Visual ────────────────────────────────────────
|
||||
case 'screenshot': {
|
||||
// Parse priority: flags (--viewport, --clip) → selector (@ref, CSS) → output path
|
||||
const page = bm.getPage();
|
||||
const screenshotPath = args[0] || '/tmp/browse-screenshot.png';
|
||||
validateOutputPath(screenshotPath);
|
||||
await page.screenshot({ path: screenshotPath, fullPage: true });
|
||||
return `Screenshot saved: ${screenshotPath}`;
|
||||
let outputPath = '/tmp/browse-screenshot.png';
|
||||
let clipRect: { x: number; y: number; width: number; height: number } | undefined;
|
||||
let targetSelector: string | undefined;
|
||||
let viewportOnly = false;
|
||||
|
||||
const remaining: string[] = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--viewport') {
|
||||
viewportOnly = true;
|
||||
} else if (args[i] === '--clip') {
|
||||
const coords = args[++i];
|
||||
if (!coords) throw new Error('Usage: screenshot --clip x,y,w,h [path]');
|
||||
const parts = coords.split(',').map(Number);
|
||||
if (parts.length !== 4 || parts.some(isNaN))
|
||||
throw new Error('Usage: screenshot --clip x,y,width,height — all must be numbers');
|
||||
clipRect = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
|
||||
} else if (args[i].startsWith('--')) {
|
||||
throw new Error(`Unknown screenshot flag: ${args[i]}`);
|
||||
} else {
|
||||
remaining.push(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Separate target (selector/@ref) from output path
|
||||
for (const arg of remaining) {
|
||||
if (arg.startsWith('@e') || arg.startsWith('@c') || arg.startsWith('.') || arg.startsWith('#') || arg.includes('[')) {
|
||||
targetSelector = arg;
|
||||
} else {
|
||||
outputPath = arg;
|
||||
}
|
||||
}
|
||||
|
||||
validateOutputPath(outputPath);
|
||||
|
||||
if (clipRect && targetSelector) {
|
||||
throw new Error('Cannot use --clip with a selector/ref — choose one');
|
||||
}
|
||||
if (viewportOnly && clipRect) {
|
||||
throw new Error('Cannot use --viewport with --clip — choose one');
|
||||
}
|
||||
|
||||
if (targetSelector) {
|
||||
const resolved = bm.resolveRef(targetSelector);
|
||||
const locator = 'locator' in resolved ? resolved.locator : page.locator(resolved.selector);
|
||||
await locator.screenshot({ path: outputPath, timeout: 5000 });
|
||||
return `Screenshot saved (element): ${outputPath}`;
|
||||
}
|
||||
|
||||
if (clipRect) {
|
||||
await page.screenshot({ path: outputPath, clip: clipRect });
|
||||
return `Screenshot saved (clip ${clipRect.x},${clipRect.y},${clipRect.width},${clipRect.height}): ${outputPath}`;
|
||||
}
|
||||
|
||||
await page.screenshot({ path: outputPath, fullPage: !viewportOnly });
|
||||
return `Screenshot saved${viewportOnly ? ' (viewport)' : ''}: ${outputPath}`;
|
||||
}
|
||||
|
||||
case 'pdf': {
|
||||
|
||||
Reference in New Issue
Block a user