| name | playwright-figma-scrape |
| description | 用 Playwright MCP 系統性抓取 Figma 設計稿(screenshot / design token / layer inventory)的完整工作流。
適用於:Figma MCP 限額耗盡 / 需要批量抓取多個 frame / 需要從 DOM 提取 token / 需要建立 frame-to-code 對照表。
觸發條件:
- 用戶說「抓 Figma 設計稿」「批量截圖 Figma」「提 Figma token」
- Figma MCP 回報 "tool call limit" / "View seat"
- 需要 Phase 對齊驗證(Figma vs 代碼實現)
- 需要更新 design token 文件
|
為什麼用這個 skill
Figma 官方 MCP 工具(get_design_context、get_screenshot、get_metadata)有 View seat 配額限制,超限後所有 Figma MCP 工具同池鎖死。Playwright MCP 不算 Figma 配額,可以:
- 批量抓取 — 一次抓 N 個 frame(MCP 只能一個一個)
- DOM 提取 — 從 Figma web UI 讀 Properties panel 提 token
- 畫 layer 結構圖 — 列出所有 node-id + 名稱建立 inventory
- 系統對齊驗證 — Figma 實際值 vs 代碼當前值逐項對照
唯一缺陷:Playwright 拿不到 Figma MCP 獨有的 Code Connect 代碼映射。但截圖、token、layer 結構、視覺對比都完全夠用。
環境前置條件
必須
mcp__playwright__browser_* tools 可用(Claude Code session 有加載 Playwright MCP)
- Figma account 已登入(瀏覽器有 session cookie 或 Chrome extension bridge 模式)
- 知道 target Figma
fileKey(從 URL 抽出)
推薦
- 知道至少一個
nodeId 當起始點(否則從 recents 找)
- 有 Figma PAT(Personal Access Token)備用 REST API 路徑
檢測工具可用性
ToolSearch query="select:mcp__playwright__browser_navigate,mcp__playwright__browser_resize,mcp__playwright__browser_take_screenshot,mcp__playwright__browser_evaluate,mcp__playwright__browser_press_key,mcp__playwright__browser_wait_for,mcp__playwright__browser_snapshot"
Playwright MCP 沒 load → stop + report:告訴用戶「需要重啟 Claude Code」。不要試 AppleScript / cliclick / mac-use 等野路子(見「環境合規」章節)。
核心工作流(8 步)
Step 0:Load Playwright tools
用 ToolSearch 一次 select 所有需要的 Playwright tools(見上方 detect 命令)。
Step 1:擴大 viewport
browser_resize width=1920 height=1200
預設 viewport 太窄,Figma canvas 會被側欄擠占。1920x1200 是 Figma 官方推薦的設計稿可讀寬度。
Step 2:導航到 Figma file
知道 fileKey + nodeId 時(最快):
browser_navigate url=https://www.figma.com/design/{fileKey}/?node-id={nodeA}-{nodeB}
URL 裡 node-id 用 - 分隔(如 18912-184525),MCP tool 參數用 : 分隔(如 18912:184525)。
只知道 fileKey 不知道 nodeId:
browser_navigate url=https://www.figma.com/design/{fileKey}
啥都不知道,從 recents 找:
browser_navigate url=https://www.figma.com/files/recent
browser_snapshot depth=6
# 從 snapshot 找 role="group" 的 file 卡片 ref
browser_click ref={fileGroupRef} doubleClick=true
關鍵:從 recents 開啟必須 doubleClick: true,單 click 只是選中。
Step 3:等 Figma 載入
browser_wait_for time=5
Figma 是 SPA,資源載入慢。首次開啟要至少 5 秒才能互動。後續 navigate 同 file 的不同 node 只需 2-3 秒。
Step 4:切 Page(如需要)
Figma 檔案通常有多個 page,想抓的設計可能在特定 page(如「附件/紅包」)。
A. 真實 click(優先)
browser_snapshot depth=12
# 在 snapshot markdown 找目標 page 名 + ref=eXXX
browser_click ref=eXXX
B. URL 直跳
如果知道目標 page 裡任意一個 node-id,直接 browser_navigate URL 帶 node-id={nodeA}-{nodeB},Figma 自動切到該 node 所在的 page。判斷當前 page:Pages 面板選中 page 的 <button> 帶 aria-current="page"。
已知坑:
dispatchEvent(MouseEvent) 或 element.click() 切 Figma page 不會生效(Figma 的 PagesRowWrapper 監聽 React synthetic events + pointerdown/up 序列,單發 click 不觸發)
- 必須用
mcp__playwright__browser_click 真實 click,不是 JS click
Step 5:View 調整
Figma 快捷鍵:
| 快捷鍵 | 作用 | 何時用 |
|---|
Shift+1 | Fit view to entire page | 想看 page overview 結構 |
Shift+2 | Zoom to selected node | 精確對齊單個節點 |
Shift+0 | Zoom to 100% | 按 1:1 實際尺寸顯示 |
Cmd++ / Cmd+- | 縮放 | 微調 |
典型順序:先 Shift+1 看全貌確認方向 → 點某個 node → Shift+2 zoom 上去截圖。
Step 6:列 Layers(建 inventory)
用 browser_evaluate 執行 JS 從 Layers panel DOM 讀 node-id + 名稱。
基本 snippet(單次查詢):
() => {
const rows = document.querySelectorAll('[data-testid$="-layers-panel-row"]');
const out = [];
rows.forEach(r => {
const tid = r.getAttribute('data-testid');
const m = tid.match(/^(.+?)-layers-panel-row$/);
if (!m) return;
const name = (r.innerText || r.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 60);
out.push({ nodeId: m[1], name });
});
return out;
}
⚠️ Virtual scroll 限制(實測 2026-04-23 ✓):Layers panel 只渲染可見 layer。不滾動只能拿到 ~62 個 node;滾動後可拿到 111 個(本次實測數字)。必須滾動才能建完整 inventory。
滾動收集 snippet(正確做法):
async () => {
const container = document.querySelector('[class*="layersPanel"]') ||
document.querySelector('[data-testid*="layers"]');
const seen = new Map();
const collect = () => {
document.querySelectorAll('[data-testid$="-layers-panel-row"]').forEach(r => {
const tid = r.getAttribute('data-testid');
const m = tid.match(/^(.+?)-layers-panel-row$/);
if (!m) return;
if (seen.has(m[1])) return;
const name = (r.innerText || '').trim().replace(/\s+/g, ' ').slice(0, 60);
seen.set(m[1], { nodeId: m[1], name });
});
};
collect();
if (container) {
const scrollStep = 300;
let lastScrollTop = -1;
while (container.scrollTop !== lastScrollTop) {
lastScrollTop = container.scrollTop;
container.scrollTop += scrollStep;
await new Promise(r => setTimeout(r, 200));
collect();
}
}
return [...seen.values()];
}
每次 scrollTop += 300 → wait 200ms(等虛擬列表重繪)→ 再 collect → 直到 scrollTop 不再變化。
去重靠 Map key(nodeId),合併結果即完整 inventory。
其他替代方法(當 Layers panel 收合時):
- 用 Figma
Cmd+F Find 搜名字 → 精確定位
- 手動展開所有 group(點 chevron)後再 query
已知坑:
- node-id 看起來像 frame component 實際是 Text label —— 抓的時候 Shift+2 zoom 上去看到大字而非組件,就是命中了 Text node。要從 Layers 找父節點(通常是
Frame xxx 名的節點)。
Step 7:逐個 frame 截圖
# 對每個 frame:
browser_navigate url=https://www.figma.com/design/{fileKey}/?node-id={nodeA}-{nodeB}
browser_wait_for time=2
browser_press_key key=Shift+2
browser_wait_for time=1
browser_take_screenshot filename=<absolute-path>/screenshots/{name}.png type=png
檔名慣例:{category}-{descriptor}-{nodeA-nodeB}.png,例如:
bubble-lucky-rp-18912-184525.png(紅包氣泡類型)
detail-header-18912-184529.png(詳情頁 header)
animation-frame-01-28272-99441.png(動畫關鍵幀)
截圖包含側欄:Playwright browser_take_screenshot 預設截 viewport,左右側欄會被包含。要純 canvas 可以:
- 用
clip 參數限定區域(未來擴展)
- 或截完後 Bash 用
sips -c 裁剪
Step 8:DOM 提取 design token
每個 frame 選中時,右側 Properties panel 顯示其 layout / typography / colors / padding / radius。JS 抽出:
() => {
const panel = document.querySelector('[class*="right_panel"]') ||
document.querySelector('[data-testid="properties-panel"]');
if (!panel) return { error: 'properties panel not found, selection may have changed' };
const fullText = panel.innerText || panel.textContent || '';
const sections = {};
panel.querySelectorAll('[class*="section"]').forEach(s => {
const title = (s.querySelector('[class*="title"]')?.innerText || '').trim();
if (title) sections[title] = (s.innerText || '').replace(title, '').trim().slice(0, 500);
});
return {
fullText: fullText.slice(0, 3000),
sections,
};
}
常見 token 欄位(文字中會出現):
- Layout: Width / Height
- Typography: Font / Weight / Size / Line height / Letter spacing
- Colors: Hex values (e.g., #E75140)、token label(如
固定色/红包/红包颜色)
- Fills / Strokes: fill color + opacity
- Effects: shadow (x y blur spread color alpha)
- Padding: vertical / horizontal
- Gap: auto layout spacing
- Corner radius: per-corner or uniform
- Content:Text node 的文字內容
View seat 限制下 Properties panel 能讀什麼(實測 2026-04-23 ✓)
很多人誤以為 View seat = Properties panel 也看不到。不對。
| 可讀 ✅ | 不可讀 ❌ |
|---|
| Width / Height / Padding / Gap | Dev Mode code snippet(需 Editor seat) |
Colors(含 token label 如 固定色/红包/红包颜色) | Code Connect 組件映射 |
| Typography(Font / Weight / Size / Line height) | Inspect panel 進階 API 數據 |
| Corner radius | — |
| Content(Text node 文字) | — |
| Modes / Component properties | — |
| Export 設定 | — |
結論:View seat 下 innerText 抽 Properties panel 完全可用,能拿到所有視覺 token。只有代碼生成相關功能需要升 seat。
高階 Pattern
Pattern 1:系統性 frame inventory
抓完 Layers 後建 JSON 存檔:
const inventory = [... ];
然後逐個 navigate + screenshot,把 inventory 內容擴充 screenshotPath 欄位。
Pattern 2:Figma vs 代碼 token 對照
- Step 8 提取當前 frame token
- Read 代碼的 colors.dart / font.dart / theme.dart
- 生成對照表:
| Token | Figma 原始值 | 當前代碼 | 一致? |
| xxx | #E75140 | 0xFFE75140 | ✅ |
| yyy | rgba(189,88,78,0.10) | 0x1ABD584E | ✅ |
| zzz | (Figma 無此 token) | 0x40BD584E | ⚠️ code 獨有 |
Pattern 3:動畫關鍵幀抓取
Figma 沒有原生動畫時序,但設計師會畫連續多個 frame 表示動畫各階段。命名如:
animation-open-01-start
animation-open-02-scale-up
animation-open-03-reveal
抓的時候按順序 screenshot,檔名保留順序 animation-{stage}-{nn}-{nodeId}.png,方便之後逐幀對齊實現。
Pattern 4:REST API fallback(PAT 有時)
如果有 Figma PAT(不是 View seat 限制的 MCP token,是個人 API token),可繞開 Playwright:
curl -H "X-FIGMA-TOKEN: $PAT" \
"https://api.figma.com/v1/images/{fileKey}?ids={nodeId}&format=png&scale=2" | jq -r '.images["{nodeId}"]' | xargs curl -o {out}.png
curl -H "X-FIGMA-TOKEN: $PAT" \
"https://api.figma.com/v1/files/{fileKey}/nodes?ids={nodeId}"
PAT 存 Keychain:
security add-generic-password -s "figma-pat" -a "$USER" -w "<your-pat>" -U
PAT=$(security find-generic-password -s "figma-pat" -a "$USER" -w)
已知坑(別再踩)
| ✗ 不要 | 原因 |
|---|
dispatchEvent(MouseEvent) 或 element.click() 切 Figma page | Figma 監聽 React synthetic events + pointerdown/up 序列,單發 click 不觸發 |
mac-use-mcp__click | 操作的是真實用戶 Chrome,不是 Playwright 的 browser 實例 |
/ 快捷鍵搜 node | Figma / 是 Comment |
Cmd+P | Figma Quick Actions 選單,不能搜 node |
Cmd+Shift+C 想拿 SVG | 是 Copy as PNG,不是 SVG。要 SVG 走 Properties → Export panel |
靠 a[href] 查找 recents 文件 | Figma recents 卡片用 role="group",不是 anchor |
| Shift+2 看到大字 = 組件 | 可能是 Text label(不是 frame),要從 Layers 找父節點 |
| 靠視覺「猜」設計規格 | 必須用 DOM 抽精確值,不要用估 |
PageDown 鍵在 Figma canvas 上按 | PageDown 會切換整個 Figma Page(不是 scroll canvas!)實測 2026-04-23 ✓:按 PageDown → 跳去下一個 page(如「聯系人·發現」),截圖就錯了。滾動 canvas 要用 browser_scroll 或 Layers panel container scroll,不要用 PageDown |
| 不滾 Layers panel 直接 query | 只拿到可見 ~62 個 node,完整 inventory 需滾動收集(見 Step 6 滾動 snippet) |
Text label 判斷法(實測 2026-04-23 ✓)
navigate 到一個 node → Shift+2 zoom → 若畫面中央出現 超大字(如 84px「幸運紅包」),這是 Figma 文件內部分區標題 Text label,不是用戶可見的 UI component。
快速辨識:
- Properties panel 右側看 Typography 區:出現 Font / Weight / Size 就是 Text node
- Layers panel 該行有 T icon(Text),Frame icon 是方塊
- 尺寸是
{n×84}px(每個漢字 84px 寬),高度固定 84px
確認是 Text label 後,按 Escape 跳到父節點(Layers 面板自動選中父層)。找 Frame 14200XXXXX 或 控制台 命名的 Frame 才是真正的 UI component。
Escape 跳父節點法(實測 2026-04-23 ✓)
在 Figma canvas 已選中某節點時按 Escape,會跳到該節點的直接父節點(等同 Layers panel 往上一層)。本次實測:從 Text label 18912:184525(幸運紅包)按 Escape 後跳到父 Frame 12116:222139(page root frame),確認氣泡 UI 無獨立 component 節點。
用法:
- navigate 到未知節點
Shift+2 zoom 確認是 Text label 或 sub-layer
- 按
Escape → 看 URL + Layers panel 選中項變化 → 新 node-id 即父節點
- 重複 Escape 可沿樹向上爬,直到找到目標 Frame
Figma annotation text 藏 UX 決策(實測 2026-04-23 ✓)
截圖只捕捉視覺 UI。Figma 文字標注(設計師在 canvas 上附加的說明文字)含有截圖不可見的架構決策。
本次實測:node 18912:184683 的 annotation text 為「查看詳情入口進入該頁面的都帶返回按鈕」,揭示詳情頁是 full-screen page(非 modal),這是與代碼現狀最大的架構偏離。純靠截圖無法發現。
操作方式:
() => {
const panel = document.querySelector('[class*="right_panel"]');
return panel ? panel.innerText.slice(0, 3000) : 'not found';
}
或直接 browser_snapshot depth=12 看 Figma Annotations 面板(右側面板切換到「Annotations」tab)。
何時特別重要:
- 「這頁怎麼打開」(push vs modal)
- 「有沒有返回按鈕」
- 「是否支持手勢關閉」
- 任何涉及導航方式的 UX 決策
Mobile mockup 內部 UI 無獨立 Figma node
Figma 設計師會把 UI 放在 mobile 手機框 mockup 內,mockup 內的 UI sub-layer 不是獨立 reusable component,無獨立 node-id 可直接訪問。
本次實測:紅包氣泡 UI 是 page root frame 12116:222139 下的 mobile mockup sub-layer。9 個紅包 Text label 的父節點全部指向 12116:222139(page root),表示氣泡無獨立 component。
含義:
- 不要期望每個 UI 元素都有獨立 node-id
- 無 node-id 的 UI → 以代碼現狀為準(不等 Figma node 抓取)
- 想看 mockup 內部 UI → 截 parent frame +
Shift+1 全局截圖,用截圖推算比例
環境合規:別做疑似惡意軟體行為
企業環境可能裝有 EDR(如 CrowdStrike Falcon Sensor),任何看起來像惡意軟件的自動化手法會觸發安全告警。
❌ 禁止
- 隱藏 / 最小化 Terminal 視窗(「想偷偷切換 app」= 惡意軟件典型手法)
- AppleScript 跨 process event(
tell application "Chrome" to ...)—— EDR 攔截,timeout -1712
- 操作用戶前台視窗(user 正在用的 Chrome / VSCode / IM app 等)
- cliclick / 鍵盤模擬 / 滑鼠模擬搶焦點
- 任何「偷偷進行」或「繞過 UI」的 workaround
✅ 正確做法
- Playwright MCP — Chrome extension bridge 模式。本 skill 推薦
- Figma REST API + PAT — 純 HTTP 零 UI 操作
- MCP 工具不可用 → 停 + 報告,不試其他 stealth 手段
- 寧可任務失敗,不觸發安全警報
Playwright MCP 的 browser 實際是什麼
本 skill 實測(2026-04-23):mcp__playwright__browser_* 工具走 Chrome extension bridge(URL 顯示 chrome-extension://mmlmfjhmonkocbjadbfplnigmagldckm/connect.html),是 Chrome 原生 extension API 代理用戶 Chrome,不是完全 isolated Chromium。
含義:
- ✅ 不觸發 EDR 惡意軟件檢測(是 Chrome 原生行為)
- ⚠️ 會操作用戶真實 Chrome tab(會開新 tab 或佔用現有 tab)
- ⚠️ 依賴用戶 Chrome 已登入 Figma(session cookie)
如果嚴格要求 isolated browser(不碰用戶 Chrome),需要另用 Playwright CLI(非 MCP)或 Puppeteer with --user-data-dir=<tmp>。
失敗模式 + 復原
| 失敗 | 診斷 | 復原 |
|---|
browser_navigate 報 timeout | Figma 載入慢 / 網路問題 | 增加 browser_wait_for time=10,重試一次 |
browser_take_screenshot 只拿到空白畫布 | Figma 還沒完全渲染 | Shift+1 fit view + wait 3s 再截 |
browser_evaluate 返回空 [] | Layers panel 收合了 / virtual scroll | Cmd+F Find 定位目標後再 query;或展開所有 group |
| Playwright MCP 工具不可用 | Claude Code session 沒 register | 停 + 重啟 Claude Code,不要切 AppleScript / cliclick |
| Figma 顯示 "Request access" | Dev Mode / Code Connect 需要 seat 升級 | 不拿 code,只拿 screenshot + DOM token |
| File 顯示 login page | Chrome extension bridge 沒登入 Figma | 用戶在 Chrome 裡手動登入 Figma 後重試 |
| 抓的 screenshot 側欄佔一半 | viewport 太小 | browser_resize 1920x1200 + 按 \\ 切全屏模式(Figma 預設 shortcut) |
完整 workflow template(可複製)
# 1. Load tools
ToolSearch "select:mcp__playwright__browser_navigate,browser_resize,browser_take_screenshot,browser_evaluate,browser_press_key,browser_wait_for,browser_snapshot"
# 2. Prep
browser_resize 1920x1200
browser_navigate https://www.figma.com/design/{fileKey}/?node-id={root}
browser_wait_for time=5
# 3. Build inventory (Step 6 snippet)
browser_evaluate "<list-layers.js>"
# → save to .omc/figma-snapshots/{feature}/frame-inventory-raw.json
# 4. Overview screenshots (Shift+1 per section root)
for section in [附件, 紅包皮, 發包, ...]:
browser_navigate url=...?node-id={section-root}
browser_wait_for time=2
browser_press_key key=Shift+1
browser_wait_for time=1
browser_take_screenshot filename=section-{name}.png
# 5. Detail screenshots (Shift+2 per frame)
for frame in inventory:
browser_navigate url=...?node-id={frame.nodeId}
browser_wait_for time=2
browser_press_key key=Shift+2
browser_wait_for time=1
browser_take_screenshot filename=frame-{frame.name}-{frame.nodeId}.png
browser_evaluate "<extract-properties.js>"
# → append token to tokens-raw.json
# 6. Generate outputs
# frame-inventory.md(人類可讀版 inventory)
# tokens.md(Figma vs 代碼對照)
# README.md(抓取狀態彙總)
產出結構建議
.omc/figma-snapshots/{feature}/
├── README.md # 抓取狀態彙總(成功/失敗/Phase 歸屬)
├── frame-inventory.md # 人類可讀 frame 清單
├── frame-inventory-raw.json # 原始 inventory 數據
├── tokens.md # Figma vs 代碼對照
├── tokens-raw.json # 原始 DOM token
└── screenshots/
├── section-01-attachment.png
├── section-02-theme.png
├── frame-bubble-lucky-18912-184525.png
├── frame-detail-header-18912-184529.png
├── animation-open-01-xxx.png
└── ...
觸發關鍵詞
當用戶說以下任何一個,考慮用本 skill:
- 「抓 Figma 設計稿」「批量截圖 Figma」「提 Figma token」
- 「Figma MCP 用不了」「View seat 限額」「tool call limit」
- 「對齊 Figma」「Figma vs 代碼」「驗證 Figma」
- 「列 Figma frame」「建 Figma inventory」
- 「用 Playwright 抓 Figma」「繞開 Figma MCP」
Related skills
figma-use — use the official Figma MCP when quota is available (preferred). playwright-figma-scrape is a fallback for when Figma MCP hits View seat limits.
verify-ui — use after playwright-figma-scrape to do visual comparison verification between Figma mockups and actual code.
visual-verdict — use for structured visual diff validation after scraping Figma designs.
verify-ui-auto — use for automated pixel-based comparison via SSIM after playwright-figma-scrape screenshots.