Generated banner for UI Screenshots
Generated logo for UI Screenshots

UI Screenshots

GitHubOfficialFirst committed by nohwndDistributed by GitHub

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk

From the author

Capture screenshots of web apps during development using Playwright and PIL. Supports full-page captures, interactive states, and an iterate-on-crop workflow that avoids slow re-screenshots.
— the description by nohwnd

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a robust workflow for documenting visual UI states during development. It emphasizes an efficient 'capture-once, crop-many' strategy using Playwright for web automation and PIL for image processing. By separating the heavy lifting of browser rendering from the lightweight task of image cropping, developers can iterate on visual documentation without the overhead of repeated page loads. The guide also covers specialized techniques for capturing interactive states, desktop windows, and Electron applications.

What it does

  • Automates full-page web screenshots using Playwright with configurable viewports.
  • Implements an iterative cropping workflow using PIL to avoid redundant browser captures.
  • Handles interactive UI states like hovers, tooltips, and selected elements.
  • Provides methods for capturing desktop application windows using mss and Win32 APIs.
  • Offers specific guidance for capturing Electron-based apps via Node.js Playwright.
  • Establishes best practices for consistent before-and-after visual comparisons.

When to use it

  • When you need to document UI changes before and after code modifications.
  • When you need to capture specific interactive states like tooltips or dropdowns.
  • When you need to generate multiple cropped views from a single full-page render.
  • When you need to capture desktop application windows that are currently visible.
  • When you need to automate visual regression testing for web or Electron apps.

Advantages

  • Significantly faster iteration by decoupling image capture from image cropping.
  • Ensures consistent visual comparisons by using fixed viewport and cropping parameters.
  • Supports complex UI states that require mouse interaction or asynchronous rendering.
  • Provides a clear decision tree for selecting the right tool based on the application type.
  • Reduces the need for manual screenshotting and cropping tasks.

Usage examples

“How do I capture a full-page screenshot of my local web app and then crop it to a specific header section?”
The agent provides a Python script using Playwright to save a raw full-page image, followed by a PIL snippet to crop and save the header section.
“I need to capture a tooltip state on my button, but it disappears when I move the mouse.”
The agent explains how to use Playwright's locator hover method followed by a screenshot command to capture the element in its active state.
“How can I take a screenshot of a specific desktop window like VS Code?”
The agent provides a Python script using mss and ctypes to find the window by title and capture its specific coordinates.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/github/ui-screenshots/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip ui-screenshots.zip -d .agents/skills

Documentation by nohwnd

UI Screenshots

Capture screenshots of web apps and graphical UIs during development to document visual changes.

When to Use This Skill

Use this skill when you need to:

  • Capture the current state of a running web app

  • Document a UI before and after a code change

  • Screenshot interactive states (tooltips, hovers, selected elements)

  • Capture specific sections of a page without re-screenshotting

Prerequisites

pip install playwright Pillow -q
playwright install chromium

Core Workflow

1. Take a raw full-page screenshot
from playwright.async_api import async_playwright

async def capture(url="http://localhost:3000", out="screenshot-raw.png", width=1400, height=5000):
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": width, "height": height})
        await page.goto(url, wait_until="networkidle")
        await page.wait_for_timeout(4000)  # let charts/animations render
        await page.screenshot(path=out, full_page=True)
        await browser.close()
  • Use a tall viewport (height=5000) so the page renders everything without scrolling

  • wait_until="networkidle" + wait_for_timeout(4000) ensures async charts load

  • full_page=True captures the entire scrollable content

2. View the raw image, then crop with PIL

Do NOT try to get perfect crops via Playwright's clip parameter. It's unreliable with full-page captures.

from PIL import Image

img = Image.open("screenshot-raw.png")
cropped = img.crop((left, top, right, bottom))  # adjust based on what you see
cropped.save("screenshot-final.png")
  1. Take the raw screenshot

  2. View it to see actual pixel positions

  3. Crop with PIL based on what you see

  4. View the result — if not right, re-crop (instant, no re-screenshot needed)

3. Iterate on crop, not on capture
  • Re-screenshotting is slow (browser launch + page load + render wait)

  • Re-cropping is instant (just PIL)

  • Get one good raw capture, then slice it as many ways as needed

4. Interactive states
element = page.locator("selector").first
await element.hover()
await page.wait_for_timeout(1000)  # let tooltip appear
await page.screenshot(path="screenshot-hover.png", full_page=True)

For "selected" state without hover effect, move the mouse away after clicking:

await element.click()
await page.mouse.move(300, 300)  # move away so hover doesn't show
await page.wait_for_timeout(500)
await page.screenshot(path="screenshot-selected.png", full_page=True)
5. Section-specific captures

Crop different sections from a single full-page screenshot:

img.crop((0, 200, 920, 900)).save("screenshot-header.png")
img.crop((0, 900, 920, 1600)).save("screenshot-main.png")

Guidelines

  1. Always capture before state BEFORE making any changes — if you forget, you have to revert code to get a before shot

  2. Before/after pairs must use the same viewport width and crop — otherwise the comparison is useless

  3. To get a "before" after you already changed code: use git checkout HEAD~1 -- <files> to revert, screenshot, then git checkout HEAD -- <files> to restore

  4. For interactive states: capture before AND after for each state — don't assume the "normal" before covers all cases

  5. Use device_scale_factor=1 in Playwright to force 1x pixels so screenshots match what users see at 100% zoom

  6. Charts need extra wait time — Plotly, D3, etc. render asynchronously; 4s minimum after networkidle

  7. Narrow viewport reveals rendering bugs — some border/alignment issues only appear at specific widths

Non-Web App Screenshots

For desktop apps (VS, WPF, WinForms, console apps, terminals) where Playwright can't reach.

Find a window by title via Win32 API, capture its region with mss. Tested at ~33ms per capture.

import ctypes
from ctypes import c_int, Structure, byref, windll
import mss
from PIL import Image

user32 = windll.user32

def find_window(title_contains):
    """Find visible windows matching a title substring."""
    results = []
    WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, ctypes.c_void_p)
    def cb(hwnd, _):
        if user32.IsWindowVisible(hwnd):
            buf = ctypes.create_unicode_buffer(256)
            user32.GetWindowTextW(hwnd, buf, 256)
            if title_contains.lower() in buf.value.lower():
                results.append((hwnd, buf.value))
        return True
    user32.EnumWindows(WNDENUMPROC(cb), 0)
    return results

def capture_window(title_contains, output_path):
    """Capture a window by title substring."""
    windows = find_window(title_contains)
    if not windows:
        raise ValueError(f"No window matching '{title_contains}'")
    hwnd = windows[0][0]

    class RECT(Structure):
        _fields_ = [('left', c_int), ('top', c_int), ('right', c_int), ('bottom', c_int)]
    rect = RECT()
    user32.GetWindowRect(hwnd, byref(rect))
    w, h = rect.right - rect.left, rect.bottom - rect.top

    with mss.mss() as sct:
        shot = sct.grab({'left': rect.left, 'top': rect.top, 'width': w, 'height': h})
        img = Image.frombytes('RGB', shot.size, shot.rgb)
        img.save(output_path)
        return img

# Usage:
capture_window('Visual Studio Code', 'vscode-capture.png')

Prerequisites: pip install mss pillow Limitation: Window must be visible (not behind other windows or minimized).

Electron apps (VS Code, etc.)

Node.js Playwright only — Python Playwright has no electron API. Captures via CDP (Chrome DevTools Protocol), not from the screen — works even while minimized.

const { _electron: electron } = require('playwright');
const app = await electron.launch({
    executablePath: 'C:\\Program Files\\Microsoft VS Code\\Code.exe',
    args: ['--new-window', '--disable-extensions', '--user-data-dir=' + tmpDir]
});
const window = await app.firstWindow();
await window.waitForLoadState('domcontentloaded');

// Minimize immediately — captures still work via CDP
await app.evaluate(({ BrowserWindow }) => {
    BrowserWindow.getAllWindows()[0].minimize();
});

await window.screenshot({ path: 'capture.png' }); // works while minimized!
await app.close();

Critical: --user-data-dir=<temp> is required or VS Code hands off to the existing instance and the launched process exits immediately.

Decision tree
ScenarioToolNotes
Web app (localhost)PlaywrightProven, full DOM access
Electron app (VS Code)Playwright Electron (Node.js)Works minimized via CDP
Desktop app, visible windowmss + ctypes (find by title)~33ms per capture
Desktop app, behind windowsWindows Graphics Capture APIComplex setup, Win10 1903+
Quick full-screenmss~68ms

Limitations

  • Web capture requires a locally running app or accessible URL

  • Desktop capture (mss) requires the window to be visible and unobstructed

  • Electron capture requires Node.js Playwright (not Python)

  • Some SPAs with heavy client-side rendering may need custom wait logic beyond networkidle

Files

Requirements and compatibility

Written for: GitHub Copilot

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

First committed by
GitHub avatar of nohwndnohwnd
Publisher
GitHub (Official)
Distributed by
GitHub
Source
github/awesome-copilot · skills/ui-screenshots · commit 797337f
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Contributors
nohwnd

AutoGineer is not affiliated with or endorsed by nohwnd or GitHub.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download ui-screenshots.zip