e2e

tester-army/e2e — agentic end-to-end testing for web and mobile apps

1 — 概要

e2e は Web アプリとモバイルアプリ向けの E2E テストフレームワーク。テストの中で自然言語のゴールを agent.act() に渡すとエージェントがアプリを操作し、その結果をロケータとアサーションで厳密に確かめる。どこまでエージェントに任せるかはテストごとに選べる。ロケータだけの決定的なテスト、ゴールだけのテスト、両者を混ぜたテストのどれも書ける。

WebChromium / Firefox / WebKit
MobileiOS / Android
0 callsキャッシュ再生時のモデル呼び出し
0.17e2e パッケージ(1.0 前)

後続のチェックで検証された agent.act の操作は記録され、次回の実行ではモデルを呼ばずに再生される。アプリが変わって再生が合わなくなった時点でエージェントが引き継ぐ。エージェントステップを含まないテストはモデル不要。モデルは自前のサブスクリプション、API キー、ローカルモデルのいずれでもよい。

開発段階: README によると 1.0 に向けて開発中で、マイナーリリース間で API と設定が変わることがある。ライセンスは Apache-2.0。CLI は匿名の利用データを送る(テスト内容・アプリの内容・認証情報は含まない)。npx e2e telemetry disable または E2E_TELEMETRY_DISABLED=1 で止められる。
2 — テストの書き方

テストファイルは既定で tests/**/*.e2e.ts。フィクスチャとして app・agent・screen(Web では browser も)を受け取る。

// tests/checkout.e2e.ts
import { test, expect } from 'e2e';

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(goal) — ゴールを 1 つ渡す。操作の順序はモデルが決め、達成すれば返り、失敗すれば例外を投げる。
  • agent.assert / agent.waitFor / agent.extract — 画面の状態をモデルが判定する。こちらは毎回モデルを呼ぶ。
  • テストデータは params で渡す(例: 'invite {email} as an editor')。毎回変わる値は unique() で包むとキャッシュが効く。

厳密に書くステップ

  • screen.getByRole / getByText / getByTestId などのセマンティックロケータで操作・取得する。
  • expect(...) はアサーションを再試行する。アクションは要素の準備完了を待つ。
  • 2 つの要素に一致するロケータは LOCATOR_AMBIGUOUS で失敗するので絞り込む。sleep は入れない。
  • 1 回の act にゴールは 1 つ。ゴール内の手順はモデル、ゴールの順番はテスト作者が決める。
  • 画面に表示されている文言をそのまま使う。複数のゴールで共有する語彙はエージェントの context に書く。
  • 各ゴールの直後に expect か agent.assert で結果を確かめる。検証済みの act だけがキャッシュされる。
  • モデルが生成した文の完全一致ではなく意味で判定する(例: 文全体ではなく toContain('Pro'))。
3 — 設定とエンジン

targets が必須。UI のターゲットはエンジンとテスト対象アプリを並べて宣言する。アプリの起動コマンドも設定に書けるので、ランナーが開発サーバーを立ち上げてからテストを流す。各テストはターゲットごとに 1 回ずつ実行される。

import type { E2EConfig } from 'e2e';
import { web } from '@e2e-dev/web';
import { gateway } from 'ai';

export default {
  targets: [
    {
      engine: web(),
      app: {
        url: 'http://127.0.0.1:3000',
        command: { executable: 'pnpm', args: ['dev'], log: '.e2e/logs/app.log' },
      },
    },
  ],
  // agent.* ステップに使うモデル(AI SDK のインスタンス)
  agents: {
    default: {
      model: gateway('openai/gpt-6-luna-fast'),
      system: 'You are a thorough QA agent. Verify every outcome on screen.',
    },
  },
} satisfies E2EConfig;

モバイルは mobile({ platform: 'ios' }) のようにエンジンを差し替え、app に bundleId を指定する。テストは Web と同じ agent / screen / expect API で書ける。セットアップは npx e2e init がエンジン(Web かモバイル)とモデルプロバイダを尋ね、設定とサンプルテストを生成する。

パッケージ役割
e2eSDK、ランナー、CLI。全ドキュメントを同梱しており、コーディングエージェントはオフラインで node_modules/e2e/docs を読める。
@e2e-dev/webブラウザエンジン。Playwright 経由で Chromium / Firefox / WebKit を動かす。
@e2e-dev/mobileiOS / Android エンジン。agent-device 経由でシミュレータ、エミュレータ、接続済み実機を動かす。
@e2e-dev/githubGitHub Actions から、1 回の実行につき 1 件のプルリクエストコメントで結果を投稿するレポーター。
@e2e-dev/kernelKernel がホストする Chromium で Web ターゲットを動かす。
@e2e-dev/easExpo の EAS Simulators(ホスト型 iOS シミュレータ / Android エミュレータ)でモバイルターゲットを動かす。
@e2e-dev/decisionLLM の代わりに決定モデルで act / assert を実行する。操作と要素は選択肢から選ばせ、入力値だけ小さなテキストモデルが書く。

エンジンは自作もできる(公式ドキュメントの「Writing an engine」)。完成済みのセットアップ例は examples/ に Vite、Next.js、Expo、SwiftUI の 4 本がある。

4 — エージェントステップ

エージェントステップは agent.act / assert / waitFor / extract の 1 呼び出し。ランナーがマスキング、アクションの許可、予算、シークレット、レポートを受け持ち、エグゼキュータ(既定は組み込みエージェント)がどのアクションを要求するか決めて判定を返す。

モデルに渡るもの

  • 指示と params、act では同じテスト内の完了済みステップ
  • 画面のテキストスナップショット(ロール、名前、テキスト、状態)。生 HTML、Cookie、ヘッダー、環境変数は含まない
  • 操作後は画面の差分。大半が変わったときは全体を再送
  • スクリーンショットは必要時か vision 指定時のみ。シークレット入力後は送らない

モデルが使えるもの

  • エンジンが対応するツール: tap、type、press、select、scroll、navigate など
  • 認証情報を宣言したステップでは type_secret
  • テキストで表せないコントロール向けのピクセルツール(座標でタップ・入力)
  • defineTool で定義したプロジェクトツール

ループは同じ呼び出しの繰り返しを検出する。3 回続けてアクションが失敗するとアプローチの変更を求め、5 回で判定を求める。ターゲット指定のアクションのタイムアウトは最大 15 秒。

判定意味例
passed成功の根拠が画面にある—
failed条件が偽、または確認できない根拠不足は ASSERTION_INCONCLUSIVE
blocked前提・環境・実行上限のせいでプロダクトとしての判定が出せないAUTH_CREDENTIAL_INVALID(終了コード 2)、ENVIRONMENT_UNAVAILABLE(3)、STEP_BUDGET_EXHAUSTED / STEP_TIMEOUT(1)

コストを決める要因

  • assert と extract は 1 回の判定(応答が壊れていれば修復呼び出しが 1 回追加)。
  • waitFor は画面が変わらない間はモデルを呼ばない。
  • スクリーンショットは載せた呼び出しごとに入力トークンを増やす。
  • 合格した act はリプレイキャッシュでモデル呼び出しなしに再実行される。
5 — リプレイキャッシュ

後続のチェックで結果が検証された agent.act() の操作を記録し、次回はモデルを呼ばずに再生する。assert / waitFor / extract は常にライブで実行される。

STEP 1
開始画面を照合
同じルートか確認する。リトライは常にライブ実行。
STEP 2
操作を再現
記録したコントロールをロール・名前・テスト ID・周辺文脈で探し、同じ操作を繰り返す。曖昧なら止める。
STEP 3
結果を照合
最終ルートと、元のステップで現れた・消えたコントロールとその状態を確かめる。日時や ID など毎回変わる文字列は使わない。
STEP 4
完了か引き継ぎ
一致すればモデル呼び出しなしで完了。ずれたら、何を実行しどこで止まったかを添えてエージェントへ渡す。
実行サマリstep.cache.mode意味
replayedself-finalized記録の再生だけで完了した
handed offagent-concluded再生を始めたが途中でエージェントが引き継いだ
missedmissed最初からエージェントが実行した

エントリはテスト、ターゲット、指示、params、エージェントの組に属する。次の変更はミスになる。

  • テスト名・ターゲット名の変更、指示や通常の param の変更
  • 別のエージェントでの実行、エージェントの context やテストの agentContext の変更
  • エンジンのメジャー / マイナーバージョンの更新

モデルの変更ではミスにならない。タイムスタンプや毎回新しいメールアドレスは unique() で包むと、再生時に現在の値へ差し替えられる。キャッシュを疑うときは npx e2e run --no-cache で一度切り分ける。

CI での扱い: キャッシュモードを設定していない場合、CI では読み取り専用が既定。e2e init は .e2e/cache/ を Git の無視対象にする。記録をコミットする場合は、テストコードと同じく中の操作と入力値をレビューする(エントリは署名されていない)。
6 — explore と bug bash

テストファイルを書かずにゴールだけを渡すと、エージェントが自分でステップを計画してアプリを操作し、気づいた点(期待、実際、再現手順、可能ならスクリーンショット)を .e2e/report.json に記録する。agent.act() と同じモデル、ツール、アクション規則を使う。

npx e2e explore 'Explore the checkout flow like a first-time buyer and report anything off'
予算既定範囲
探索ステップ(--max-steps)81〜12
実時間(--timeout)10 分3〜15 分
ステップあたりの操作 / モデル呼び出し各 401〜100
結果状態終了
issue の指摘なしpassed0
issue が 1 件以上failed1
全ステップが blockedblocked1〜3
設定・エンジン・プロバイダのエラーerror2 / 3

issue は機能上の不具合で実行を失敗させ、warning は見た目の問題で失敗させない。失敗または blocked のステップが新しい指摘なしに 3 回続くと探索を終える。

コーディングエージェントがスキルの bug-bash トピックに沿って実行する。ルート(ブランチなら差分も)を読み、1 文のチャーターを 5〜10 本書き、初回ユーザー・数値と文言・境界入力などの観点をペルソナとして e2e explore を 4 本ずつ並列に走らせる。環境やシードデータで説明できる指摘を除いたあと、残りの指摘ごとに期待動作をアサートする再現テストを書く。そのテストが ASSERTION_FAILED で落ちたものだけをバグとして報告する。

7 — コーディングエージェント連携

リポジトリには skills/e2e/SKILL.md があり、トピック別の参照ファイルを持つ。インストール済みの CLI は同じ内容を npx e2e guide <topic> で表示する。

トピック読む場面
setupプロジェクトへの導入、e2e.config.ts の作成、アプリ起動設定、モバイルターゲット
writing-testsフィクスチャ、ロケータ、アクション、マッチャ、サインインセッション
agentagent.* ステップ、モデル選び、コストと予算、リプレイキャッシュ
runningCLI フラグ、レポーター、.e2e/report.json、終了コード、CI
explore / bug-bashテストなしの探索、並列探索と再現テストによる検証
debuggingエラーコードと対処、--headed、--debug、--ai-trace
mcpMCP 経由でライブのアプリを操作し、調べてからテストを書く流れ

プロジェクトのライブアプリを stdio の MCP サーバーとしてコーディングエージェントに公開する。テストを書く前に画面を見て、ロケータが一意に当たるかを確かめるための仕組み。テストの実行と失敗の読解は CLI 側で行う。ai パッケージがなくても決定的なツールは動く。

claude mcp add e2e -- npx e2e mcp   # Claude Code
ツール動作
open_session設定を読み、アプリの起動コマンドを実行し、エンジンを起動して URL を開く。セッション ID、カタログ、最初の観測結果を返す
toolsセッションで使えるカタログ(observe、tap、type、locate、screenshot など)を一覧する
callカタログのツールを 1 つ実行する。引数はスキーマで検査され、誤りは INVALID_ARGUMENT
close_session録画を保存し、エンジンを破棄し、他のセッションが使っていなければアプリのプロセスを止める

カタログの locate は role / text / label / placeholder / testId のいずれか 1 つでロケータを試し、一致数と、テストに書くべき screen.* 呼び出しを返す。同時セッション数は既定 4(--max-sessions で 1〜16)。

8 — CLI と終了コード
npx e2e init [directory]          # エンジンとモデルを選んで設定とサンプルを生成
npx e2e login [provider]          # モデルプロバイダのサブスクリプションにサインイン
npx e2e models [provider]
npx e2e guide [topic]             # スキルのトピックを表示
npx e2e mcp [options]
npx e2e run [files...] [options]  # --headed / --debug / --ai-trace / --no-cache など
npx e2e explore [goal] [options]
npx e2e list [files...] [options]
npx e2e cache ls|clear|stats
npx e2e feedback -m <text> [options]
npx e2e telemetry [status|enable|disable]

CLI は Node.js 24.8 以降(22 系なら 22.22.3 以降)で動く。npm、pnpm、Yarn、Bun のどれでもインストールできるが、実行ランタイムは Node.js(bunx e2e も Node.js 上で動く)。

終了コード意味
0選択したテストがすべて合格、flaky、または明示的にスキップ
1テストまたはセットアップの失敗、テストのタイムアウト
2CLI、設定、収集、依存関係、認証情報、モデル設定、ポリシーのエラー
3エンジン、アプリプロセス、モデルプロバイダ、成果物、後片付けのインフラ障害
4ランナー内部の不変条件違反や未処理エラー
130ユーザーまたは CI のシグナルで中断

複数の結果が混ざった実行では 130 > 4 > 3 > 2 > 1 > 0 の優先順で終了コードが決まる。個々の結果は .e2e/report.json に残る。.e2e/(report.json、artifacts/、cache/、logs/)は出力専用で、読むが編集しない。

9 — セキュリティ境界

テストコードにシークレットを書かない。アカウントは設定の credentials、その他の値は secrets に宣言し、credentials.user(name).password や secrets.get(name) で不透明な Secret を受け取る。Secret を渡せるのは fill() と agent.act の params だけ。

  • モデルが受け取るのはシークレットの名前と用途だけ。ランナーがモデルの選んだフィールドを検査してから値を入力する。
  • 画面上に現れた既知のシークレット値は <secret:name> に、セキュアフィールドは value=<secure> に置き換えられる。
  • シークレットを入力したあとは、その試行の間スクリーンショットをモデルに送らない。

アプリの内容がツール、認証情報、予算を与えることはできない。ランナーはモデルのテキストをコード、セレクタ、シェルコマンド、設定として評価しない。

拒否される操作理由
http: / https: 以外への移動(about:blank を除く)file:、data:、javascript: はローカルやインラインのコンテンツを読み込むため
セキュアフィールドの値・テキスト・属性の読み取りシークレットではなく結果をアサートする
宣言されていない名前での type_secretステップの params にあるシークレットだけ入力できる
パスワード以外のフィールドへのパスワード入力用途が一致する必要がある
シークレット入力後の app.screenshot()セキュアフィールド以外にシークレットが表示されている可能性がある
キャッシュの信頼: エントリは使用前に検証され、無効ならミス扱い。1 MiB を超えるファイルは拒否される。あるエージェントで記録したエントリは別のエージェントでは再生されず、アプリのオリジンで記録したものは別オリジンのページでは再生されない。