关于 ui-screenshots
ui-screenshots 是用于开发阶段捕获网页和图形界面的技能,可记录视觉变化。它涵盖整页及交互状态截图,并通过裁剪生成页面局部图像。
主要功能
- 01网页整页截图
- 02记录修改前后的界面
- 03捕获交互状态
- 04使用 PIL 裁剪图像
- 05网页、Electron 和桌面截图指南
适用场景
- 01记录代码修改前后的界面
- 02捕获工具提示或选中状态
- 03从整页图像裁剪页面区域
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ github/ awesome-copilot --skill ui-screenshots安装后在 Claude Code 中输入/ui-screenshots确认已加载。
详细介绍
UI 截图
在开发过程中捕获 Web 应用和图形界面的截图,以记录视觉变更。
何时使用此技能
当你需要执行以下操作时,请使用此技能:
- 捕获运行中的 Web 应用的当前状态
- 记录代码变更前后的 UI
- 截取交互状态(工具提示、悬停、选中元素)
- 捕获页面的特定部分而无需重新截图
前提条件
pip install playwright Pillow -q
playwright install chromium
核心工作流
1. 截取原始整页截图
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()
- 使用高视口(height=5000),让页面无需滚动即可渲染全部内容
wait_until="networkidle"+wait_for_timeout(4000)确保异步图表加载完成full_page=True捕获整个可滚动内容
2. 查看原始图像,然后用 PIL 裁剪
不要试图通过 Playwright 的 clip 参数获得完美的裁剪结果。 它在整页截图时并不可靠。
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")
- 截取原始截图
- 查看它以了解实际的像素位置
- 根据你看到的内容用 PIL 裁剪
- 查看结果——如果不正确,重新裁剪(即时完成,无需重新截图)
3. 在裁剪上迭代,而不是在捕获上迭代
- 重新截图很慢(浏览器启动 + 页面加载 + 渲染等待)
- 重新裁剪是即时的(只需 PIL)
- 获取一张高质量的原始截图,然后按需以多种方式切片
4. 交互状态
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)
对于没有悬停效果的"选中"状态,在点击后将鼠标移开:
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. 特定区域截图
从单张整页截图中裁剪不同区域:
img.crop((0, 200, 920, 900)).save("screenshot-header.png")
img.crop((0, 900, 920, 1600)).save("screenshot-main.png")
指南
- 务必在进行任何更改之前捕获变更前状态——如果忘记,你必须回退代码才能获得变更前的截图
- 变更前/变更后配对必须使用相同的视口宽度和裁剪——否则对比毫无意义
- 在你已经更改代码后获取"变更前"状态:使用
git checkout HEAD~1 -- <files>回退,截图,然后使用git checkout HEAD -- <files>恢复 - 对于交互状态:为每个状态捕获变更前和变更后——不要假设"正常"状态的变更前截图涵盖了所有情况
- 在 Playwright 中使用
device_scale_factor=1强制 1x 像素,使截图与用户在 100% 缩放时看到的一致 - 图表需要额外等待时间——Plotly、D3 等异步渲染;在 networkidle 后至少等待 4 秒
- 窄视口会暴露渲染缺陷——某些边框/对齐问题只在特定宽度下出现
非 Web 应用截图
适用于 Playwright 无法触及的桌面应用(VS、WPF、WinForms、控制台应用、终端)。
mss + ctypes(推荐用于桌面窗口)
通过 Win32 API 按标题查找窗口,用 mss 捕获其区域。经测试每次捕获约 33ms。
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')
前提条件: pip install mss pillow
限制: 窗口必须可见(不能在其他窗口后面或最小化)。
Electron 应用(VS Code 等)
仅限 Node.js Playwright——Python Playwright 没有 electron API。通过 CDP(Chrome DevTools Protocol)捕获,而非从屏幕捕获——即使最小化也能工作。
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();
关键:必须使用 --user-data-dir=<temp>,否则 VS Code 会交给现有实例处理,启动的进程会立即退出。
决策树
| 场景 | 工具 | 备注 |
|---|---|---|
| Web 应用(localhost) | Playwright | 经过验证,完整的 DOM 访问 |
| Electron 应用(VS Code) | Playwright Electron(Node.js) | 通过 CDP 可在最小化时工作 |
| 桌面应用,可见窗口 | mss + ctypes(按标题查找) | 每次捕获约 33ms |
| 桌面应用,在其他窗口后面 | Windows Graphics Capture API | 设置复杂,需 Win10 1903+ |
| 快速全屏 | mss | 约 68ms |
限制
- Web 捕获需要本地运行的应用或可访问的 URL
- 桌面捕获(mss)要求窗口可见且未被遮挡
- Electron 捕获需要 Node.js Playwright(不是 Python)
- 某些具有大量客户端渲染的 SPA 可能需要超出 networkidle 的自定义等待逻辑
评论
支持 Markdown