Chrome Web Store用画像をproduction buildから自動生成する
Playwrightで実Google Sheetsを撮影し、RemotionでEN/JAのStore画像をproduction buildから再生成するpipeline。
Chrome拡張 Spex の公開準備で、Chrome Web Store用のスクリーンショットを作りました。
最初は普通に考えていました。
- Google Sheetsを開く
- Spexを動かす
- スクリーンショットを撮る
- Figmaや画像編集でタイトルを載せる
- 5枚できたら終わり
ところが、実際にリリース直前までプロダクトを触り続けると、この方法がかなりつらいことに気づきました。
KeyTipsの見た目を変える。
キーマップの既定を変える。
画面上の配置を直す。
日本語対応を入れる。
ダークモードやチュートリアルを追加する。
すると、昨日作ったStore画像がもう古い。
しかもChrome Web Storeでは、単に綺麗な画像を作ればよいわけではありません。
現在公開するbuildと、画像の中で見せている挙動が一致している必要があります。
そこで、Store画像を「デザイン成果物」ではなく、production buildから再生成できるbuild artifactとして扱うことにしました。
現在のSpexでは、次のコマンドでStore素材を作り直せます。
pnpm store:assets
この記事では、なぜ静止画5枚のためにPlaywrightとRemotionまで使うことになったのかを書きます。
最初に壊れたのは「スクリーンショットは一度作れば終わり」という前提だった
Git履歴を見ると、Store素材は一度で完成していません。
最初にrelease graphicsを用意した後も、
- Store iconのsafe area修正
- primary screenshotのcrop修正
- 実際の5枚構成の作り直し
- 英語 / 日本語の両対応
- trust claimの検証
が続きました。
特にスクリーンショットのcrop修正は象徴的でした。
プロダクトの画面をそのまま全部見せれば「正直」ではありますが、Storeの一覧や詳細ページで見ると、小さすぎて何を見せたいのか分からなくなります。
一方で、見せたい部分だけを手で切り取って作り込むと、次のUI変更でまた撮り直しです。
この時点で、Store画像をPSDやFigmaの完成データとして持つより、どう生成するかをコードで持った方がよいと考えました。
まず、実プロダクトを自動で撮る
SpexのStore画像では、マーケティング用に作った偽のGoogle Sheets画面を使いません。
現在のproduction extensionをbuildし、PlaywrightでChromiumへ読み込ませ、実Google Sheetsを開きます。
概念的には次の流れです。
current Spex source
↓
WXT production build
↓
.output/chrome-mv3
↓
Playwright launches Chromium with the extension
↓
real Google Sheets
↓
actual Spex state / actual keyboard sequence
↓
raw capture
重要なのは、Store画像制作のための別実装を作らないことです。
KeyTipsを見せたいなら、本物のSpexでAltを押します。
Sheets準拠キーマップを見せたいなら、実際にsettingsをそのprofileへ変更します。
コマンド実行前後を見せたいなら、本物のcommandを走らせます。
これにより、Store画像制作のコードがproduction behaviorの第二実装になることを避けています。
撮影用のSheetも自動で作る
実Google Sheetsを使うと、次の問題が出ます。
毎回同じ見た目のfixtureをどう用意するか。
人間が撮影前に、
- 適当な表を作る
- 太字にする
- 選択範囲を合わせる
- キーマップを変える
- 余計なToastを閉じる
とやっていると、再現性がありません。
そこでcapture script自身が一時Sheetを作ります。
例えばStore画像のfixtureでは、概念的に次のような表を自動入力します。
Month | Region | Sales | Cost | Margin
Apr | East | 128,400 | 91,200 | 29.0%
May | West | 146,800 | 98,500 | 32.9%
Jun | East | 139,600 | 92,400 | 33.8%
Jul | North | 158,300 | 103,100 | 34.9%
その上で、
- headerを太字化
- 指定セルへ移動
- Spex profile変更
- Altを押してKeyTips表示
- 必要ならcommand実行
までPlaywrightが行います。
「撮影用データを消す」コードの方が怖い
実Google Sheetsへ一時Sheetを作る以上、cleanupが必要です。
ここはかなり保守的にしています。
撮影scriptは、名前が明示的なprefixを持つSheetだけを削除します。
SPEX_PROMO_IMAGE_...
SPEX_PROMO_STORE_...
想定外のSheet名なら削除を拒否します。
Sheet数についても、
baseline
↓
+1だけ増えた
↓
撮影用prefixである
↓
削除
という条件にしています。
Marketing automationで本物のSpreadsheetを触る以上、画像生成の便利さより、誤ってユーザー資産を削除しないことの方が重要です。
EN / JAは「翻訳した画像」ではなく、それぞれ実際に撮る
Spexは日本語・英語に対応しています。
Store画像も両方必要です。
ここで一枚の英語スクリーンショットに日本語captionだけ載せ替える方法もあります。
ただ、それではGoogle Sheets側の表示言語とSpex側のlocaleが一致しません。
そのためcapture段階から分けます。
en
browser locale: en-US
Sheets: ?hl=en
Spex locale: en
ja
browser locale: ja-JP
Sheets: ?hl=ja
Spex locale: ja
同じcapture scenarioをlocaleごとに実行し、その上からそれぞれのcopyを組みます。
これにより、
- ENだけ古いUI
- JAだけ別のKeyMap
- Google Sheetsは英語なのにSpexだけ日本語
のようなStore上の不整合を減らせます。
静止画なのにRemotionを使う
撮ったraw screenshotをそのままStoreへ出すわけではありません。
Store画像には、
- title
- short body
- framing
- before / after
- Key sequence
- product positioning
などを載せます。
ここで画像編集ソフトではなくRemotionを使っています。
動画のためのライブラリという印象が強いですが、RemotionはReact componentから静止画をrenderする用途でも便利です。
Spexでは概念的に、
raw real-product capture
+
locale-specific copy
+
shared visual primitives
↓
Remotion still composition
↓
1280x800 PNG
としています。
5枚の構成は、例えば次のように分けています。
01 Hero
02 Interaction
03 Toolbar
04 Excel shortcuts
05 Why Spex
ENとJAでcomponent structureは共通です。
文章だけをdataとして分離しています。
hero: {
title: "Press Alt. See what to press next.",
body: "Spex puts the next key directly on Google Sheets."
}
hero: {
title: "Altを押す。次のキーが見える。",
body: "Google Sheetsの上に、次に押すキーをそのまま表示します。"
}
この方法だと、余白やカード位置を修正したとき、EN/JAを別々に直す必要がありません。
Store画像のcopyまで検証対象にした
画像生成を自動化しても、内容が嘘なら意味がありません。
そこでbuild pipelineでは、Remotion renderingの前にtrust claimの検査を入れています。
例えばSpexでは、Privacyについて、
No Spex analytics backend. Settings and diagnostics stay local.
という表現をStore素材で使っています。
こうしたclaimは、実装が変わったのに画像だけ古いまま残ると危険です。
そのため、Store asset buildはproduction sourceと公開claimの整合性を確認するrelease checkと組み合わせています。
つまり画像は単なるPNGではなく、release declarationの一部として扱っています。
最後に寸法も機械で見る
生成後にはffprobeで画像streamを検査します。
期待値と違えば失敗です。
Store screenshots: 1280 x 800
Small Promo: 440 x 280
Marquee: 1400 x 560
さらにQA用に半サイズpreviewとcontact sheetも生成します。
なぜcontact sheetを作るかというと、5枚を一枚ずつ開いていると、シリーズ全体のバランスが見えにくいからです。
EN 5枚 -> en-contact.png
JA 5枚 -> ja-contact.png
最終確認では、
- 5枚の情報密度が偏っていないか
- 1枚だけ文字が小さくないか
- EN/JAで構図がズレていないか
- 同じ画面を何度も見せていないか
を一覧で見ます。
現在の生成フロー
最終的には次の6段階になりました。
1. production Spex build
2. real Google Sheets capture
3. prepare image assets
4. validate claims / ESLint / TypeScript
5. Remotion still render
6. dimensions / QA previews / contact sheets
コード上もほぼこのままログを出します。
[store-image] 1/6 build production Spex
[store-image] 2/6 capture current real Google Sheets states
[store-image] 3/6 prepare image assets
[store-image] 4/6 validate claims and Remotion source
[store-image] 5/6 render Store stills
[store-image] 6/6 verify and create QA previews
何が一番良かったか
自動化したことで制作時間がゼロになった、という話ではありません。
むしろStore画像の構成やcopyは何度も人間がレビューします。
良かったのは、「この画像は今の製品を写しているのか?」を毎回疑わなくてよくなったことです。
手作業だと、綺麗に作った画像ほど更新しづらくなります。
コード生成なら、UIを変えたときは撮り直す前提です。
そして変更後に、
pnpm store:assets
を実行すれば、現在のproduction buildから一式を作り直せます。
Chrome拡張に限らず、StoreやApp Marketplaceへ継続的に画像を出すプロダクトでは、Marketing assetもrelease artifactとして扱う価値があると思います。
Spex
この記事で扱ったpipelineは、Google Sheetsをキーボード中心で操作するChrome拡張SpexのStore素材制作に使っています。
- Product: https://spex.kikuta.dev/ja
- Chrome Web Store: https://chromewebstore.google.com/detail/gahnolkboklnjhnaahjdkingoejbepcc