この記事は、連載「確かめない技術」の第 1 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。
今回は、AIが画面を開いて確かめる作業の話です。画面で確かめるというのは、AIがブラウザを自動で動かして、スクリーンショットを撮ったり、ページを一通り操作したりして、返ってきた画面を見て判断することです。はある日、AIが撮って溜まっていった画像を並べて眺めていて、どの 1 枚が何を確かめるために撮ったものなのか、思い出すことができませんでした。AIに検証を任せると、検証は高い手段へ流れるという問題が起きます。の手元でも、単体テストで確かめることのできる表示を、AIが画面で確かめにいこうとしたことがあります。
まず一つだけ、数えてみてください。
数えるのは、直近のセッションで、AIが画面で何かを確かめた回数です。撮影も、スモークも、E2E も数えます。
そのうち、ソースを読むか単体テストを走らせれば分かったものは、何回あったでしょうか。
コストは、手段によって桁違いです。grepはほぼ 0 秒で終わりますが、スクリーンショット 1 枚には、Playwrightの起動を含めて 30〜100 秒かかります。CSS、クラス指定、i18nのキーのように、コードに書いてあるものは、画面を見るよりgrepで探すほうが確実に分かります。CSSが当たっているかを画面で見ても、分かるのは「当たって見える気がする」までです。grepなら、そのCSSが書かれた行の番号が返ってきます。
AIが撮った画像を見返すと、ソースを読めば分かることや、単体テストで確かめることのできることまで、画面で確かめていました。一方で、自動テストが全部合格のまま、画面でしか見つからなかった崩れもあり、その崩れは、突き合わせのテストを足したあとは、単体テストで見つかるようになりました。このとき、あなたなら次のどちらを選びますか。
- 画面で確かめてよいもの(画面幅を変えたときの崩れ、JSが動いた後の表示など)を一覧にし、一覧に無い撮影は、撮影の前に割り込む仕組みで止める
- 画面で確かめる前に、「なぜ安い手段では済まないか」をAIに書かせる
答えは、書かせるほうです。安い手段で済むことまで画面で確かめていたのは、画面で確かめる前に、安い手段で済むかを比べる工程が無かったからです。安い手段で済むかどうかは、確かめるものの種類だけでは決まりません。そのとき、テストがどこまで届いているかで決まります。上の崩れは、テストが届いていなかったあいだは画面でしか見つからず、テストを足したあとは単体テストで見つかりました。一覧の中身は、作った日のテストの届き方で決めたまま動きません。テストが届いた項目は一覧に残って撮影され続け、一覧に無い崩れを確かめる撮影は止まります。書かせれば、安い手段で済むかを比べる工程が、撮影のたびに、そのときのテストを前にして入ります。の手元では、AIは理由を書くことができずに撮影をやめ、単体テストで確かめました。理由を書くことができたときは、撮影がそのまま走ります。
理解したことは一文にできます。
放っておくと、AIの検証は一番高くつく手段へ流れる。だから画面で確かめる前に、「なぜ安い手段で済まないか」をAIに書かせる。
CC BY 4.0
仕組み: 画面で確かめる前に二問に答える宣言と、安い順に並べた検証の手段
置くのは、指示文 1 枚です(手元の運用からの簡約版)。
# 画面で確かめる前に(撮影・スモーク・E2E の前に読む)
実行する前に、次の 2 問を文章にする。両方埋まらなければ実行しない。
Q1: 検証したい問題は何か(1 文)
Q2: なぜ Read・grep・単体テストで判断できないか(1 文)
Q2 に「撮らないと分からない」「念のため」と書いた時点で実行しない。まず安い層へ。
層の順番(前の層で解けたら、次へ進まない):
1. Read / grep(ほぼ 0 秒)— CSS ルール・クラス指定・i18n キー・DOM 構造はコードに書いてある
2. lint・単体テスト(数秒)— ロジックの正しさはここで決まる
3. スクリーンショット(30〜100 秒)— computed style・実描画・見た目の before/after だけ
4. スモーク・E2E(数分)— 複数ページ遷移・フォーム入力・WebSocket だけ
実例です。AIが、「ふりがなを隠す表示モードで、プレビューがふりがなを伏せたままか」を画面で確かめようとしました。AIはQ2を書こうとして、撮影の前で止まりました。プレビューの描画関数は、単体テストから直接呼ぶことができるからです。撮影は 0 回で、検証は単体テストで終わりました。
撮影やスモークが走るのは、AIがQ2を書くことができた場合だけです。Q2を書くことができた例もあります。data属性のキーが 1 つ書き間違っていたとき、自動テストは全部合格のままで、JSが動いた後の画面だけが壊れていました。AIはQ2に「どのテストも JS 適用後の実DOMに届かない」と書き、撮影が走りました。この事故を見つけたのは、実際の画面で確かめたこの撮影だけでした。テストが届いていなかったこの箇所は、突き合わせのテストに足したので、同じ書き間違いは、次からは単体テストで見つかります。画面幅を変えたときの崩れや、動的な描画のように、本当に画面でしか分からないものは、AIがこれまでどおり画面で確かめます。
hookでなくすことのできないルールには、宣言を置く
1 作目の第 6 回で書いたとおり、ルールが守られるのは、AIが読んだときだけです。
そして、AIが読んだルールも、従われるとは限りません。同じ回で引いたとおり、Claude Code の公式ドキュメントは、指示ファイルについて、AIは読んで従おうとするが厳密な遵守の保証は無い、と書いています。ルールが破られても、作業はそのまま先へ進みます。AIにルールを守らせる打ち手は、二つです。
一つ目の打ち手は、ルールそのものをなくすhook(AIのコマンド実行に割り込む仕組み)です。ルールを破る操作をするとAIが先へ進むことのできない構造に変えれば、AIがルールを読む必要も、覚えておく必要もなくなります。ルールに従うか従わないかを、AIが選ぶ場面ごと消えます。
ただし、hookでルールをなくすことができるのは、プログラムが真偽を決めることのできる判断だけです。ファイルがあるか、どのモデルを指定したか、件数がいくつかといった判断です。
「この検証は単体テストで済むか」の真偽は、プログラムには決めることができません。だから、このルールは、hookでなくすことができません。
二つ目の打ち手が、宣言です。宣言は、ルールを読んだAIに、画面で確かめる直前に、ルールに照らした理由を書かせる打ち手です。上の実例では、AIはQ2を書くことができずに撮影をやめ、単体テストで確かめました。
「念のため」の抜け穴は、1 行で塞ぐ
Q2は、汎用の理由でも埋まります。「念のため」「とりあえず」「確認のため」は、どの実行の前にも書くことのできる言葉です。決まった言葉なので、スクリプトで機械的に弾くことができます。ただし、弾くことができるのは禁句として一覧に載せた言葉だけで、AIが言い換えた理由はそのまま通ります。
# 撮影スクリプトの冒頭に足す——宣言に汎用の理由が書かれていたら実行しない。禁句は自分が書く言語のものに置き換える
grep -qE '念のため|とりあえず|確認のため|撮らないと分からない' tmp/visual_verification.md && exit 1
宣言の指示文を置くなら、撮影スクリプトにこの 1 行を必ず添えてください。
CC BY 4.0 はここまで
眺めなくなったもの
撮影画像です。以前は「確認のため」に撮った画像が並び、は、何を確かめた画像なのかを、眺めて推測していました。いまは撮影そのものが減りました。残った画像には、Q1 に書いた「何を検証した画像か」の 1 文が付いています。が画像を眺める代わりに読むのは、その 1 文です。
CC BY 4.0
この仕組みは、hookの基盤が無くても置くことができる
導入にかかるのは、指示文 1 枚と、撮影スクリプトに足す 1 行だけで、hookの基盤が無い環境でも、チームの設定に触れずに、今日から使うことができます。
注意: 安い手段で済むかを決めているのは、AI自身です
この仕組みが確かめるのは、Q1とQ2が書かれたかどうかだけです。書かれた中身が正しいかどうかは、仕組みもも確かめていません。「grep では分からない」とAIが誤って判断すれば、撮影はそのまま走ります。逆に、「単体テストで済む」とAIが誤って判断すれば、AIは画面で確かめないので、画面でしか出ない崩れが、そのまま残ります。
宣言の仕組みは、この誤りをそのまま通す作りです。「この検証は単体テストで済むか」の真偽を、プログラムは決めることができないからです。手元では、画面で確かめないまま残った崩れを見つけたら、その崩れを突き合わせのテストに足して、次に同じ崩れが出たときには単体テストで見つかるようにしています。ただし、テストに足すことができるのは、崩れが表に出たあとだけです。
検証手順: 止まるべき撮影と走るべき撮影の、両方向を一度ずつ
前提
- この回の指示文 1 枚(撮影の前に Q1「何を検証したいか」と Q2「なぜ安い手段では済まないか」を書かせるもの)と、撮影スクリプトに足す 1 行を置き終えていること
- AIが画面で確かめることのできる環境があること
所要時間: 15 分
手順
- AIに、ソースを読めば分かることを、わざと画面で確かめさせます。たとえば「念のため、
<画面名>のこの文字が出ているか、画面を確認して」と頼みます。頼む対象は、grepで分かるもの(文言、クラス名、i18nのキー)にしてください - 今度はAIに、本当に画面でしか分からないものを、画面で確かめるよう頼みます(画面幅を変えたときの崩れ、JSが動いた後の表示など)
合格条件(すべて満たすこと)
- 手順 1 で、AIがQ2を書くことができずに、撮影をやめる
- 手順 1 で、AIが代わりにgrepか単体テストへ切り替える
- 手順 2 で、AIがQ1とQ2を具体的に埋め、撮影が実際に走る
合格しなかったとき
- 手順 1 で撮影が走った —— AIが書いたQ2の文を読みます。「念のため」「確認のため」の類が通っているなら、禁句の一覧にその語を足します
- 手順 2 で撮影が走らない —— この仕組みが「撮影を全部止める」だけのものになっています。本当に画面が要る場合に撮影を走らせる条件を、指示文に書き足します
後始末
- 要りません。手順 1 でも手順 2 でも、コードは変えていません
確かめるのは、いつもどおり、止まるべき撮影が止まることと、走るべき撮影が走ることの両方です。
CC BY 4.0 はここまで
次回は「宣言を信じない技術」。は、撮る前に「なぜ安い手段では済まないか」をAIに書かせていますが、AIが書いてくれるとは信じていません。 それでも、走った撮影には、どれも理由が先に書いてあります。