SaaSのE2Eテストを社内AIエージェントで底上げした話

Jiho Ryuu

Jiho Ryuu

Archaive事業部 プロダクトCTO

こんにちは、ArchAIve事業部でCTOを務めている柳です。

この記事は、E2EテストやリリースまえのQAの改善に取り組んでいるエンジニアに向けて書いています。 私たちSTARUPが開発する製造業DXプラットフォームArchAIveでは、人によるQA体制に加えて、社内で組んだAIエージェントがリリース前の検証環境でE2Eテストを毎日実行しています。

ポイントは、「AIにテストを書かせる」のではなく「AIにテストそのものを実行させる」ことです。 E2Eテストをスクリプトとして自動化するのではなく、人間のQA担当者が行うのと同じ確認作業をエージェントにも毎日実行させて、人の確認にダブルチェックの層を重ねる構成です。 デプロイ前の確認はこれまでも人手で毎日行っていますが、70以上ある画面をすべて網羅しようとすると、どうしても抜け漏れが出る可能性は残ります。 エージェントを加えてからは、この網羅的な確認を毎朝機械的に回せるようになり、ユニットテストと結合テストでは原理的に検出できない環境起因の不具合を、より確実に顧客へ届く前の検証環境で捕まえられるようになりました。

この記事では、テスト戦略上の位置づけ、エージェントの構成と検出ロジック、誤検知への対策、運用して分かったデメリットまでを書きます。

そもそもソフトウェアのテストは何を保証しているのか

テスト構成を考える出発点として、テストピラミッドという考え方があります(Mike Cohnが提唱し、Martin Fowlerの解説で広まりました)。 テストを次の3層に分け、下層ほど数を多く速く安く回し、上層ほど数を絞ります。

テストピラミッドの図。下から順にユニットテスト、インテグレーションテスト、UIテストの3層で構成される

  • ユニットテスト:関数やクラス単位でロジックを検証する。実行が速く安価なので、もっとも多く書く
  • インテグレーションテスト:モジュール間の結合や、DBと外部APIとのやり取りを検証する
  • UIテスト(E2E):実際の画面操作でシステム全体を通しで検証する。遅く高価なので数を絞る

逆にE2Eへ偏った逆三角形の構成は「アイスクリームコーン」と呼ばれるアンチパターンで、遅くて壊れやすいテストスイートになります。

ArchAIveでもユニットテストと結合テストはCIで毎コミット実行しており、バックエンドはOnion Architectureを採用して、ドメインロジックをインフラから切り離したテストしやすい構造にしています。

それでも、この2層が保証するのは「コードが仕様どおりに動くこと」までです。 本番相当の環境で起きる不具合の多くは、環境変数の設定漏れやインフラ構成の差分、デプロイのタイミングずれ、外部サービスの応答やブラウザ環境の差といったコードの外側に原因があり、モックとテストダブルの上に成り立つ下層のテストでは検出できません。

インフラ構成の差分なら、IaC(Infrastructure as Code)のテストで防げるのではないかと思うかもしれませんし、実際にAWS CDKのアサーションテストやスナップショットテストを書けば、リソースの定義漏れは検出できます。 ただ、IaCのテストが保証するのも「定義が正しいこと」つまり合成されたテンプレートの検証までで、適用漏れや手動変更によるドリフトがあれば定義と実環境はずれます。 また、デプロイのタイミングずれや外部サービスの実際の応答、ブラウザ環境の差は、そもそもIaCの守備範囲の外です。 コードと定義がどれだけテストされていても、「デプロイされた環境が実際に動くこと」は、その環境を実際に操作しないと確かめられません。

実例を挙げると、リリース前の検証環境で図面ファイルをアップロードすると500エラーになる不具合があり、原因はアップロード後の解析ジョブを積む非同期キューがその環境にだけ作られていなかったことでした。 テストコードではこのキューをモックしているためユニットテストと結合テストはすべて通ってしまい、デプロイされた環境で実際にファイルをアップロードして初めて検出できる不具合でした。 このバグは後述するエージェントが検証環境で検出して開発チームへ通知し、顧客へ届く前に修正できました。

私たちのAIエージェントが担当しているのは、このピラミッドの最上層、実環境での画面操作です。

E2E自動化の弱点と、AIで代替できること

E2Eテストの自動化はこれまで、SeleniumやPlaywrightを使って「このセレクタの要素をクリックし、この値を入力し、この文言の表示を検証する」という一手ずつをスクリプトに固定していく方式が定石でした。

この方式の弱点はよく知られており、スクリプトがセレクタ(画面要素の特定方法)に依存しているため、UIの構造や文言が少し変わるだけでテストが壊れます。 画面の数だけシナリオを書いてUIの変更のたびに修正し続ける保守コストが、E2E自動化の定着を妨げてきました。

AIエージェントはこの依存を持たず、実行時にDOMやアクセシビリティツリーを読んで「注文を確定するボタン」を毎回自分で特定します。 操作をセレクタで固定するのではなく「図面を登録する」という目的で指示するため、ボタンの位置や文言が変わっても、目的が達成できる限り追従します。

もう1つ、探索的テスト(決められたシナリオの外側を触って未知の不具合を探す作業)にも寄与します。 スクリプトは書かれた手順しか実行しませんが、エージェントは画面を認識しながら操作を組み立てるため、シナリオにない画面遷移や入力の組み合わせを試せます。 ただし、エージェントの探索は網を広く張る力であって、正常系・異常系を業務知識に基づいて確実に押さえる判断は人のほうが的確です。 ArchAIveでは人が正常系と異常系を手動確認したうえで、エージェントが人の手の届きにくい組み合わせの探索で補完する構成にしています。

ただし、すべてをAIに寄せたわけではありません。 ArchAIveは70以上の画面と300近いAPIエンドポイントを持つtoBの業務システムで、図面や書類の登録といった基幹フローは顧客の業務を直接支えています。 この規模の回帰確認は人手でも毎日行っていますが、人手だけでは確認の抜け漏れをゼロにしきれず、かといって品質の判断までAIに委ねるわけにもいきません。 人とAIの分担は次のように分けています。

領域担当理由
単体テストと結合テストの実装人(開発者)ロジックの意図を最もよく知るのは実装者
テスト戦略と重要度の判断何が事業上クリティカルかは人が決める
修正方針の決定とマージ本番コードへの責任は人が持つ
網羅的な画面操作AI膨大で反復的。機械のほうが確実にこなせる領域
画面横断の探索と切り分けAI大量の状態を粘り強く試すのが得意
バグの一次起票とレポート作成AI定型的な記述はドラフト生成が向く

分担の基準は、判断は人が握り、網羅と反復はAIに委ねるという点に尽きます。 新機能の受け入れ確認とリリース可否の判断はこれまでどおり人が担い、AIは毎日の回帰確認でそれを支えます。

bugcheck-kitの仕組み

この仕組みを社内ではbugcheck-kitと呼んでおり、構成要素は次の4つです。

  • Claude Code:エージェント本体。QAの手順を「スキル」(自然言語で書いた手順書)として持つ
  • Playwright:ブラウザ操作。クリック、入力、画面遷移、スクリーンショットの取得
  • スケジューラ:毎朝定時にエージェントを無人で起動する
  • SlackとGitHub:検出結果の通知と、issueの起票

PlaywrightとSlack、GitHubはMCP(Model Context Protocol)経由でClaude Codeに接続しており、画面操作から起票までが無人で完結します。

テストの手順は、人間のQA担当者に渡す手順書と同じ形式の自然言語で記述しており、E2Eをスクリプトではなく手順書として管理するのがこの構成の中心的な選択です。

毎朝の実行フローは次のとおりです。

定時起動(スケジューラ)
  │
  ├─ 1. kit 自身を更新(バグ台帳・テスト計画を最新化)
  ├─ 2. 検証対象のコードを最新に同期(仕様と実装を読むため)
  ├─ 3. ブラウザ起動 → セッションを確立 → 鮮度チェック
  ├─ 4. 検出(網羅スイープ → 深掘り → 探索クロール)
  ├─ 5. バグ候補をコードまで裏取り(誤検知を落とす)
  ├─ 6. 既知バグ台帳と照合し、新規だけ起票+通知
  └─ 7. 台帳・計画を更新して保存

プロダクト固有の値は、すべて設定ファイルに分離しています。

# 設定のイメージ(実際の項目を簡略化しています)
target_env: staging          # 対象は検証環境のみ。本番には接続しない
failure_conditions:          # 「失敗」とみなす条件
  - console_error
  - http_5xx
  - unexpected_dialog
ignore_patterns:             # 失敗条件から除外する既知のノイズ
  - "外部アクセス解析スクリプトが出すコンソールエラー"
deep_dive_priority:          # 深掘り対象のローテーション順
  - recently_changed         # 直近でコードが変わった画面
  - critical                 # 事業上クリティカルな画面
  - least_visited            # 長く確認していない画面
explore_budget:              # 探索クロールの打ち切り予算(時間・画面数)
  ...

エージェント本体は汎用のまま、この設定を差し替えれば別プロダクトにも展開できる構成です。

3つの検出モード

検出は、粒度の異なる3つのモードを毎日組み合わせています。

  1. 網羅スイープ:全画面を浅く一巡する安全網。各画面を開き、コンソールエラー、5xx応答、想定外のダイアログを失敗条件としてチェックする
  2. 深掘り:対象画面のボタン、フォーム、タブを1つずつ操作する。全画面を毎日深掘りするのはコストが見合わないため、「直近でコードが変わった画面 → クリティカルな画面 → 長く確認していない画面」の優先順位でローテーションする
  3. 探索クロール:時間と画面数の予算内で、エージェントが自律的に操作を組み立てて未知の不具合を探す

深掘りの優先順位のうち「直近でコードが変わった画面」は、変更された箇所が壊れやすいという経験則に基づいて、リポジトリの差分とリリース状況から自動で決めています。

バグ台帳と回帰チェック

検出したバグ候補はまず、症状、原因、再現手順、修正状態を1バグ1ファイルのMarkdownで記録した既知バグの台帳(known-bugs)と照合します。

# BUG-012 図面アップロードが500エラーになる

- 重大度: high
- 状態: 修正済み
- 症状: 図面を1枚アップロードすると500が返る
- 原因: 解析キューが未作成の環境で例外(インフラ要因)
- 再現: ログイン →「図面アップロード」→ ファイルを選択

台帳に載っているバグは通知せず新規のものだけをSlackに流してGitHubのissueを起票し、通知は環境と検出モードごとの結果を並べたサマリとして毎朝届きます。

毎朝Slackに届くバグチェックのサマリ通知

この台帳には重複排除のほかにもう1つ、記録した再現手順が翌日以降の回帰チェックの入力になるという役割があります。 一度見つけたバグは再現手順を台帳に固定して以後は決まった手順で毎日踏み直すので、一度壊れた場所はまた壊れやすいという経験則のとおり、運用を続けるほど回帰チェックの対象が検出実績ベースで増えていきます。

検出から修正まで

起票されたバグのうちコードで修正できるものは、別のジョブがブランチを切って修正し、CIの通過を確認したうえでドラフトのPull Requestを作ります。

エージェントに与えている権限は、次の範囲までです。

  • 検証環境の画面操作(本番環境と顧客データには接続しない)
  • ブランチ上でのコード修正と、ドラフトPRの作成(自動マージはしない)

レビューとマージは必ず人が行います。

AIに任せるデメリットを補う4つの工夫

ここまでが毎朝動いている仕組みですが、AIに任せる構成には弱点もあります。 導入を検討する人のために、弱点と、それを補うために入れている工夫をセットで4つ整理します。

誤検知

運用して分かったのは、いちばん問題になるのは見逃しではなく誤検知だということで、誤った警告を出し続けるテストは信頼を失い、通知が読まれなくなった時点で仕組み全体が機能しなくなります。

実際に起きた例を挙げると、無人実行がブラウザの認証状態(PlaywrightのstorageState)を使い回していたため、エージェントが期限切れのセッションのまま画面を操作し、「編集権限が必要な操作はすべて権限エラーになる」という重大度の高いissueを起票しました。 製品側は正常に動作しておりこの権限エラーはテスト側のセッション切れによる誤検知でしたが、期限切れのセッション状態が実行をまたいで引き継がれるため、修正するまで同じ誤検知が実行のたびに再発しました。

この件を踏まえ、誤検知対策は現在4層で構成しています。

  1. ノイズの除外リスト:外部スクリプトが出し続けるコンソールエラーなど、既知のノイズを設定で失敗条件から除外する
  2. セッション鮮度ゲート:実行の冒頭で認証状態の有効性を確認し、無効ならstorageStateを破棄して再ログインする
  3. デプロイ状態の実画面確認:コード差分から「この修正は未反映のはず」と推測させず、実画面の操作結果で判定する
  4. 起票前のコード裏取り:バグ候補ごとに該当コードを読み、原因箇所を特定できたものだけを起票する。現象だけで原因を説明できない候補はここで落ちる

もう1つ、根本の設計として、正常か異常かの判断基準をエージェントの推測に置かない方針を徹底しています。 テストシナリオは「図面を登録し、BOMを作成し、見積もりを出す」といった実際のユーザーストーリーから組み立て、期待挙動は仕様に紐づけます。 エージェントに「正しい挙動」を想像させると、そこがハルシネーションの入り口になるためです。

それでも、画面が増えればノイズも増えるため、チューニングを一度で終えることはできません。 除外リストと失敗条件は設定ファイルに分離してあるので、新しいノイズが見つかっても設定の更新だけで対処でき、チューニングを日常の運用作業として続けられます。

APIコスト

3つの検出モードを毎日回すと、LLMのAPI利用料は無視できない額になります。 そこで、探索クロールは時間と画面数の予算で打ち切り、モデルはタスクの複雑さに応じて使い分けることで、1回あたりのコストに上限をかけています。

保守

E2Eスクリプトの保守は消えましたが、代わりにスキル(手順書)と設定ファイルの保守が残ります。 そこで、保守対象を手順書と設定に寄せることで、修正が必要になる契機を絞りました。 スクリプトはセレクタ単位でUIの小さな変更のたびに壊れますが、手順書を直すのは業務フローや失敗条件が変わったときだけで、保守の粒度がセレクタ単位から方針単位に粗くなったと言えます。

再現性

スクリプトと違い、エージェントの実行は非決定的で同じ状態からでも操作経路が変わりえるため、探索の幅としては利点ですが、回帰チェックとしては弱点です。 そこで、既知バグは台帳の再現手順に固定して決まった手順で毎日踏み直す構成にしており、仕組みの節で述べた台帳の回帰チェックはこの弱点への対処でもあります。

まとめ

ユニットテストと結合テストが保証するのは「コードが仕様どおりに動くこと」までで、環境構成、デプロイ、外部サービスに起因する不具合は、実環境を実際に操作しないと検出できません。 ArchAIveでは、その層をClaude CodeとPlaywrightで構成したAIエージェントが毎朝リリース前の検証環境で確認しており、E2Eをスクリプトではなく自然言語の手順書として管理することで、セレクタ起因の保守コストをなくしました。

運用のうえで検出精度を決めるのは誤検知への対策で、ノイズの除外やセッション鮮度の確認、起票前のコード裏取りを重ねて通知の信頼を守っています。

そして、網羅と反復はAIに任せてテスト戦略や重要度、リリース可否、マージの判断は人が握るという分担で、人のQAにAIの層を1枚重ねています。 網羅的な確認をAIに任せられるぶん、人はテスト戦略や新機能の品質づくりに時間を使えるようになりました。

APIコストはかかりますが、同じ網羅性と頻度を人手で維持する工数と比較すれば、私たちの規模では割に合っています。 同じ課題に取り組むチームの参考になればうれしいです。

参考文献

Jiho Ryuu

Jiho Ryuu

Archaive事業部 プロダクトCTO

// we are hiring

STARUPで一緒に働きませんか?

こうした技術的な挑戦を一緒に楽しめる仲間を募集しています。

採用情報を見る →