screengrabs

Capture, redact, annotate, and embed screenshots in GitHub PRs for UI changes.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "screengrabs" with this command: npx skills add inkeep/team-skills/inkeep-team-skills-screengrabs

Screengrabs

Capture, redact, annotate, and embed screenshots in GitHub PRs for UI changes.

When to use

  • Creating/updating PRs that touch frontend components, pages, or styles

  • User asks for screenshots, before/after comparisons, or PR body enrichment

  • Skip for backend-only, test-only, or non-visual changes

Prerequisites

These scripts require the following npm packages. Install them as dev dependencies in your project:

Package Purpose Install

playwright

Browser automation for screenshot capture npm add -D playwright

sharp

Image annotation (labels, borders, stitching) npm add -D sharp

tsx

TypeScript runner for scripts npm add -D tsx

After installing Playwright, download browser binaries: npx playwright install chromium

Workflow

Most screenshots require understanding the target page before capture — what state it's in, what popups appear, what content needs to be visible. The default workflow is explore → capture → verify → iterate.

  • Identify affected pages from the PR diff

  • Explore target pages — visit each page with the browser to understand layout, state, and interaction needs before writing any capture logic

  • Plan & write pre-scripts — based on what you observed, write pre-scripts for interaction needed before capture

  • Capture screenshots — run scripts/capture.ts with --pre-script

  • Verify captures — look at each captured image to confirm it shows what was expected

  • Iterate if needed — if a capture is wrong (spinner, overlay, wrong state, missing content), adjust and re-capture

  • Validate no sensitive data — run scripts/validate-sensitive.ts

  • Annotate — run scripts/annotate.ts (labels, borders, side-by-side)

  • Upload & embed — update PR body with images

Simple captures (no interaction needed): For static pages where goto + wait is sufficient, skip step 3 and omit --pre-script . Steps 2 (explore) and 5 (verify) still apply — always understand what you're capturing and confirm you got it right.

Step 1: Identify Affected Pages

Analyze the PR diff to determine which UI routes are impacted. Map changed component/page files to their corresponding URLs. If the diff only touches backend code, tests, or non-visual files, skip screenshot capture.

Step 2: Explore Target Pages

Before writing any pre-scripts or capture commands, visit each target page to understand what you're capturing. Load /browser skill and use its Visual Inspection pattern — navigate to each route, take a temporary screenshot, and read it to see what the page looks like.

What to observe

For each page, note:

  • Current layout and content — what's visible above the fold, key sections, data states

  • Popups and overlays — cookie banners, modals, onboarding tours, notification prompts

  • Loading behavior — spinners, skeleton screens, lazy-loaded content, how long until stable

  • Auth requirements — login walls, permission gates, session-dependent content

  • Dynamic state — tabs, accordions, expandable sections, content that requires interaction to reveal

  • What the PR changed — which specific elements or areas the screenshot needs to highlight

Decide what to capture

Based on exploration, decide:

  • Which view states each page needs (e.g., default tab vs. specific tab, collapsed vs. expanded)

  • Whether multiple captures per route are needed (e.g., before/after a user action)

  • What viewport and scroll position will frame the relevant change

  • What interaction is needed before each capture (popups to dismiss, elements to click, sections to scroll to)

Do not proceed to pre-script writing until you understand each page's behavior. Exploration often reveals interaction needs that aren't obvious from the diff alone (popups that appear on first visit, content behind tabs, lazy loading delays).

Step 3: Plan & Write Pre-Scripts

Load /browser skill for writing pre-scripts. A pre-script is a JS file that receives the Playwright page object and runs interaction before masking + screenshot. Use your findings from Step 2 to write targeted pre-scripts.

Pre-script contract

The file must export an async function that receives { page, url, route } :

// /tmp/pw-pre-dashboard.js module.exports = async function({ page, url, route }) { // Dismiss cookie banner await page.click('button:has-text("Accept")').catch(() => {});

// Click the "Analytics" tab await page.click('[data-tab="analytics"]'); await page.waitForTimeout(500); };

Common pre-script patterns

Dismiss popups / modals:

module.exports = async function({ page }) { // Cookie banner await page.click('button:has-text("Accept all")').catch(() => {}); // Marketing popup await page.click('[data-testid="close-modal"]').catch(() => {}); };

Navigate through a login flow:

module.exports = async function({ page }) { await page.fill('input[name="email"]', 'test@example.com'); await page.fill('input[name="password"]', 'password123'); await page.click('button[type="submit"]'); await page.waitForURL('**/dashboard'); };

Scroll to a specific section:

module.exports = async function({ page }) { await page.locator('#pricing-section').scrollIntoViewIfNeeded(); await page.waitForTimeout(300); };

Expand collapsed content:

module.exports = async function({ page }) { await page.click('button:has-text("Show more")'); await page.waitForSelector('.expanded-content', { state: 'visible' }); };

One pre-script per route — if routes need different interaction, write separate scripts and run capture once per route. If all routes share the same interaction (e.g., dismiss the same cookie banner), one script covers all.

Step 4: Capture Screenshots

Environment setup

Environment Base URL Notes

Local dev http://localhost:3000 (or your dev server port) Start your dev server first

Preview deployment Your preview URL (e.g., Vercel, Netlify, etc.) Available after PR push

Playwright server Connect via --connect ws://localhost:3001

See "Reusable server" below

Capture command

With pre-script (default for most captures)

npx tsx scripts/capture.ts
--base-url http://localhost:3000
--routes "/dashboard,/settings"
--pre-script /tmp/pw-pre-dashboard.js
--output-dir tmp/screengrabs

Simple capture (no interaction needed)

npx tsx scripts/capture.ts
--base-url http://localhost:3000
--routes "/landing,/about"
--output-dir tmp/screengrabs

Preview deployment with pre-script

npx tsx scripts/capture.ts
--base-url https://your-preview-url.example.com
--routes "/dashboard"
--pre-script /tmp/pw-pre-dismiss-popups.js
--output-dir tmp/screengrabs

All capture options

Option Default Description

--base-url <url>

required Target URL (local dev or preview)

--routes <paths>

required Comma-separated route paths

--pre-script <path>

— JS file to run on page before capture (for interaction)

--output-dir <dir>

tmp/screengrabs

Where to save PNGs and DOM text

--viewport <WxH>

1280x800

Browser viewport size

--connect <ws-url>

— Connect to existing Playwright server

--mask-selectors <s>

— Additional CSS selectors to blur

--wait <ms>

2000

Wait after page load before capture

--full-page

false

Capture full scrollable page

--auth-cookie <value>

— Session cookie for authenticated pages

Reusable Playwright server

Start a server once, reuse across multiple captures:

Terminal 1: start server

npx tsx scripts/capture.ts --serve --port 3001

Terminal 2+: connect and capture

npx tsx scripts/capture.ts
--connect ws://localhost:3001 --base-url http://localhost:3000
--routes "/..." --pre-script /tmp/pw-pre-script.js --output-dir tmp/screengrabs

Step 5: Verify Captures

Do not skip this step. After capturing, look at each screenshot to confirm it captured what you intended.

Verification checklist

For each captured image, read the PNG file and check:

  • Correct page/route — the screenshot shows the intended page, not a redirect, error page, or login wall

  • Expected content visible — the elements or sections that the PR changed are visible in the frame

  • Stable state — no spinners, skeleton loaders, or partially-rendered content

  • No unexpected overlays — cookie banners, modals, notification toasts, or tooltips aren't blocking the content

  • Proper framing — the viewport and scroll position highlight the relevant change (not cut off, not too zoomed out)

  • Redaction intact — sensitive data masking was applied correctly (passwords blurred, tokens replaced)

How to verify

Use the Read tool to view each captured PNG — it renders images visually. Compare what you see against what you observed during exploration (Step 2).

Read the captured image to verify

Read tool → tmp/screengrabs/<route-name>.png

If all captures pass verification, proceed to Step 7 (validate sensitive data). If any capture is wrong, go to Step 6.

Step 6: Iterate (if verification fails)

When a capture doesn't match expectations, diagnose and re-capture. Do not upload incorrect screenshots.

Common issues and fixes

Problem Likely cause Fix

Spinner or skeleton visible Insufficient wait time Increase --wait (e.g., --wait 5000 ) or add waitForSelector in pre-script

Cookie banner or modal blocking content Pre-script didn't dismiss it Add dismiss logic to pre-script (.catch(() => {}) for optional popups)

Wrong tab or section visible Pre-script didn't navigate to correct state Update pre-script to click the right tab/accordion/section

Login wall or auth error Missing auth cookie or expired session Use --auth-cookie or add login flow to pre-script

Content cut off or wrong scroll position Default viewport insufficient Adjust --viewport , add scrollIntoViewIfNeeded() in pre-script, or use --full-page

Partially loaded images or assets Network still loading Add waitForLoadState('networkidle') in pre-script after interaction

Iteration process

  • Identify which captures failed verification and why

  • Adjust the pre-script, capture parameters, or both

  • Re-run scripts/capture.ts for the affected routes only

  • Re-verify (Step 5) — read the new images and confirm they're correct

  • Repeat if needed — maximum 3 iterations per route before stopping to reassess the approach

When to stop iterating

  • After 3 failed attempts for the same route, reconsider whether the page is in a capturable state (is the dev server running correctly? is the feature complete?)

  • If the issue is environmental (server not running, deployment not ready), fix the environment rather than adjusting capture parameters

Step 7: Validate Sensitive Data

Always run before uploading to GitHub.

npx tsx scripts/validate-sensitive.ts
--dir ./screengrabs

The script checks .dom-text.txt files (saved by capture) for:

  • API keys (sk- , sk-ant- , AKIA , sk_live_ )

  • Tokens (Bearer, JWT, GitHub PATs)

  • PEM private keys

  • Connection strings with credentials

Exit code 1 = sensitive data found. Re-capture with additional --mask-selectors or fix the source before proceeding.

Pre-capture masking (automatic)

The capture script automatically masks these before taking screenshots:

Selector / Pattern What it catches

input[type="password"]

Password fields

Text matching sk- , Bearer , eyJ , ghp_ , PEM headers In-page tokens/keys

Add more with --mask-selectors "selector1,selector2" .

Step 8: Annotate Images

Add "Before" label with red border

npx tsx scripts/annotate.ts
--input before.png --label "Before" --border "#ef4444" --output before-labeled.png

Add "After" label with green border

npx tsx scripts/annotate.ts
--input after.png --label "After" --border "#22c55e" --output after-labeled.png

Side-by-side comparison

npx tsx scripts/annotate.ts
--stitch before.png after.png --labels "Before,After" --output comparison.png

Step 9: Upload & Embed in PR

Images in PR markdown need permanent URLs.

Primary: Bunny Edge Storage (programmatic, permanent CDN URLs):

const helpers = require('./lib/helpers'); const result = await helpers.uploadToBunnyStorage( './tmp/screengrabs/dashboard-labeled.png', pr-${prNumber}/dashboard-before.png ); // result.url → "https://{cdn-hostname}/pr-123/dashboard-before.png" (permanent)

Requires BUNNY_STORAGE_API_KEY , BUNNY_STORAGE_ZONE_NAME , BUNNY_STORAGE_HOSTNAME env vars. Set up with ./secrets/setup.sh --skill screengrabs (see secrets/secrets.json).

Fallback: GitHub drag-and-drop — drag images into the PR description editor on GitHub. GitHub generates permanent CDN URLs automatically.

Update PR body

gh pr edit {pr-number} --body "$(cat pr-body.md)"

PR body templates

Use the templates in references/pr-templates.md for consistent formatting. Include:

  • Visual Changes section with before/after screenshots

  • Test URLs section with links to preview deployment pages

  • Summary of what changed and why

Additional Resources

  • references/pr-templates.md — PR body markdown templates

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

write-agent

No summary provided by upstream source.

Repository SourceNeeds Review
General

ship

No summary provided by upstream source.

Repository SourceNeeds Review
General

spec

No summary provided by upstream source.

Repository SourceNeeds Review
General

implement

No summary provided by upstream source.

Repository SourceNeeds Review