| name | demo-reel |
| description | PR・ブランチの変更からデモ動画を撮る。差分を読んで「撮る価値のあるストーリー」を1〜3本起こして提示し、ユーザーが選んだものを spec/demos/scenarios/*.rb(または test/demos/)の台本に落として録画、フレームを目視して撮り直すところまでやる。ストーリーを直接渡せば録画から始まる。ユーザー体験が変わっていない変更のときは「撮るべきものは無い」と結論づけて終わる。 |
| disable-model-invocation | true |
| argument-hint | [PR番号 | ブランチ名 | Issue URL | ストーリー本文](省略時は現在のブランチ) |
変更内容からデモ動画を撮る
仕事は2段階。引数が既にストーリーなら、ストーリー編は飛ばして7章から始める。
ストーリー編(1〜6章) — 変更(PR・ブランチの差分・Issue・口頭の説明)を読み、ユーザーが画面で見たときに
何が変わって見えるかを特定して、ストーリー(動画で見せたい流れ)を1〜3本書き起こし、提示して止まる。
5章の「撮るものが無い」に着地するのは正常な結果で、失敗ではない。無理にひねり出すと、誰も見ない動画のために
全工程を走らせることになる。
録画編(7〜12章) — 選ばれたストーリーを <demos_dir>/scenarios/<name>.rb(7章で確定する
spec/demos か test/demos)の台本に落とし、
bundle exec demo_reel record <name> で録画し、フレームに変換して実際に目で見て、問題があれば直して
撮り直す。録画が成功しても見せられる出来とは限らない — ラベルが要素に被る、空の画面が写る、といった
不具合は終了コードに一切現れない。
サブエージェントの使い方
差分読解とアプリ調査は Agent ツールで subagent_type: "general-purpose" に投げる。
どちらも大量のファイルを読んで少しだけ返す仕事なので、こちらの文脈を汚さずに済む。
Explore は使わない。 速いが抜粋しか読まないので、根拠: <ファイル:行> を埋める調査や
「service の戻り値が view まで届いているか」の追跡には足りない。取りこぼした結果は
「不明」ではなくもっともらしい間違った表として返ってくるので、こちらでは気付けない。
サブエージェントはまっさらな文脈で起動する。この SKILL.md も、ここまでに読んだファイルも、一切見えていない。
だから:
- 参照させたいファイルは
${CLAUDE_SKILL_DIR}/references/<名前>.md の絶対パスで渡す。
references/investigate.md のような相対パスは、向こうの文脈では解決できない。
逆に自分が読むときは相対リンクでよい(この文書の [dsl.md](references/dsl.md) 等)。
使い分けの基準は「誰が読むか」で、サブエージェントに渡すときだけ ${CLAUDE_SKILL_DIR} を使う
- 会話でユーザーから聞いた前提(対象の PR、ストーリー本文)はプロンプトにそのまま貼る。
「先ほどのストーリー」は通じない
- 返ってくるのはテキストだけ。返却フォーマットを指定しないと散文が返る
1. 引数から入口を決める
上から順に見て、最初に当てはまったものを採る。似ているかどうかの判断ではない。
| # | 引数の形 | 入口 |
|---|
| 1 | 空 | ストーリー編(現在のブランチの差分) |
| 2 | 123 / #123 / PR の URL | ストーリー編(gh pr view --json title,body,files,baseRefName / gh pr diff) |
| 3 | Issue・チケットの URL や番号 | ストーリー編(GitHub Issue なら gh issue view。他のトラッカーはツールやスキルがあればそれを使い、無ければ本文を貼ってもらう) |
| 4 | ブランチ名(git rev-parse --verify が通る) | ストーリー編(git diff <base>...<branch>) |
| 5 | 上記以外の文章 | 下の3問で判定 |
一次資料は常に差分。 Issue も口頭の説明も、意図を掴むための補助でしかない。Issue は「何が欲しかったか」
しか書いておらず、実装が要求どおりとは限らないし、要求に無いものが入っていることもある。だから
Issue や説明が入力のときも、対応する差分を必ず読んで裏を取る。紐づく変更が見つからなければ、
それを言ってユーザーに聞く。
5 のとき次の3問を立て、2つ以上「はい」なら録画編(7章)へ直行、そうでなければストーリー編:
- 画面の並びが読み取れるか(どこを開いて → 何をして → 何を見せて終わるか)。
「〜を直した」のような変更の説明は該当しない
- 見せ場が書いてあるか(何が変わるところを見せたいのか)
- 完了形ではなく、これから見せる形で書いてあるか。 「〜を追加した」は変更の報告、
「〜を開き、〜する」は見せたい流れ。日本語ではこれがいちばんよく効く
迷ったらストーリー編に倒す。 外した場合の損は「これは既にストーリーですか」と1問聞くだけ(数十秒)だが、
録画編に倒して外すと調査 → 台本 → 録画 → 目視を丸ごと1周してから気付くことになる。
base ブランチは決め打ちしない。git symbolic-ref refs/remotes/origin/HEAD(gh があれば
gh repo view --json defaultBranchRef でもよい)で調べる。main を仮定すると master や develop の
リポジトリで差分が丸ごとズレる。gh が無い、GitHub のリポジトリではないこともあるが、git diff で
同じ仕事ができるので「gh が使えないので出来ません」で止まらない。
2. 差分読みサブエージェントを投げる
差分の読解はサブエージェントに任せる。プロンプトには対象(PR 番号・ブランチ名・base ブランチ)を具体的に貼り、
次の仕分け表と返却フォーマットをそのまま含める。
<対象> の差分を読み、画面に見える変化を洗い出してください。判断はせず事実だけ集めます。
| 変更されたもの | 画面に出るか |
| --- | --- |
| `app/views/`、コンポーネント、`*.html.erb` / `.slim` / `.jsx` | ほぼ確実に出る |
| CSS / Tailwind のクラス | 出る(微調整か、レイアウトが変わるのか) |
| `app/controllers/` の redirect 先・flash | 出る(遷移とメッセージが変わる) |
| フロントの JS(バリデーション、モーダル、非同期更新) | 出る(操作への反応が変わる) |
| `config/routes.rb` | 新しい画面が増えたなら出る |
| `app/models/` のバリデーション・スコープ | エラー表示や一覧の中身が変わるなら出る |
| `app/models/` の内部ロジック、`app/services/` | 出力が画面に届くかを追って判断 |
| `spec/`、`test/`、`db/migrate/`、`schema.rb`、依存更新、CI | 出ない |
「サービスの変更だから UI には出ない」と**追わずに決めない**。戻り値が view に
届いていれば、ユーザーには見える変化として届く。PR 本文に「確認手順」や
スクリーンショットがあれば抜き出す(ユーザーが既に見せたいと思っている流れ)。
60行以内で、この形式だけ返してください:
## 画面に出る変化
- <変化の一文> / 根拠: <ファイル:行> / 触る画面: <画面名か URL>
## 画面に出ない変更
- <ファイル群> — <なぜ出ないか一言>
## 気になる点
- 実装が壊れて見える箇所(data-action はあるが data-controller が無い、
i18n キーが locales に無い、view が呼ぶメソッドがモデルに無い等)。断定はせず根拠と
- モバイル向けの兆候(sm:/md: breakpoint、メディアクエリ、ハンバーガーメニュー)
3. 撮る価値があるかを判定する
返ってきた変化を、ユーザー体験が変わるかで選別する。ここは厳しくてよい。基準は「動画で見せられたとき、
受け取った人が動画でなければ分からなかったと思うか」。
撮る:
- 新しい画面・新しい操作が増えた
- 既存の操作の流れが変わった(手順が減った、順番が変わった、確認が挟まった)
- これまで出来なかったことが出来るようになった
- 壊れていた操作が直った(壊れていた状態が想像できる場合に限る)
- 操作に対する反応が変わった(エラーの出方、非同期更新、モーダルの挙動)
撮らない:
- リファクタ、内部実装の入れ替え、パフォーマンス改善
- テストの追加・修正のみ/依存の更新、設定変更、CI
- 文言の修正、色やマージンの微調整
- 既存フローの見た目の調整で、操作の流れ自体は何も変わらないもの
- 管理者しか通らない画面の細かい変更(ユーザーがそう求めた場合を除く)
線引きに迷ったら、撮らない側に倒す。ストーリーを1本増やすのはタダに見えるが、このあと1本ごとに
「調査 → 台本 → 録画 → フレーム目視 → 撮り直し」を丸ごとやるのはこの会話の自分自身で、空振りの1本は
ユーザーの時間を実際に奪う。ただし判定に自信が持てない変化は勝手に落とさず、「これは撮りますか」と
1行添えて聞く。 落としたことを黙っているのが一番よくない。
ここで残りが0本になったら、4章は飛ばして5章へ行き、そこで終わる。 厳しく選別した結果として
0本になるのは正常で、そのための節が用意してある。ひねり出して埋めるところではない。
動かなそうな兆候が出ていたら、先に言う
差分読解の「気になる点」に動かないように見える指摘が上がってきたら、ストーリーと一緒に伝える。
黙って進むと、台本を書き、録画し、タイムアウトで落ちてからようやく原因調査を始めることになり、
差分を読んだ時点で見えていたことに録画1回ぶんを払う。
断定はしない。親のレイアウトで宣言されている、別 PR で入る、といったこちらから見えない事情は普通にある。
「〜が見当たらないので、このままだと〜が動かないかもしれません。録画前に確認してください」と根拠と一緒に
添える程度でよく、ストーリー自体は普通に出しておく。
4. ストーリーに起こす
残った変化を、画面を頭から辿れる流れとして書く。必要なのは「何が変わったか」ではなく
「どこから始めて、何をして、何を見せて終わるか」。
原則1本。多くても3本まで。 1本にまとめるのは、ユーザーが実際に連続して通る流れのときだけ。
「登録画面を直した」と「登録後のダッシュボードを直した」は続けて通るので1本。「登録画面」と
「管理画面の CSV 出力」は別の人が別のタイミングで触るので、無理に繋げると意味の分からない動画になる。
分ける。3本を超えそうなら判定が緩んでいるので3章に戻る。それでも本当に4本以上あるなら、
重要な順に並べて「上から何本撮りますか」と聞く。
ストーリーは1段落の地の文で書く。シーンに割った撮影台本にしない。セレクタ・URL・秒数・ナレーションは
書かない(このあと調査して確定させる)。
良い例:
未ログインの状態でトップページを開き、新規登録フォームからアカウントを作る。
登録直後にダッシュボードへ遷移し、これまで空だった「今日のタスク」欄に
チュートリアル用のタスクが3件あらかじめ入っていることを見せる。
そのうち1件を完了にして、進捗バーが 0% から 33% に変わるところで終わる。
見せ場(進捗バーの変化)が明示されているので、そこに highlight を置けばよいと分かる。始点と終点も
はっきりしている。各ストーリーに次の3つを添える:
- シナリオ名の候補 — 見出しに
## 1. signup_tutorial_tasks — 新規登録直後の… の形で。
そのままファイル名になるので /\A[a-z0-9_-]+\z/(英小文字・数字・_・- のみ)。
日本語やスペースが混じるとロード時に弾かれる
- 対応する変更 — ファイル名を1〜2個。8章でコードを読むときの入り口になる
- 前提データ — 見せ場が成立する件数まで考えて書く。 「タスクが1件」では一括選択の動画は撮れないし、
「請求書が1件」ではフィルタを切り替えても何も起きない。機能が動いて見えるのに必要な最小のデータを書く
差分を読んだからこそ分かる落とし穴があれば、それも書く。 今書かないと失われる:
- 現在時刻に依存する分岐 — 「期限切れ」が
Date.current との比較なら、固定日付で作ると時間が経って
分類が変わる。「due_on は 3.days.ago のように相対で」と書く
- 表示に I18n が要る — 新しいラベルが
config/locales/ 頼りなら、ロケール次第で
translation missing が動画に写る
- 未適用のマイグレーションが要る — 録画前に
db:migrate が済んでいる必要がある
5. 撮るものが無いときは、そう報告して終わる
それが結論なら、そう報告して終わる。無理にひねり出さない。 「PR を渡されたら必ず動画を撮るもの」に
なると、リファクタの PR に対して「一覧画面を開いて、変わっていないことを確認する」のような、誰も見ない
動画が出てくる。報告はこう書く:
- なぜ無いのか — 「変更は
app/services/ の中に閉じていて、app/views/ は1ファイルも変わっていない」
のように、根拠を具体的に
- 何を見て判断したか — 変更ファイルの内訳
- もし撮るとしたら の代替案があれば1行(「強いて言えば〜の画面が対象ですが、見た目は変わりません」)
「撮るべきものはありませんでした」だけで終わらせない。 ユーザーが判断をひっくり返せるだけの材料を残す。
この PR に撮る価値のあるストーリーはありませんでした。
変更は app/services/ と spec/ に閉じていて、app/views/ は1ファイルも変わっていません。
TaskSerializer の出力も変わっていないので、画面に出る差はありません。
見たもの:
- app/services/task_serializer.rb — 内部実装の入れ替え
- spec/services/task_serializer_spec.rb — テストの追加
強いて言えばタスク一覧画面が対象ですが、見た目も操作も変わりません。
撮ったほうがよければそう言ってください。
ここで仕事は終わり。6章以降には進まない。
6. 提示して、止まる
ストーリーは会話に出す。ファイルには書かない(求められたら書く)。そしてここで止まる。
勝手に録画に進まない。 録画は1本あたり数分〜十数分かかるうえ、撮ってみたら見せ場が違ったということも
起きる。ユーザーが「1番を撮って」と言えるようにしておくほうが、結果的に速い。
このPRで撮る価値があるストーリーは1本です。
## 1. signup_tutorial_tasks — 新規登録直後のチュートリアルタスク
<4章の「良い例」の形で1段落>
- 対応する変更: app/services/onboarding/seed_tasks.rb, app/views/dashboards/show.html.erb
- 前提データ: なし(シナリオ内で新規登録する)
撮らないと判断したもの:
- TaskSerializer のリファクタ(出力は変わっていない)
- タスク一覧の N+1 解消(画面の見た目は同じ)
撮らなかったものも並べる。 ユーザーは PR の全体像を知っているので、言及の無い変更があると
「見落としたのか、判断して外したのか」が分からない。理由も1行添える。
待ちが解除される条件は「ユーザーが撮る対象を名指しした」こと。 解除されたら7章へ進む。
それまでは次の章を読まない。TodoWrite に「ユーザーの選択待ち」を1件残しておくと、待ちが自分でも見える。
- 「いいね」「ありがとう」は感想であって名指しではない。どれか分からなければ番号を聞く
- 1本しか出していなくても解除条件は同じ。 「選ぶ余地が無いから待つ必要も無い」は成立しない。
ユーザーはまだ撮ると言っていない。「これで撮りますか」と聞く
録画編(ユーザーが選んでから)
7. 前提を確認する(未整備なら中断する)
デモ関連ファイルの置き場所は spec/demos と test/demos のどちらでもよく、demo_reel は
実在する方(両方あれば spec/demos)を使う。まずどちらなのかを確定させる。
ls spec/demos/demo_helper.rb test/demos/demo_helper.rb 2>&1
ls -d spec/demos/scenarios/ test/demos/scenarios/ 2>&1
ls node_modules/.bin/playwright 2>&1
見つかった側(両方あるなら spec/demos)がこのプロジェクトの <demos_dir> で、
以降の章に出てくる <demos_dir> はすべてこの値に読み替える。どちらも無ければ未整備
なので次段落のとおり中断する(このとき demo_reel 自身は spec/ か test/ のある方に
寄せて探すが、その判断はユーザーに委ねる)。
ブートファイルか playwright CLI が無い場合は、セットアップを代行せずに中断し、
setup.md の内容をもとに何が足りず何をすればよいかを案内する。ブートファイルは
アプリごとに正解が違う(RAILS_ENV、config.prepare のビルドコマンド、test_id_attribute の慣習)
ので、雛形を置くと後で必ず直すことになる。
中断するときも、8章の調査だけは済ませてから案内する。 そのアプリを読めば test_id_attribute に
何を設定すべきか・config.prepare が要るのかが分かり、それを添えればユーザーは調べ直さずにブートファイルを
書ける。最後に「こちらで作ってよければ作ります」と一言添える。中断の目的は作業を止めることではなく、
アプリ固有の判断をユーザーの手に残すことにある。
両方あれば cat <demos_dir>/demo_helper.rb と bundle exec demo_reel list を見る。既存シナリオが1本でも
あるなら、それがこのプロジェクトのお手本になる。 1本読んで、ログインの仕方・factory の使い方・chapter の
粒度を踏襲する。新規に流儀を作るより、既にあるものに揃えるほうがレビューが通りやすい。
8. アプリ調査サブエージェントを投げる
ストーリーだけではパスヘルパー名もセレクタも分からない。台本を書く前に必ずアプリのコードを読む。 想像で書くと
録画が Playwright のタイムアウトで落ち、10秒 × 失敗箇所ぶんを失う。
まず ${CLAUDE_SKILL_DIR}/references/investigate.md を読んでから作業してください。
対象のストーリー:
<ストーリー本文をそのまま貼る>
ブラウザは開かないこと(--headed での試写もしない)。すべてコードを読んで決めます。
分からなかった欄は推測で埋めず「不明」と書いてください。
| 画面 | パスヘルパー | 操作 | セレクタ | 根拠 |
| --- | --- | --- | --- | --- |
| ダッシュボード | dashboard_path | 完了ボタンをクリック | by_test_id("task-<id>-complete") | app/views/tasks/_task.html.erb:12 |
表のあとに4つを箇条書きで(各3行以内):
- ログイン方法 / 必要な factory / **テスト ID が無かった要素**(あれば、どのファイルの
どの要素に何を足せばよいかの提案。編集はしないこと)/ 代替セレクタで済むならその案
最後に1行: sm:/md: の breakpoint がどのくらいあるか
(ほとんど無い / 目立つ量ある / PC とスマホで構造が変わる)
上記の形式だけを返してください。前置きと感想は不要です。ただし
**セレクタの信頼度が低い箇所(条件分岐の中にある、パターンから組み立てた等)があれば、
その行の根拠列に一言添えてください。** 録画が落ちたときにそこから当たります。
返ってきた表に「不明」が残っているなら、台本を書き始めない。その画面だけを対象に調査を投げ直すか、
ユーザーに聞く。埋まっていない欄を想像で埋めるのが撮り直しの最大の原因。
data-testid の追加が提案されていたら、それを足すのは自分の仕事(調査は読み取りのみなので
向こうは足せない)。代替セレクタで済むならそちらを採り、足したなら12章の報告に必ず含める。
端末を決める
既定は PC 版だけ。 1シナリオ = 1動画 = 1解像度なので、スマホ版を撮るなら別シナリオを1本足すことになり、
台本も録画も目視もすべて2倍になる。両方撮るのは、次のどれかに当てはまるときだけ:
- ユーザーが明示的にスマホ版を求めた
- 差分がモバイル向けだった(差分読解の「気になる点」に出ている)
- 調査で「PC とスマホで構造が変わる」と返ってきた
- 機能自体がモバイル前提(カメラ、位置情報、外出先での入力)
迷ったら PC 版だけを仕上げる。 報告時に「スマホ版も撮りますか」と添える。台本を流用できるので追加は
安い。両方撮ると決めたら、命名は <pc版>_mobile に揃える。
9. 台本を書く
7章で確定した <demos_dir>/scenarios/<name>.rb を作る(spec/demos/scenarios/ か
test/demos/scenarios/。既存シナリオがあるならその隣に置く)。
name はそのまま動画のファイル名になるので
/\A[a-z0-9_-]+\z/。日本語のタイトルは第2引数 description に置く。
DemoReel.scenario("onboarding", "新規登録からタスク完了まで") do
def login_as(user)
page.goto(login_path)
slow_type(by_test_id("email"), user.email)
slow_type(by_test_id("password"), "password")
page.get_by_role("button", name: "ログイン").click
beat
end
user = create(:user, name: "山田太郎")
chapter("ログイン", description: "メールアドレスとパスワードを入力します")
login_as(user)
chapter("ダッシュボード", description: "今日のタスクが一覧で確認できます")
caption("未完了のタスクがここに並びます")
caption("タスクを完了にします") do
by_test_id("task-1-complete").click
end
highlight(by_test_id("task-count"), label: "残りタスク数が減りました")
beat_long
end
DSL の署名・既定値・デバイスプリセット・設定項目は dsl.md にある。データは
factory_bot がそのまま使え、録画はトランザクションで包まれてロールバックされる(制約は dsl.md
「factory_bot とデータベース」)。ここには動画の質を左右する判断だけ書く。
演出の組み立て方
ストーリーをチャプター単位に割る。「〜して、〜して」の区切りがだいたい境目になる。
chapter の後に beat / wait を足さない。 chapter は duration が切れるまでブロックするので、
重ねると1チャプターごとに2倍待ち、動画が間延びする。
caption のブロック形式は画面が変わらない操作だけに使う。 duration を持たない貼りっぱなしの
字幕なので、中で画面遷移すると遷移後の画面に無関係な字幕が残る。遷移を伴う操作は、遷移の前に
duration 付きの caption(text) を出してから操作する。
highlight はクリックしない要素のためのもの。 クリックには show_actions が自動でカーソル・枠・
ラベルを付けるので重ねても情報が増えない。バッジの数字が変わった、ボタンが enabled になった、
といった変化に使う。
highlight の label は対象の真下に固定で出る。 衝突判定は無いので、すぐ下に別の要素がある
小さなインライン要素(一覧の件数バッジなど)に付けると、下の入力欄やボタンを覆い隠す。
録画は成功しエラーも出ないので、フレームを見ないと気付けない。 被るなら label を外して枠だけに
するか、下に余白のある要素を対象にする。
highlight の対象が画面外(スクロール前)にあるなら、scroll_into_view_if_needed を呼んでから
beat を挟んで highlight する。 highlight は locator.bounding_box でその場の座標を読んで
赤枠を描くだけなので、要素が画面外にあれば枠も画面外に描かれて一度も見えないまま duration が過ぎる。
scroll_into_view_if_needed の直後は実際のスクロール(アニメーション)が終わっていないことがあり、
間に beat を挟まず即 highlight を呼ぶと座標がずれてやはり見えないことがある。録画は成功しエラーも
出ないので、フレームを見ないと気付けない。
beat_long を2回続けない。 ただの尺の水増しになる。
sleep は絶対に使わない。 Playwright のイベントループを止める。wait(ms) を使う。
DSL が演出を担当し、要素の操作は Playwright の生 API(page)が担当する。page.goto /
page.get_by_role / page.locator は遠慮なく使ってよく、DSL に無いことをやりたくなったら page に
降りるのが正解。 page.goto の遷移先は routes のパスヘルパー(login_path など)で書く
— _url は host が無いので Missing host to link to! で落ちる。
スマホ版の台本
DemoReel.scenario(..., device: :mobile) と渡すだけでよい。中身は PC 版と同じで通る — パスも
セレクタも変わらない(変わるのは viewport とエミュレーションだけ)。ただし台本そのものを共有しようと
しないこと。 シナリオブロックは instance_eval されるので、片方で定義した def は他方から呼べない
(そのシナリオ1本の特異メソッドにしかならない)。共通化したいならモジュールに切り出して
config.include する。それ以外は素直に2本書く。
演出もそのままでは狭くて成立しない。幅 393px を前提に文字量を削る:
chapter の description は1行20文字が目安。 ここだけは gem から幅を制御できない
(Playwright 側がチャプターカードを描いており、demo_reel は文字列を渡すだけ)。長いと切れるか潰れる
caption は折り返すので画面外には出ないが、3行を超えると操作している箇所が隠れる。
25文字以内に収める
highlight の label が特に危ない。 ラベルの被り(前述)はスマホ幅では下の要素をほぼ確実に
覆う(PC では横に余白があって助かっていただけ)。label を外して枠だけにするか、caption に置き換える
- 縦長だからといって
beat を増やさない。 スクロールが増えるぶん尺は自然に伸びる。そこに間を足すと、
ただ止まっているだけの動画になる
10. 録画する
bundle exec demo_reel record <name>
引数なしの record は全シナリオを撮るので、名前を必ず指定する。出力は config.output_dir
(既定 tmp/demos/)に <name>.webm。録画は ensure の中で止まるので、台本が途中で落ちても、
落ちるまでの動画は残り、失敗の診断に使える。撮り直しでは --no-prepare でアセットビルドを飛ばせる
(初回だけはビルドさせる)。
コマンドがタイムアウトして返ってこないときも、原因を推測する前にまず途中までの動画を見る。
page.screencast.start は録画開始直後に呼ばれるので、シナリオがどこかの page.get_by_... や
click で待ち続けて固まっていても、そこに至るまでの操作は既に tmp/demos/<name>.webm に
書き込まれている。ログイン画面から一向に進まない、ある操作の後でハングする、といった症状が
出たら、コマンドを止めて(または timeout 付きで一度打ち切って)動画を確認するのが最短経路。
11. 動画を見て、直して、撮り直す
コマンドが 0 で終わっただけでは完了ではない。 ls -lh tmp/demos/<name>.webm でログの saved: と
ファイルサイズを確認する(数十 KB しかないなら、ほぼ何も映っていない)。
そのうえでフレームを抜いて実際に目で見る。ここがこの工程の本体。 手順は frames.md
(同梱 ffmpeg の解決、連番 png、コンタクトシート、見つからないときの扱い)。抜いたフレームには
カーソル・アクションラベル・caption・チャプターカードまで合成済みで写る(page.screencast が
焼き込むため)。逆に page.screenshot は DOM を描くだけでこれらが写らないので、目視には使えない。
この目視はサブエージェントに投げない。 画像を何枚も読むので投げたくなるが、判断材料は
「何を見せたい台本だったか」であって、それを知っているのは台本を書いた自分だけ。収集は委譲してよいが、
評価は委譲しない。
見るべき点:
- 字幕・ラベルが要素に被っていないか。 ラベルの被り(前述)が実際に起きていたら、label を外すか、
下に余白のある別の要素を対象にするか、
caption に置き換える
highlight の赤枠が実際に画面に写っているか。 台本通りに動いていても、対象が画面外にあると
枠が一度も見えないまま次に進む(前述)。1〜2秒刻みの粗いサンプリングだと duration(既定2秒)の
表示ウィンドウをそのまま外して「写っていない」と誤判定しやすいので、疑わしい箇所は0.2〜0.5秒刻みで
細かく抜き直してから判断する
- チャプターカードが意図した位置・尺で出ているか
- 空白の画面や Rails のエラー画面が写っていないか(遷移待ちの不足を疑う)
- 入力やクリックがちゃんと映っているか(速すぎて一瞬で終わっていないか)
- 画面下部が大きく余っていないか。 viewport が大きすぎるサイン。
config.default_device = { viewport: { width: 1000, height: 620 } } のように詰める
- スマホ版なら、字幕とラベルが幅に収まっているか。 折り返すので画面外には出ないが、3〜4行になると
操作箇所が隠れる。隠れていたら文字を削る。さらに横スクロールバーや極端に小さい文字が写っていたら、
それはアプリ側がその幅に対応していない。 viewport を広げて誤魔化すと「実機ではこう見えない動画」に
なるので、動画を直そうとせず、どの画面のどこが崩れていたかを添えてユーザーに報告する
落ちた場合、エラーメッセージが原因をほぼそのまま教えてくれる:
| 症状 | 何が起きているか | 対処 |
|---|
Timeout ... waiting for locator | セレクタが実際の DOM と合っていない | 表の根拠列のファイルを読み直す。テスト ID が無いなら足すか role で指す |
strict mode violation | 同じテキストの要素が複数ある | テスト ID か role で絞る |
| Rails のエラー画面が動画に映っている | アプリ側が 500 を返した | エラーページが写っているのでそこから原因を読む。データ不足のことが多い |
playwright CLI が見つかりません | npm 側が未インストール | pnpm install && pnpm exec playwright install chromium を案内する |
unknown scenario: ... | 名前の綴り違い、または glob に載っていない | bundle exec demo_reel list で登録名を確認 |
原因を直したら撮り直す。 直した「つもり」で終えない。セレクタ1つの修正でも、撮り直して saved: を
見るまでは直った保証がない。同じ箇所で3回直しても通らないときは、深掘りより、何を試して何が起きたかを
添えてユーザーに相談したほうが早い。アプリ側の前提(シードが要る、外部サービスに繋ぐ)をこちらが
知らないだけ、ということが多い。
12. 完了したら報告する
- 動画のパス(
tmp/demos/<name>.webm)と長さ
- シナリオファイルのパスと、チャプター構成の要約
- フレームを見て確認した結果(何を見て、どう判断したか)
- アプリのコードに手を入れたなら、そのファイルと理由(
data-testid の追加など)
- 撮り直した場合は、何が原因で何回撮り直したか
フレームで確認できるのは「映っているもの」であって、テンポが心地よいか・字幕の分量が好みかといった
感覚の部分は再生してみないと分からない。そこは断定せず、ユーザーに見てもらう。
直しきれなかった不満点があるなら、黙って渡さずに書く。「ラベルが入力欄に少し掛かっているが、対象を
変えると別の要素に掛かるのでこのままにした」のように、判断とその理由まで書いてあると、ユーザーは
動画を見る前に何を見ればいいか分かる。
参照ファイル
references/dsl.md — DSL の署名・既定値、デバイスプリセット、設定、factory_bot と DB の仕組み。9章で台本を書くとき
references/setup.md — spec/demos/(または test/demos/)未整備のときにユーザーへ案内する内容。7章で中断するとき
references/frames.md — ffmpeg でフレームを抜く手順。11章で目視するとき
references/investigate.md — 調査の観点とセレクタの選び方。8章でサブエージェントに読ませる