デザインと実装の乖離を「0」へ:Penpot × PlaywrightによるVisual Regressionの完全自動化パイプライン
デザインツールとコードベースの間に横たわる「解釈の揺らぎ」は、プロダクトの品質を蝕む静かなる癌だ。Figmaの独占に終止符を打つべく登場したオープンソースの旗手「Penpot」は、CSS GridやFlexboxをネイティブの思考で扱えるという点で、エンジニアにとって最も「コードに近い」デザイン環境である。
しかし、どれほど優秀なデザイナーが設計しても、実装フェーズでピクセル単位のズレやレスポンシブの破綻は発生する。本稿では、PenpotのAPIを叩き、Storybook上のコンポーネントと直接比較を行う、「デザイン意図の自動検証パイプライン」の構築手法を伝授する。
—
1. アーキテクチャの核心:なぜ「Penpot API」を起点とするのか
多くのチームが陥る罠は、デザインの変更を「Slack通知」や「手動確認」で管理することだ。我々が目指すのは、Penpotをソース・オブ・トゥルース(信頼できる唯一の情報源)とし、コードをその射影として自動検証するシステムである。
システムフロー
1. Webhook / Webhook Bridge: Penpotでの変更を検知。
2. Penpot API Client: 特定のBoard / Componentのレンダリング結果をSVG/PNGとしてエクスポート。
3. Playwright Visual Comparison: Storybook/Appの該当コンポーネントを同一環境(Viewport, Font-rendering)でキャプチャし、ピクセル差分を計算。
4. CI Report: 差分があればGitHub Check Runとしてコメントバック。
—
2. 実装の深淵:Penpotからコンポーネントを切り出す
PenpotのAPIは、単なるデザインデータではない。CSSプロパティを直接吐き出すエンジンとして活用する。まず、対象のコンポーネントIDを指定し、headlessにエクスポートするスクリプトを構築する。
// scripts/export-penpot.ts
import axios from ‘axios’;
/
- Penpot APIを使用して、指定したBoardをPNGとして取得する
- 内部的にはPenpotのレンダリングエンジンを叩く
/
async function fetchDesignSnapshot(fileId: string, boardId: string) {
const response = await axios.get(`${process.env.PENPOT_API_URL}/api/files/${fileId}/boards/${boardId}/png`, {
headers: { ‘Authorization’: `Token ${process.env.PENPOT_TOKEN}` },
responseType: ‘arraybuffer’
});
return response.data; // Bufferを返す
}
—
3. PlaywrightによるVisual Regressionの極致
単にスクリーンショットを撮るだけでは、アンチエイリアスやフォントレンダリングの差異でCIが落ちる(Flakiness)。「人間の目」ではなく「数学的閾値」で判断させるためのPlaywright設定がここでの肝となる。
// tests/visual.spec.ts
import { test, expect } from ‘@playwright/test’;
test(‘Component visual consistency check’, async ({ page }) => {
await page.goto(‘http://localhost:6006/iframe.html?id=component–primary’);
// 1. レンダリングの安定化を待機(重要)
await page.waitForLoadState(‘networkidle’);
// 2. カスタム閾値による比較(アンチエイリアスの揺らぎを許容)
await expect(page).toHaveScreenshot(‘button-primary.png’, {
maxDiffPixelRatio: 0.02, // 2%までのピクセル差は許容(環境差分対策)
threshold: 0.2, // 色味の変化に対して敏感に
});
});
—
4. パイプライン最適化:CI/CDでのメモリ管理と並列化
大量のコンポーネントを検証する場合、Playwrightの起動オーバーヘッドがボトルネックとなる。Dockerコンテナ内でのメモリリークを防ぎ、実行速度を最大化するためのハックを共有する。
実行戦略の最適化
- Shardによる分散実行: `–shard=n/m` を使用し、GitHub Actions上で並列実行する。
- Font Subsetting: 実行環境にOS標準フォント以外が含まれていると描画がずれる。`playwright.config.ts`で`font-rendering-settings`を固定し、`Dockerfile`でGoogle Fontsをプリインストールせよ。
- キャッシュ層の活用: `playwright-report`と`test-results`をGitHub Actions Cacheで保持し、差分発生時のみアーティファクトをアップロードする。
—
5. 伝説的アーキテクトからの助言:真の自動化とは
ツールを導入するだけでは不十分だ。真に強いチームは以下の「運用規約」をコードに落とし込んでいる。
1. Fail-Fastの原則: Visual Regressionが失敗した際、なぜ失敗したかの理由(デザインの意図的な変更か、バグか)をUI上で即座に承認できるワークフローを構築すること。
2. デザイントークンの同期: PenpotからエクスポートしたCSS変数と、プロダクト側のCSS変数が一致しているかを`style-dictionary`でビルド時に静的解析する。
3. UIカバレッジの可視化: 「どのコンポーネントがテスト対象で、どれが放置されているか」をダッシュボード化し、技術負債を定量化する。
Penpotという強力なツールを使いこなすことは、単なるデザイン管理ではない。「デザインをコードという言語へ、ロスレスで翻訳し続ける規律」そのものだ。
君たちが今日書くこのスクリプトが、明日には数万時間の「手動確認」という名の無駄な労働を消し去るだろう。さあ、実装に取り掛かれ。コードは裏切らない。