Skip to main content
skill official · GitHub

ui-screenshots — GitHub Copilot skill: UI screenshots

A skill that photographs web pages and desktop windows so the agent can document visual changes before and after a code edit.

Install into GitHub Copilot · Skills CLI (Vercel) All methods and platforms ↓
npx skills add github/awesome-copilot --skill ui-screenshots
Open on GitHub

What ui-screenshots does

Playwright does the capture: Chromium opens the page in a tall viewport and saves the whole scrollable area once it has settled. Cropping then happens in Pillow, and this is the useful part — one raw shot can be sliced any number of ways without launching the browser again.

The instructions cover hover and selected states, with a note to move the pointer aside after clicking so a hover effect does not bleed into the shot. Screens of desktop apps are handled separately: a window is located by its title through ctypes and its visible region grabbed with mss. Electron apps go through Node.js Playwright over CDP, and that keeps working with the window minimised.

You need Python with pip install playwright Pillow -q and playwright install chromium before the first run; the window route adds pip install mss pillow. Take the before shot before touching code, or roll back with git checkout HEAD~1 afterwards. Two ways it breaks: an mss capture of a window hidden behind another one, and before/after pairs taken at different viewport widths.

What it helps with

  • shoot the interface before and after a code change
  • capture tooltips and hover states in a screenshot
  • slice a header and a main section out of one image
  • install the Playwright browser and take a first capture
  • photograph a desktop app window that Playwright cannot reach

Example prompts for the agent

  • Screenshot the page at http://localhost:3000 and save it as before.png
  • Capture the button with its tooltip showing, then move the mouse away and shoot the selected state
  • Crop the header and the main section out of screenshot-raw.png into two files

How to install ui-screenshots

1Skills CLI (Vercel)

npx skills add github/awesome-copilot --skill ui-screenshots

2GitHub CLI

gh skill install github/awesome-copilot ui-screenshots

3Manually: put the files in place

.github/skills/ui-screenshots/SKILL.md

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about ui-screenshots

How do I install ui-screenshots in GitHub Copilot?

Run npx skills add github/awesome-copilot --skill ui-screenshots, or use gh skill install github/awesome-copilot ui-screenshots. Either way the skill file ends up at .github/skills/ui-screenshots/SKILL.md.

What does ui-screenshots do?

It hands the agent a workflow for capturing pages with Playwright and trimming frames with Pillow. Separate sections deal with interactive states and desktop windows.

What do I need before using it?

Python with the playwright and Pillow packages, plus the browser from playwright install chromium. Window capture adds mss; Electron needs Node.js Playwright.

Source text

SKILL.md · MIT
---
name: ui-screenshots
description: '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.'
---

# 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

```bash
pip install playwright Pillow -q
playwright install chromium
```

## Core Workflow

### 1. Take a raw full-page screenshot

```python
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.

```python
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

```python
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:

```python
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:

```python
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.

### mss + ctypes (recommended for desktop windows)

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

```python
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.

```javascript
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

| Scenario | Tool | Notes |
|---|---|---|
| Web app (localhost) | Playwright | Proven, full DOM access |
| Electron app (VS Code) | Playwright Electron (Node.js) | Works minimized via CDP |
| Desktop app, visible window | mss + ctypes (find by title) | ~33ms per capture |
| Desktop app, behind windows | Windows Graphics Capture API | Complex setup, Win10 1903+ |
| Quick full-screen | mss | ~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

© github · MIT · github/awesome-copilot

Similar skills

Everything for Frontend and UI →

From the same set732

The whole set →
codebase-memory-mcp Explore unfamiliar code with Codebase Memory: find symbols, relationships and data flows, then check graph-based findings against source files. 259installs conventional-branch This skill creates and validates Git branches following the Conventional Branch specification. Useful when standardizing branch prefixes like feature/ or bugfix/. 243installs mongodb-performance-advisor Analyzes MongoDB queries across your codebase and audits database performance using a readonly MCP server. Identifies slow operations and optimizes aggregation pipelines. 223installs apify-integration-expert Integrates Apify Actors into JavaScript and Python codebases. Finds suitable Actors, drafts client code, and validates run outputs. 222installs diagnose This skill audits an AI workflow across five dimensions and returns scores with prioritized fixes. Use it before launch or after major workflow changes. 209installs technical-content-evaluator A subagent that edits technical courses and docs: code accuracy, chapter flow, whether the exercises exist. Useful before publishing to see if the material teaches or just links out. 209installs batch-files Helps GitHub Copilot write, debug, and maintain Windows batch scripts (.bat and .cmd). Ideal for cmd.exe task automation and Task Scheduler jobs. 206installs power-platform-architect This skill turns business requirements, use cases, and meeting transcripts into a Power Platform solution architecture with component choices and Mermaid.js diagrams. 205installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor