「購入ボタン」のテキストやclass名が変わるたびに、昨日まで通っていたE2Eテストが落ちる。直しても、次の改修でまた同じことが起きる。E2Eテスト(ブラウザやアプリを実際に操作して、画面の結果まで確認するテスト)を書いたことがある人なら、一度は経験している保守コストだと思う。
この面倒を、AIに自然言語で指示するテストなら避けられると聞いたことがあるかもしれない。「購入ボタンを押す」と書いておけば、ボタンの見た目やclass名が変わってもAIが見つけてくれる。ただし、そこで次の心配が浮かぶ。CIでテストを何百回も回すなら、そのたびにAIへ問い合わせるコストと待ち時間がかかるのではないか。
TesterArmyが2026年10月1日に公開したOSSのテストフレームワーク「e2e」(公式ブログ)は、この心配に具体的な仕組みで答えている。AIに操作や判定を任せるのは、その手順が初めて成功するまでの1回だけ。以降はその記録をそのまま再生するので、2回目からはAIを呼ばずに、スクリプトで書いたテストと同じ速さで動く。
自然言語のテストは重くならないか
「e2e」は、Web・iOS・Androidのアプリを対象にしたE2Eテストフレームワークだ。Playwrightのような役割・名前を指定するロケータと、自然言語で目標を書く「エージェントステップ」を、同じテストの中で組み合わせられる。公式リポジトリのREADMEに載っている例はこうなっている。
test('a member upgrades to Pro', async ({ app, agent, screen }) => {
await app.open('/settings/billing');
await agent.act('upgrade the workspace to the Pro plan');
await agent.assert('the invoice preview shows a prorated amount');
await expect(screen.getByRole('status')).toContainText('Pro');
});
agent.act()に目標を自然言語で渡すと、エージェントが画面を見て必要な操作を行う。agent.assert()も同じく自然言語で、期待する状態を検証する。ボタンのセレクタを1つずつ指定する必要がない(GitHub公式リポジトリ)。
公式ブログは、この種の自然言語ベースのテストが元々抱えていた課題もはっきり書いている。「エージェントは『このリストで最も安い商品を買う』というゴールを完了できるが、テストが成功しても何が正確に検証されたのか不明確」だという(公式ブログ)。目標は達成できても、何を確認したテストなのかが曖昧になりやすい、という問題だ。
agent.actとagent.assertを分けて書けるようにしたのは、この曖昧さへの対策になる。ただ、操作と検証をAIに任せる設計である以上、「CIで毎回AIを呼ぶなら高くつくし遅いのでは」という冒頭の心配は、まだ解決していない。ここから先が、「e2e」が独自に用意した仕組みになる。
なぜ2回目からはAIを呼ばなくて済むのか
「e2e」は、成功したエージェントステップの操作を「トレースキャッシュ」に記録する。公式ブログの説明を引く。
When an agent step passes with a recorded check, e2e caches its actions and replays them on later runs without calling the model, so a cached step runs at the speed of a scripted test and costs no tokens. (エージェントステップが記録済みのチェックに合格すると、e2eはその操作をキャッシュに保存する。次回以降の実行ではモデルを呼び出さずに再生するので、キャッシュされたステップはスクリプトテストと同じ速度で動き、トークンも消費しない) — TesterArmy公式ブログ
自分は最初、ここを「AIが同じ判断をもう一度下している」のだと思っていた。公式ドキュメントを読むと、起きているのはそれとは違う。1回目にAIが実際に行った操作(どの要素を、どう操作したか)をそのまま記録に残し、2回目以降はその記録を機械的に再生しているだけだ。「後続のチェックが検証したactステップは記録される。次回の実行ではそれを再生し、アプリが変わっていたらエージェントへ処理を戻す」(公式ドキュメント)。
この記録と再生の一致判定は、具体的には「役割(role)・名前(name)・test id・周辺のコンテキスト」で行われる(公式ドキュメント「cache」)。つまり、キャッシュが効いているときの「e2e」は、AIに何かを問い合わせているのではなく、前回成功した操作の記録を、今の画面と照合しているだけになる。
実際どれくらい変わるのか
この仕組みがどれだけの差を生むかは、公式発表には具体的な数値が出ていない。個人の開発者がTodoアプリ向けのテストで検証した記事(azukiazusa.dev)に、実測値が載っている。
await agent.act("Todo に「牛乳を買う」を追加してください");
await agent.assert("Todo に「牛乳を買う」が追加されていることを確認してください");
このテストを初めて実行したときは6.90秒かかり、モデル呼び出しが3回発生した。トレースキャッシュが記録されたあと、同じテストをもう一度実行すると、169ミリ秒・モデル呼び出し0回で終わった(azukiazusa.dev)。
この数字は公式のベンチマークではなく、開発者が自分のテストコードで1回計測した例である。アプリの複雑さやモデルの応答時間によって変わるはずだ。それでも、「記録を再生するだけ」という仕組みの説明とこの実測値を並べると、2回目以降のテストがなぜここまで軽くなるのかが具体的につながる。
キャッシュはいつ効かなくなるのか
記録をそのまま再生する以上、画面がキャッシュの前提と変わったときは、AIに判断を戻す必要がある。公式ドキュメントは、キャッシュが無効になり処理がエージェントへ戻る条件を3つ挙げている(公式ドキュメント「cache」)。
- 録画時と異なるUI構造が検出された場合。操作対象を role・name・test id・周辺のコンテキストで照合しており、一致しなければ引き継ぎになる
- 録画時になかった想定外のアラートが表示された場合
- 記録したページ遷移(ルート)が再現されなかった場合
この3条件に当てはまらない限り、2回目以降はAIを呼ばない。UIを改修してテストが一度落ちれば、その回だけAIが新しい操作を記録し直し、次回からまた再生に戻る。「毎回AIが遅い・高い」という状態には戻らず、「変更があった回だけコストが発生する」という動き方になる。
向く人とまだ対応していないもの
「e2e」はWeb(Chromium・Firefox・WebKit、Playwright経由)・iOSシミュレーター・Androidエミュレーターに対応している。一方、デスクトップアプリはまだ対応していない(公式ブログ、gihyo.jp)。モデルはChatGPT Plus/Pro・GitHub Copilot・SuperGrokなどのサブスクリプション契約があればAPIキーなしで使える。ローカルモデルでも動き、料金は「プロバイダー価格でトークンを支払い、追加のマークアップはない」という(gihyo.jp、公式ブログ)。
Playwrightで書いたセレクタ指定のテストをすでに持っている場合、全部を書き換える必要はない。READMEの例が示すとおり、役割ベースのロケータとエージェントステップは同じテストの中で混ぜて使える。UI変更に弱い箇所だけagent.act・agent.assertに置き換え、初回の記録さえ済ませてしまえばいい。2回目以降のコストは据え置きのまま、保守の手間だけ減らせる。試すならnpx e2e initでエンジン(webまたはmobile)とモデルプロバイダーを選ぶところから始められる(GitHub公式リポジトリ)。
まとめ
TesterArmyが公開した「e2e」は、自然言語でE2Eテストの操作・検証を書けるフレームワークだ。それだけでなく、操作をAIに判断させるのは最初に成功するまでの1回に絞っている。以降はrole・name・test id・周辺のコンテキストで画面と照合しながら、記録を再生する「トレースキャッシュ」を備えている。
個人の検証では、初回6.90秒・モデル呼び出し3回だったテストが、再実行では169ミリ秒・0回まで縮んだ。キャッシュが効かなくなるのは、UI構造の不一致・想定外のアラート・ルートの不一致が起きたときに限られる。セレクタの保守に疲れていた人にとって、自然言語の柔軟さとスクリプトの速さ・低コストを両立させる選択肢が増えたことになる。
コメント
気づきや感想をどうぞ記事への補足や、読んで考えたことをお寄せください。個人情報や、他の人を傷つける内容の投稿はお控えください。
投稿にはGoogleログインが必要です。
Googleでログイン投稿にはGoogleアカウントの表示名が公開されます。メールアドレスは取得・公開しません。
コメントを読み込んでいます…