自然言語で書くE2Eテストは、Playwrightの手書きより保守が楽か?

早瀬涼
早瀬涼

@hys_tt ・ 1本

サムネイル

UIの文言を1つ変えただけで、E2Eテストが何本もロケータを見失って落ちる。そのたびにセレクタを直す作業に、時間を取られていませんか?

自然言語で書くE2Eテストは、画面が変わりやすい操作手順の保守を、Playwrightの手書きより楽にしてくれます。ただし確認の部分は手書きと同じだけ手が要り、UIが変わったときは「落ちずに通る」という別の壊れ方をします。

TesterArmyのe2eは、Playwrightの上で自然言語のステップを動かす

e2eは、TesterArmyが2026年10月1日に公開を発表したE2Eテストのフレームワークです。ゴールを文章で書くと、エージェントがブラウザを操作してそこへ向かいます。Web版はPlaywright経由でChromium、Firefox、WebKitを動かし、iOSとAndroid向けのエンジンも別パッケージで用意されています。

テストの中で使い分けるのは3種類です。agent.act() が自然言語の操作、agent.assert() が自然言語の確認、expect(locator) がいつものロケータでの確認で、1本のテストに混ぜて書けます。自然言語のステップを含まないテストは、モデルなしで動きます。

ライセンスはApache-2.0で、GitHubのスターは約9,200(2026年10月12日時点)。執筆時点の版は0.19.0で、READMEには1.0に向けて開発中で、APIと設定はマイナーリリースの間でも変わりうると書かれています。

同じログインを、ロケータと自然言語の2通りで書いた

動かした環境は使い捨てのコンテナ(node:22-bookworm)で、モデルのAPIキーは渡していません。確かめられたのはキーなしで動く範囲までです。

npm i -D e2e@latest @e2e-dev/web@latest は45パッケージで約4.7秒、npx e2e init --yes は約3.3秒で終わりました。initは設定ファイルの e2e.config.ts とサンプルテストに加えて、Claude Code用のSkillと .mcp.json まで一度に置いていきます。

メールとパスワードを入れると「ようこそ、デモさん」と出るだけの架空のログイン画面を用意し、同じ操作を2通りで書きました。ロケータ版はこうです。

test('ログインできる(ロケータで手書き)', async ({ app, browser }) => {
  await app.open('/');
  await browser.locator('#email').fill('demo@example.com');
  await browser.locator('#pw').fill('pass1234');
  await browser.locator('#login-btn').click();
  await expect(browser.locator('#msg')).toContainText('ようこそ');
});

自然言語版では、入力とクリックの3行が1行の指示になります。

test('ログインできる(自然言語)', async ({ app, agent, browser }) => {
  await app.open('/');
  await agent.act('メールアドレス demo@example.com、パスワード pass1234 でログインする');
  await expect(browser.locator('[role=status]')).toContainText('ようこそ');
});

ロケータ版は1回目も2回目も合格しました。1回目はe2eがブラウザ(Chrome Headless Shell 153)を自分で取得する準備に14.95秒かかり、コマンド全体で約16.5秒です。先に npx playwright install で入れたブラウザ(156)とは別の版を取りにいったので、CIでは初回のこの待ち時間を見込んでおいたほうがよさそうです。2回目はDurationが750ms、全体で約1.6秒でした。

自然言語版は、キーがないので171msで落ちました。

MODEL_PROVIDER_FAILED: the model provider failed: Unauthenticated request to AI Gateway.

To authenticate, set the AI_GATEWAY_API_KEY environment variable with your API key.

何が足りず、どの環境変数を設定すればいいかまで書いてあり、迷わず読めました。initが書いた設定ではモデルがVercel AI Gateway経由になっているので、OpenAIやAnthropicのキーを使う場合は e2e.config.ts のimportを書き換えます。

自然言語のE2Eテストは、2回目からモデルを呼ばないのか?

e2eの売りは、自然言語のステップを記録して、次からはモデルを呼ばずに再生する仕組みです。キーなしの環境では再生まで進めないので、ここは公式ドキュメントの説明で整理します。

再生されるのは agent.act() の操作だけです。agent.assert、agent.waitFor、agent.extract は毎回モデルを呼びます。確認を自然言語で書くほど、2回目以降もモデルの利用料がかかり続けるわけです。

actの記録が保存されるのは、そのあとの確認が通ったときに限られます。確認に数えられるのは expect(locator).toHaveText() のようなロケータの確認や agent.assert で、expect(value).toBe() のような値の比較は数えられません。確認のないactは保存されず、毎回モデルが動きます。

あなたのテストでは、操作のあとに何で結果を確かめていますか? 自然言語版でactのあとに expect(locator).toContainText() を置いたのは、この条件を満たすためです。

UIが変わると、自然言語のE2Eテストは落ちずに通る

ボタンの名前が変わったとき、Playwrightの手書きはロケータが見つからずに落ちます。うるさい壊れ方ですが、どこが変わったかはすぐ分かります。

e2eは、記録したボタンが見つからず再生が止まると、エージェントが今の画面から続きを引き受けます。ゴールまでたどり着けばテストは合格で、実行の要約に handed off と出るだけです。

「勝手に直してくれるなら楽では?」と思いますよね。ただ、記録が古いままだと引き受けのたびにモデルが呼ばれ、誰かが記録し直すまで費用が出続けます。CIは既定で記録を更新せず、npx e2e init は .e2e/cache/ を .gitignore に入れるので、そのままだとCIは記録なしで始まります。

これを止める設定が --strict-cache(設定ファイルでは cache: { strict: true })です。記録があるのに再生できないステップは、モデルを呼ばずに REPLAY_STALE で失敗し、終了コードは2になります。UIの変更を見逃したくないCIなら、私はこちらを選びます。

Playwright側にも、落ちたテストを直すTest Agentsのhealerがあります。こちらはロケータの修正などをテストコードへのパッチとして出す方式です。直した跡がコードに残るかどうかが、両者の保守の考え方の違いです。

e2eで落ちた理由は読めるか、trace.mdとエラーコード

速いかどうかより、落ちたときに理由が読めるかのほうが私には大事です。e2eは失敗したテストごとに trace.md を出し、キーなしで落ちたテストでも .e2e/results/ の下に出力されていました。

ドキュメントによると、trace.mdにはステップごとに、操作した要素、確認で読んだ値、再生したかどうか、アプリのコンソールエラーや4xx、5xxの応答、前のステップからの画面の変化が並びます。Markdownなので、人が読んでもコーディングエージェントに渡しても扱いやすい形です。

終了コードも分かれていて、1がテストの失敗、2が設定やポリシーの問題、3がエンジン、アプリ、モデル側の失敗です。2や3なら、アプリのバグより先に環境を疑えます。テストが赤くなったとき、あなたのチームは最初にどこを見ていますか?

保守を楽にするなら、自然言語は変わりやすい手順だけに

e2e本体は無料のOSSで、払うのは自分が選んだモデルの利用料だけです。公式ブログは、トークン料金に上乗せはしないと書いています。TesterArmyのクラウド版は別の製品で、Hobbyが月99ドル(約1万5,000円)、Startupが月299ドル(約4万5,000円、いずれも1ドル150円で換算)です。e2eだけを使うなら契約は要りません。

公式の移行ガイドを見ると、fullyParallel、HTMLレポート、デバイスのエミュレーションなど、Playwrightにあってまだ無い機能も並んでいます。ガイド自身も、Playwrightと同じプロジェクトに置いて1ファイルずつ移すやり方を勧めています。

全部を置き換えるより、UIの変更でよく落ちるテストを1本だけ選ぶのが現実的です。操作は agent.act()、確認は expect(locator) で書き、CIには --strict-cache を付けます。あなたのテストの中で、いちばん直す回数が多いのはどの画面でしょうか?

その1本で npx e2e init から始めれば、保守の手間が本当に減るかを自分のアプリで確かめられます。設定の詳細は公式ドキュメントにまとまっています。