デモ動画の録画(無料、スクリプトベース)
実際のアプリの無音製品デモ動画を、ブラウザをスクリプトで操作してMP4にレンダリングします。 無料でローカル実行:Playwright がアプリを駆動し、ffmpeg が動画を作成します。有料SaaS不要、ナレーション不要、 人間のクリック操作不要。同じスクリプトはUXスモークテストとしても機能し、UIが変更されたときに再レンダリングします。 これこそが、多数の機能デモを持続的に作成する方法です。
使用タイミング
- ユーザーが1つまたは複数の機能デモ/ウォークスルー動画を必要としており、有料ツールにお金を払いたくない場合。
- 「新しい機能ができるたびに誰かが画面録画しなければならない」という繰り返しの問題。
- 機能が実際のUIでエンドツーエンドで動作することを迅速に視覚的に確認したい場合。
以下の場合には不向き:ナレーション付きのアニメーション豊富なマーケティング動画(実際の編集ツールを使用)、またはカメラ前で反応する人間が必要なもの。
基本構造
ログインセッションを取得 → Playwrightでアプリを操作(.webmを録画) → ffmpeg → .mp4
ターゲットリポジトリの demo/ の下に3つのファイルが配置されます(resources/ からコピー):
record.mjs— エンジン。CONFIGブロック(リポジトリ固有)+ FLOWSマップ(動画ごとに1エントリ)。polish.sh—.webm→.mp4、ログインリードインのトリミング、オプションのキャプション焼き付け。run.sh— 1つのコマンド:開発サーバーをステージングに向け、録画、ポリッシュ、後片付け。README.md— リポジトリごとの使用方法。
新しい動画を作成するには:FLOWSエントリをコピーし、url + steps を変更するだけです。これがループ全体です。
セットアップチェックリスト(リポジトリごとに1回実行)
- ツールのインストール(1回):
npm install -D @playwright/test && npx playwright install chromium。ffmpegが必要(Homebrew)。キャプション焼き付けにはlibfreetypeでビルドされたffmpegが必要です。それ以外の場合はキャプションは自動スキップされ、他のすべては機能します。 - リポジトリの詳細を確認し、
record.mjsのCONFIGブロックを埋めます:- 開発コマンド — アプリをローカルで実行する方法(
npm run dev、多くの場合congrats/のようなサブディレクトリ内)。 - Supabaseクライアントモジュールパス — 通常は
src/integrations/supabase/client.ts。レコーダーはこれをページ内でインポートしてログインします(後述の注意点1を参照)。 - 録画に使用するDB — ステージングを使用し、本番は絶対に使用しないこと(実際のユーザーの個人情報は含まれない)。そのURL、anon/publishableキー、サービスキー、直接DB URLを確認します。この組織では、それらはauditionリポジトリの
.env.localにあります(VettedAIステージング用のVETTED_SUPABASE_*、Congratsステージング用のSTAGING_CONGRATS_SUPABASE_*)。 - ログインメールアドレス — デモデータを所有する合成/テストアカウント。セッションを作成する前に常にユーザーにどのアカウントを使用するか尋ねてください(プロジェクトルール:許可なしに実際のユーザーテストを行わないこと)。
- ルート — 記録するフローのディープリンクパス。
- 開発コマンド — アプリをローカルで実行する方法(
- ローカル開発サーバーをステージングに向ける(後述の注意点2を参照)。
- フローを作成し、
bash demo/run.sh <flow>を実行して、フレームを抽出して検証します(後述の注意点8を参照)。
注意点(苦心して得た知見 — デバッグ前に読んでください)
1. アプリ自身のクライアント経由でログインし、localStorageを偽装しないこと
Supabase-jsはブラウザ内の非デフォルトキー/エンベロープにセッションを書き込みます。localStorage['sb-<ref>-auth-token'] = JSON.stringify(session) を手動で注入しても正しく見えますが、クライアントがそれを読み取らず、アプリは /login にリダイレクトされます。代わりに、サーバーサイドでセッションを作成し(管理者 generateLink → verifyOtp で access_token + refresh_token を取得、パスワード不要)、次にページ内でアプリのSupabaseクライアントを import し、supabase.auth.setSession({access_token, refresh_token}) を呼び出します。これにより正しいストレージが書き込まれ、セッションはナビゲーション後も持続します。これが最も重要なステップです。
2. --mode staging が黙って無視される可能性がある → .env.development.local を使用する
vite-react-ssg dev(および一部のセットアップ)は、環境ファイルの読み込みで --mode を無視するため、.env.staging が読み込まれず、アプリは本番環境を指したままになります(本番Supabaseホストへのリクエストが表示されます)。Viteは開発時に .env.development.local を .env/.env.local よりも高い優先度で常に自動読み込みします。そのため run.sh は録画のためにステージング設定を .env.development.local にコピーし、終了時に削除します(そうしないと、以降のすべての npm run dev が黙ってステージングを指したままになり、これは重大な問題です)。プレーンな vite リポジトリは --mode を尊重する場合があります。アプリが呼び出すSupabaseホストを監視してテストしてください。
3. ステージングのスキーマがフロントエンドに追いついていない可能性がある → 対象を絞った追加的な整合
ステージングブランチに、フロントエンドが選択する最近のカラムがない場合があります → データクエリが400エラー(column X does not exist)になり、画面が空でレンダリングされます。マイグレーションを再実行しないでください。画面が読み取る少数のテーブルのカラムを比較し(ステージング vs 本番、information_schema.columns 経由)、不足しているものだけを追加的でNULL許容の ALTER TABLE ... ADD COLUMN IF NOT EXISTS として適用します。ユーザーの許可を得てください — これはステージングDBの変更です。
4. シードデータのスケールが合わない可能性がある
合成シードのスコア/値が、UIフォーマッターが期待するものと一致しない場合があります(例:スコアが0–100で保存されているのにUIが value/5*100 を実行 → 「1392%」と表示される)。フォーマッターの期待範囲を本番の実際の値と照合し、必要に応じてシードデータのスケールを変更します(事前に確認してください — これはデータの変更です)。
5. Playwrightの動画にマウスカーソルが表示されない
addInitScript を使用して偽のカーソルオーバーレイを注入します(mousemoveに追従し、mousedownで拡大するドット)。テンプレートはすでにこれを行っています。クリック前にマウスを実際の座標に移動して、追跡されるようにします。
6. ffmpegに drawtext がない場合がある
libfreetypeなしでビルドされたものは、キャプション焼き付けでエラーになります。polish.sh は drawtext を検出し、ない場合はキャプションをスキップします — キャプションの失敗がMP4出力を妨げないようにしてください。
7. ログイン/起動リードインを自動トリミングする
録画にはログインとアプリ起動(無駄な秒数)が含まれます。record.mjs がリードインを測定し .trim サイドカーファイルを書き込みます。polish.sh がそれをカットします(ffmpeg -ss)。
8. フレームで検証し、「録画された」という事実だけで検証しないこと
ffmpeg -ss <t> -i out.mp4 -frames:v 1 frame.png を数箇所のタイムスタンプで実行し、実際に目視確認してください。最初の実行では /login、スケルトン、または間違ったタブにランディングすることがよくあります — フレームでそれを発見します。
9. データと認証の安全性
ステージングで合成データ(例:seed_…@test.vetted.com)を使用して録画し、実際の顧客のプロジェクト(個人情報)は決して使用しないでください。常にユーザーにどのアカウントでログインするか確認してください。認証セッションの作成は実際のアクションです — 最初に確認してください。
フローの作成
record.mjs では、FLOWS は { label, url, steps(page, ui) } のマップです。ui ヘルパーがほとんどのニーズをカバーします:
ui.settle(what, [words])— コンテンツを待機(networkidle + 画面が読み込まれたことを証明する単語)ui.scrollThrough(fraction, steps)— スムーズにスクロールダウンして戻るui.clickFirstCandidate()/ui.clickFirst(selectors)に一般化 — ベストエフォートなクリック、決してハードフェイルしないui.beat(ms)— 動画が読みやすくなるように意図的に一時停止
フローはセレクターを軽く、ベストエフォートに保ちます(クリックをtry/catchで囲む)。これにより、セレクターが見つからなくても「それでも十分なスクロールデモ」に劣化し、クラッシュしません。ペースは generously に(800–1800msのビート)。優れた無音デモは15~35秒です。
出力と受け渡し
MP4は demo/output/ に生成されます(gitignore対象)。open demo/output/<flow>.mp4 で再生します(相対マークダウンリンクはIDEでは再生されません — エディタでファイルが開かれます)。パスを共有するか、open でユーザーに渡します。
このスキルの伝播(skill-master内に存在)
このスキルは submodules/skill-master/.agent/skills/recording-demo-videos/ に正規のものがあり、各リポジトリの .agent/skills/ にシンボリックリンクされています。編集後、最初にサブモジュールをコミットしてプッシュし、次に親リポジトリのポインターをプッシュし、/distribute-skill を実行して兄弟リポジトリに反映します(プッシュ前にユーザーに確認してください)。skill-master-submodule-sync ルールを参照してください。