ui-screenshots について
ui-screenshotsは、開発中のWebやグラフィカルなUIを撮影し、見た目の変化を記録するスキルです。全ページや操作状態の撮影に加え、画像を切り出して部分表示を作れます。
主な機能
- 01Webページ全体の撮影
- 02変更前後のUI記録
- 03操作状態の撮影
- 04PILによる画像切り出し
- 05Web、Electron、デスクトップ撮影の案内
活用シーン
- 01コード変更前後のUIを記録
- 02ツールチップや選択状態を撮影
- 03全ページ画像から一部を切り出し
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ github/ awesome-copilot --skill ui-screenshotsインストール後、Claude Code で/ui-screenshotsを入力して読み込まれたか確認します。
詳細
UI スクリーンショット
開発中に Web アプリやグラフィカル UI のスクリーンショットを撮影し、視覚的な変更を記録します。
このスキルを使用する場面
次のような場合にこのスキルを使用します。
- 実行中の 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. 特定セクションの撮影
1 枚のフルページスクリーンショットから、異なるセクションを切り抜きます。
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 の撮影には Python ではなく Node.js 版 Playwright が必要です
- クライアント側の描画が多い SPA では、networkidle 以外の独自の待機処理が必要になることがあります