mobile-mcpを使ってiOSアプリのUIテストを作成するスラッシュコマンドを作成した
mobile-mcpを使ってiOSアプリのUIテストを作成するスラッシュコマンドを作成したので記事にしてみました。
モチベーション
以前から、手動での動作確認が煩わしく、UIテストを作成したいと思っていました。
UIテストは優先度が低いかつ作成およびメンテナンスコストが高いので、UIテストの作成に割ける時間は少ないです。
AI駆動開発をしていると膨大なソースコードを生み出すことができるようになった反面、動作確認がボトルネックになってしまっています。
UIテストをシュッと作成できるのは価値がありそうです。
mobile-mcpとは
mobile-mcp を使うとLLMが直接モバイルアプリを操作可能になり、構造化して分析してくれます。
基本はアクセシビリティの機能を用いて操作しますが、十分に対応されてないアプリでもソースコードとスクリーンショットから画面を把握して操作します。
この機能を生かして、画面を分析してもらいUIテストコードを書いてもらいました。
直接、プロンプトを書いてテストさせることも可能ですが、テストは何度実行しても同じ結果が返ってくることが期待されるので、UIテストに書き起こすことで動作検証を安定化させます。
(将来的にはUIテストを書かなくても高精度でテストできる日はやってくるでしょう)
以下の流れでUIテスト作成させます。
Claude Codeのカスタムスラッシュコマンドにすることで、コマンド一発で実行できるようにします。
- 画面構成ガイドを作成する
- 画面構成ガイドをもとにUIテストコードを作成する
画面構成ガイドを作成する
以下のようなカスタムスラッシュコマンドを作成しました。
---
description: Generate or update SCREEN_GUIDE.md for UI testing
allowed-tools: mcp__mobile-mcp
---
SCREEN_GUIDE.mdを作成または更新してください。
このドキュメントはUIテスト作成時のコンテキストとして使用されます。
mobile-mcp(必須)を使ってアプリを起動し
実際の画面を確認しながらソースコードと照合してください。
## mobile-mcp設定
- **デバイス**: 起動中のシミュレータを優先使用。なければiPhone 16を起動
- WebDriverAgentが未セットアップの場合はユーザーに確認
## ドキュメント構造
以下のセクションを順に記載(**各項目の記述は最大3つまで**):
### 1. アプリ概要
1文で簡潔に記述
### 2. 実装クラス対比表
3列の表形式(画面の役割、実装クラス名、セル/コンポーネント)
### 3. タブ構成
4列の表形式(タブ名、タイトル、メイン表示)
### 4. タブ別機能
各タブごとに主要な操作を**最大3つ**まで箇条書き。
### 5. 各画面
3列の表形式(画面の役割、タイトル、主要機能)
### 画面遷移図図(mermaid)
主要な画面のみ、3階層くらいまで
## 記述ルール
### 役割ベースで記述
- メインコンテンツは画面の**役割**で記述(例: "アイテム詳細画面"、"タイムライン")
- 実装クラス名は**対比表にのみ**記載(他の箇所には絶対に書かない)
- 必要最小限の情報のみ記載(AIエージェントが理解できる最小限度)
### 必須項目
- タブ構成(タブ名、数、タイトル)
- 各画面の構成要素(ナビゲーションバー、ボタン、リスト/グリッド等)
- 操作可能な要素(タップ、スワイプ、入力フィールド、チェックボックス等)
- 画面遷移図(どの操作でどの画面に遷移するか、モーダル/シート表示の区別)
- 実装クラス対比表(画面の役割 ↔ 実装クラス名)
### 含めない情報
- ViewModel、UseCase等のアーキテクチャ詳細
- ビジネスロジックの実装詳細
- 冗長な説明や補足
## 注意事項
- 表形式を活用して情報を構造化
- 冗長な表現を避け、簡潔に記述
このコマンドを実行すると、以下のような画面構成ガイドを作成してくれます。
# SCREEN_GUIDE
## 1. アプリ概要
写真を管理するアプリ
## 2. 実装クラス対比表
| 画面の役割 | 実装クラス名 | セル/コンポーネント |
|-----------|-------------|-------------------|
| タイムライン | TimeLineView | TimeLineCell |
| 写真グリッド | SpotListView | SpotListCell |
## 3. タブ構成
| タブ | タイトル | メイン表示 |
|------|----------|-----------|
| ホーム | "ホーム" | タイムライン(リスト形式) |
| 写真 | "写真一覧" | 写真グリッド表示 |
## 4. タブ別機能
### ホーム
- **ナビゲーション右上**: 情報ボタン→ アプリ情報画面(シート)
- **リストアイテムタップ**: アイテム詳細画面へ遷移
- **フローティングボタンタップ**: スポット登録画面(シート)
### 写真
- **チェックボックス**: 画像を持っているアイテムのみ表示フィルター
- **グリッドアイテムタップ**: アイテム詳細画面へ遷移
- **フローティングボタンタップ**: スポット登録画面(シート)
## 5. 各画面
| 画面の役割 | タイトル | 主要機能 |
|-----------|---------|---------|
| アプリ情報画面 | - | バージョン・ビルドバージョン表示、閉じるボタン |
| アイテム詳細画面 | - | 写真表示、編集操作 |
画面構成ガイドは詳細すぎると人間の認知負荷がかかり、メンテナンスコストがかかるので、あくまでUIテスト作成やAIエージェントがmobile-mcpを使って動作確認をスムーズに行うために概要だけを書かせてます。
機能的なところはコードベースやシミュレータで動かしただけでは把握できず、仕様書など別のドキュメントが必要であり、それらで補うべきという意図です。
また、UIテスト作成時の対話を通じてUIテストに正しい仕様が反映されているのが望ましそうです。
画面構成ガイドをもとにUIテストコードを作成する
mobile-mcpを使ってUIテストを作成させるにあたって、以下のようなUIテスト作成ガイドを作りました。
# UI Testing Guide
## 基本方針
- Page Object Pattern を使用
- mobile-mcp で実機操作し、UI要素を把握してからコード生成
- **プロダクトコード変更は原則禁止**(必要な場合は必ずユーザーに確認)
- コンテキスト不明時はユーザーに確認
- **3回連続失敗時は状況整理してユーザーと相談**
- **タスク終了前に必ずテスト実行**
## テスト追加フロー(必須)
1. **MCP で画面操作**: 遷移・UI要素を把握(画面構成は SCREEN_GUIDE.md 参照)
2. **Page Object 作成/修正**: 既存コード参考に必要な Page Object を準備
3. **テストコード生成**: Page Object を使用してテスト実装
4. **実機実行**: 実デバイスで動作確認
5. **修正ループ**: 成功するまで 3-4 を繰り返す
## 重要ルール
### 画面遷移検証
1. MCP/スクリーンショットで遷移前後のUI要素を把握
2. 遷移後の**固有UI要素**で検証(遷移前にも存在する要素では不可)
### スクリーンショット
- 保存先: `screenshots/`
- タスク完了後に削除
このコマンドを実行した際のログです。

AIエージェントがmobile-mcpを利用してシミュレータでアプリの画面を確認します。

AIエージェントがUIテストを作成します

AIエージェントがビルド後、UIテストを実行します

AIエージェントがテストを実行したが、エラーが出たことを報告してきます
実際の挙動を確認して、AIエージェントが読み取れなかった事象の説明をしてあげます
リストはデータが存在しないので表示されてないの1つ以上登録する必要があります
フローティングボタンが登録ボタンになっています。screen-guideを更新しつつ
フローティングボタンにアクセシビリティIDを追加してデータを登録するUIテストを作成してください

再び、テストを実行します

再び、テストが失敗しました

再度、実際の挙動を確認して、AIエージェントが読み取れなかったことを伝えてあげます。
※今回は、画面の認識が間違えていた(画面のネストで現在の画面がどこにいるかを認識できてなかった)
こういったようにやりとりを重ねていくと、テストコードが完成します。
import XCTest
final class HomeUITests: XCTestCase {
var app: XCUIApplication!
var homePage: HomePage!
var itemEditPage: ItemEditPage!
var coordinatesEditPage: CoordinatesEditPage!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
// Page Objectを初期化
homePage = HomePage(app: app)
itemEditPage = ItemEditPage(app: app)
coordinatesEditPage = CoordinatesEditPage(app: app)
homePage.waitForSplashScreenToDisappear()
}
override func tearDownWithError() throws {
app = nil
homePage = nil
itemEditPage = nil
coordinatesEditPage = nil
}
/// ホームタブが初期表示されることを確認(ナビゲーションバーとフローティングボタン)
func testHomeTabIsInitiallyDisplayed() throws {
homePage
.verifyNavigationBarExists()
// フローティングボタンが表示されることを確認
homePage.assertExists(homePage.floatingButton, message: "フローティングボタンが表示されませんでした")
}
/// 新しいアイテムを登録するテスト
func testRegisterNewItem() throws {
// フローティングボタンをタップしてアイテム編集画面を開く
homePage.tapFloatingButton()
// アイテム編集画面が表示されることを確認
itemEditPage.verifySheetIsDisplayed()
// 座標セルをタップして座標入力画面に遷移
itemEditPage.tapCoordinatesCell()
// 座標を入力
coordinatesEditPage.enterCoordinates(x: "100", y: "64", z: "200")
// 変更ボタンをタップ
coordinatesEditPage.tapModifyButton()
// 少し待機して画面遷移を待つ
sleep(1)
// アイテム編集画面に戻ったことを確認(座標セルが再び表示される)
itemEditPage.verifySheetIsDisplayed()
// 登録ボタンをタップ
itemEditPage.tapRegisterButton()
// ホーム画面に戻ったことを確認(フローティングボタンが表示される)
// シートが閉じる検証は、NavigationBarがホーム画面とアイテム編集画面の両方に存在するため使えない
homePage.assertExists(homePage.floatingButton, timeout: 10, message: "ホーム画面に戻りませんでした")
// データが登録されたことの確認
// Note: リストの即座の更新は保証されないため、この時点ではリストの存在確認をスキップ
// データの存在確認は別のテストで行う
}
}
出来上がったコードに大きな問題はなさそうでした。
Page Object Patternで作成することを指示しているので、可読性も悪くありません。
UIテストコードのコーディングガイドのようなものを作成して制御すればよさそうです。
といったように、まだ制御は必要ですが、これまでとは格段に作成するハードルがさがりました。
この方法を使えば、UIテストではなく通常の開発時の動作確認もサッと実行できます。
まとめ
今回、mobile-mcpを初めてガッツリつかったのですが、とても安定していてAI活用の幅がものすごく広がったなと感じます。
また、Claude Codeと組み合わせることで、さらにいろいろな可能性が考えられそうです。
Discussion