🎁

Claude × Maestro MCP で iOS アプリの E2E テストを実装してみた

に公開

こんにちは!株式会社mikanのiOSエンジニア 曽根原と申します。

もう年末だなんて信じられない。と毎年言っている気がします。
ということで株式会社mikanのアドベントカレンダー10日目です!

昨日9日目は我らがmikanの分析大先生、PMのitoryoさんの「mikanの開発ってどんな感じで進むの?」という記事でした!この記事を読んだあなたはmikanの開発フローが丸わかりです!

アドベントカレンダーはこちら

はじめに 🎄

この記事では、Claude と Maestro MCP を組み合わせて iOS アプリの E2E テストを実装してみます!

テストは書くべきとわかっていても、目の前のビジネス要件を優先して後回しにしがち...という方も多いのではないでしょうか。私自身もそうでした。
しかし最近、AIの進化によってテスト作成のハードルがグッと下がってきています。社内でもテスト機運が高まってきたので、これを機に試してみました!

なぜテストを導入するのか、テストを導入する目的 🎯

  • バグの早期発見により、リリース後の損失を最小化するため
  • 変更容易性を保ち、開発スピードを継続的に維持するため
  • 仕様をコードとして保存するため
  • etc...

正しいテストを行うことで、品質の高いサービスを安定して、中長期的にユーザーへ届けることができます。

E2Eテストとは 🤖

E2E(End-to-End)テストは、ユーザーが実際に操作するのと同じ流れでアプリ全体をテストする手法です。iOSで言えば、UIを実際にタップしたり、入力したり、画面遷移したりするところまで自動で行います。

とても便利そうに思えますよね。ただし下記のようなデメリットがあるのでコア機能や重要なビジネスロジック、回帰テストなどリスクやコストが高い場所等に絞るなどバランスが大切そうです。

  • テスト実行時間が長い
  • UI変更のたびに発生するメンテナンスコスト

Maestroとは 🧑‍🎨

Maestroは、E2Eテストフレームワーク(Appium、XCTest、Detox)の課題を解決するために設計されました。Maestro MCPをClaudeに接続すると、AIが直接シミュレーターを操作できます。

https://docs.maestro.dev/getting-started/maestro-mcp

他にも似たようなライブラリはいくつかありますが、メジャーかつメンテナンスされているMaestroを選定しました。「マエストロ(Maestro)」はイタリア語で「師」「名人」「巨匠」という意味を持ち、音楽や芸術における「達人」を表す言葉です。ええやん。

https://github.com/mobile-next/mobile-mcp

実装してみる 🏗️

1. Maestroをインストールする ⬇️

1-1. Java 17+ の確認

# openjdk 17以上が表示されればOK!
java --version

1-2. Maestroのインストール

# Maestro CLI をインストール
curl -Ls "https://get.maestro.mobile.dev" | bash

# パスを通す(zsh の場合)
echo 'export PATH="$PATH:$HOME/.maestro/bin"' >> ~/.zshrc
source ~/.zshrc

# インストール確認
maestro --version

https://docs.maestro.dev/getting-started/installing-maestro

2. Claude DesktopでMCPを設定 🍝

{
  "mcpServers": {
    "maestro": {
      "command": "/Users/※YOUR_USERNAME/.maestro/bin/maestro",
      "args": ["mcp"]
    }
  }
}

https://docs.maestro.dev/getting-started/maestro-mcp

3. サンプルアプリの準備 🥗

import SwiftUI

struct ContentView: View {
    @State private var email = ""
    @State private var password = ""
    @State private var isLoggedIn = false
    @State private var errorMessage = ""
    
    var body: some View {
        if isLoggedIn {
            HomeView()
        } else {
            loginView
        }
    }
    
    var loginView: some View {
        VStack(spacing: 20) {
            Text("ログイン")
                .font(.largeTitle)
                .fontWeight(.bold)
                .accessibilityIdentifier("login_title")
            
            TextField("メールアドレス", text: $email)
                .textFieldStyle(.roundedBorder)
                .textContentType(.emailAddress)
                .autocapitalization(.none)
                .accessibilityIdentifier("email_field")
            
            SecureField("パスワード", text: $password)
                .textFieldStyle(.roundedBorder)
                .accessibilityIdentifier("password_field")
            
            if !errorMessage.isEmpty {
                Text(errorMessage)
                    .foregroundColor(.red)
                    .font(.caption)
                    .accessibilityIdentifier("error_message")
            }
            
            Button(action: login) {
                Text("ログイン")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .accessibilityIdentifier("login_button")
        }
        .padding(32)
    }
    
    func login() {
        // テスト用のシンプルな認証
        if email == "test@example.com" && password == "password123" {
            isLoggedIn = true
            errorMessage = ""
        } else {
            errorMessage = "メールアドレスまたはパスワードが正しくありません"
        }
    }
}

struct HomeView: View {
    var body: some View {
        VStack(spacing: 16) {
            Image(systemName: "checkmark.circle.fill")
                .font(.system(size: 60))
                .foregroundColor(.green)
            
            Text("ようこそ!")
                .font(.largeTitle)
                .fontWeight(.bold)
                .accessibilityIdentifier("welcome_message")
            
            Text("ログインに成功しました")
                .foregroundColor(.secondary)
        }
    }
}


サンプルアプリのView

4. AIにE2Eテストを書かせる 🥏

Claudeにて下記のように指示します。アクションを羅列するだけなので、アプリの操作さえできればテストを作成できます。

現在 iOSシミュレーターで起動しているアプリのログイン機能について、
Maestroを使ったE2Eテストを作成してください。

## テストシナリオ
### 正常系:ログイン成功
1. アプリを起動(状態クリア)
2. メールアドレス「test@example.com」を入力
3. パスワード「password123」を入力
4. ログインボタンをタップ
5. 「ようこそ!」が表示されることを確認

### 異常系:ログイン失敗
1. アプリを起動(状態クリア)
2. 誤ったメールアドレスを入力
3. 誤ったパスワードを入力
4. ログインボタンをタップ
5. エラーメッセージが表示されることを確認

まず現在の画面を確認してから、テストを生成してください。

下記テスト(YAML)がプロジェクトのディレクトリ配下、.maestroに格納されました!
YAMLファイルの中身はこのようになっています。実際のコマンドも分かりやすいですね!

# ログイン機能 - 正常系テスト(ログイン成功)

- launchApp:
    clearState: true

- assertVisible: "ログイン"

- tapOn:
    id: "email_field"

- inputText: "test@example.com"

- tapOn:
    id: "password_field"

- inputText: "password123"

- tapOn:
    id: "login_button"

- assertVisible:
    id: "welcome_message"

- assertVisible: "ようこそ!"

詳細は下記をご参考ください。
https://docs.maestro.dev/platform-support/ios-swiftui
https://docs.maestro.dev/api-reference/commands

5. テストの実行 🛫

ついにテストを実行します!

  # 正常系テストを実行
  maestro test .maestro/login_success.yaml

  # 異常系テストを実行
  maestro test .maestro/login_failure.yaml

  # 両方のテストを実行
  maestro test .maestro/


シミュレータ上でテストが行われる様子

おー動きました!これはいいですね!
CLI上にも結果が表示されています!

テストが正常に完了しました!

  実行結果
  デバイス: iPhone 17 Pro - iOS 26.0
  ✅ 全ステップ成功:
  - アプリ起動(状態クリア)
  - 「ログイン」表示確認
  - メールアドレス入力: test@example.com
  - パスワード入力: password123
  - ログインボタンタップ
  - 「ようこそ!」メッセージ表示確認

  ログイン成功シナリオが正しく動作しています。

Maestro Studioでのデバッグ 🥃

MaestroStudio を使用すると、保守性や信頼性を損なわずに誰でもテストを作成できるようになります!シミュレータ上で実際にアプリを操作すると、その操作がyamlとして書き出されます。

maestro studio

https://maestro.dev/?utm_source=cli&utm_campaign=download_studio#maestro-studio


ブラウザ上のMaestro Studio

テストのメンテナンスコスト削減 💠

従来の障壁 Claude + Maestro MCP での解決
テスト作成の工数 AIが画面を見て自動生成し工数削減
メンテナンスコスト AIに修正を依頼し工数削減
学習コスト YAMLで直感的に記述できる
フレークテスト(不安定さ) 要素が見つかるまで自動で待機・リトライする
デバッグの困難さ Studioで視覚的に確認できる

LLMにうまいことまとめてもらいましたが、テストやMaestroに限らずAIを使うことで様々なコストを下げられますね!

まとめ 🎅

駆け足でしたがE2Eテストを気軽に実装できました!
自然言語でよしなにテストを作ったり、画面からテストケースを洗い出せたりとAIによる恩恵は大きいですね!CI/CDに組み込む等は追って検証してみたいと思います。

またmikanではSwiftUI + TCAを採用しているのでSwiftTesting × TCA TestStoreでのテストも検証しつつ、目的を見失わないようにしながらテストを深掘りしていきたい所存です。ありがとうございました!

よいお年を!

mikan blog

Discussion