同じ手順を、申請のたびに指で確かめていた
私はいくつかの壁紙アプリやユーティリティアプリを個人開発で運用していますが、Rork で組んだ小さなアプリを App Store と Google Play に出すようになってから、ひとつ困った習慣ができました。申請ビルドを作るたびに、シミュレータを起動して「オンボーディングを進める → 最初の保存をする → ペイウォールが正しく出る → 復元ボタンが動く」を指でなぞって確認していたのです。
最初の数回は数分で済みます。けれど画面が増え、プロンプトで「この画面のボタンの色を変えて」「保存処理にエラーハンドリングを足して」と修正を重ねるうちに、確認する動線が枝分かれしていきました。あるとき、ペイウォールの文言を直しただけのつもりが、復元ボタンの onPress が外れていたことに、申請直前まで気づけませんでした。手で確かめる範囲が広がるほど、見落としが増えていったのです。
AI でコードを生成・修正するワークフローには、この種のリグレッションが起きやすい構造的な理由があります。プロンプトは「直したい箇所」を指すだけで、影響範囲を保証してくれません。だからこそ、売上に直結する数本の動線だけは機械に毎回なぞらせる価値があります。ここでは Maestro を使って、Rork で生成したアプリの主要動線を自動E2E化する手順を、実際に詰まった箇所とともに記します。
なぜ Maestro を選んだか — Detox との実際の差
React Native の E2E テストというと Detox が長く定番でしたが、Rork のように生成コードを素早く回す個人開発の現場では、Maestro のほうが立ち上がりが圧倒的に軽いというのが私の結論です。両者の差を、導入の手触りで並べてみます。
観点 Maestro Detox
テストの記述 YAML(宣言的・数行で書ける) JavaScript(jest 連携・記述量が多い)
セットアップ CLI を入れて flow を1本置くだけ ビルド設定・config・テストランナー統合が必要
待機処理 要素が出るまで自動で待つ 明示的な同期 API を意識する場面が多い
フレーク耐性 リトライと自動待機が前提で安定しやすい 設定を詰めれば強いが初期は不安定になりがち
Expo との相性 ビルド済みアプリに外から触れるので影響が小さい ネイティブビルドへの組み込みが前提
Detox が劣っているという話ではありません。大規模で、ネイティブの細かい状態まで検証したいなら Detox の表現力が活きます。ただ「生成したアプリの売上動線を、明日から守りたい」という目的には、YAML 数行で書けて壊れにくい Maestro が合っていました。
準備その1: testID を要素に振る
Maestro は画面上のテキストや testID を手がかりに要素を探します。表示テキストだけでも動きますが、文言は翻訳や A/B で変わるため、安定させるなら主要な操作対象に testID を振っておくのが安全です。
Rork が生成したコンポーネントには testID が無いことが多いので、エクスポートしたコードに手で足すか、Rork へのプロンプトで出力させます。私はプロンプト側で指示するようにしています。
(Rork へのプロンプト例)
保存ボタン、復元ボタン、ペイウォールの購入ボタンに、それぞれ
testID="save-button" / testID="restore-button" / testID="paywall-buy-button"
を付けてください。表示文言は変えないでください。
手で足す場合は、対象の Pressable や TouchableOpacity に属性を追加するだけです。
// 例: 復元ボタンに testID を追加
< Pressable
testID = "restore-button"
onPress = { handleRestore }
accessibilityRole = "button"
>
< Text >購入を復元</ Text >
</ Pressable >
accessibilityRole を併記しておくと、スクリーンリーダー対応にもなり、後で Maestro が要素を見つけやすくなります。テストのためだけの属性ではないと考えると、付ける手間に納得しやすいはずです。
準備その2: Maestro を入れて最初の flow を置く
Maestro はホスト側に CLI を入れて、ビルド済みのアプリを外から操作します。アプリ本体のコードには手を入れません。
# Maestro CLI のインストール(macOS / Linux)
curl -fsSL "https://get.maestro.mobile.dev" | bash
# バージョン確認
maestro --version
# プロジェクト直下に flow 置き場を作る
mkdir -p .maestro
テストする対象は、Expo の dev client か、本番環境に近い内部配布ビルドが理想です。Expo Go ではネイティブモジュール(課金や通知)が動かないため、動線テストには dev client を使ってください。
最初の1本は、起動して最初の価値画面までたどり着けるかを見る「スモークテスト」にします。これが落ちると他の何を測っても無意味なので、土台として最優先で置きます。最初の1本はこのスモークから始めることを強く推奨します。
# .maestro/smoke.yaml
appId : net.rorklab.sample # アプリの bundle identifier に置き換える
---
- launchApp :
clearState : true # 前回の状態を消して、毎回まっさらから始める
- assertVisible : "はじめる" # オンボーディング初期画面の文言
- tapOn : "はじめる"
- assertVisible :
id : "home-screen" # ホームの testID
# ローカルで実行
maestro test .maestro/smoke.yaml
clearState: true を入れているのがポイントです。前回テストのログイン状態やオンボーディング完了フラグが残っていると、初回起動の動線をすり抜けてしまい、本番ユーザーが見る画面を検証できません。
主要動線を1本にする: オンボーディングからペイウォールまで
スモークが通ったら、売上に直結する動線を1本のフローにします。私の場合、最も壊れて困るのは「オンボーディングを抜けて最初の保存をし、無料枠を超えたタイミングでペイウォールが出て、復元が動く」という流れでした。
# .maestro/paywall-flow.yaml
appId : net.rorklab.sample
---
- launchApp :
clearState : true
- tapOn : "はじめる"
# 権限ダイアログ(通知)を許可。OS のシステムダイアログにも touch できる
- runFlow :
when :
visible : "通知を許可しますか"
commands :
- tapOn : "許可"
# 最初の保存を行う
- tapOn :
id : "add-button"
- inputText : "テスト用メモ"
- tapOn :
id : "save-button"
- assertVisible :
id : "home-screen"
# 無料枠を使い切る想定の操作を繰り返す(サブフロー化)
- repeat :
times : 3
commands :
- tapOn :
id : "add-button"
- tapOn :
id : "save-button"
# ペイウォールが出ることを確認
- assertVisible :
id : "paywall-buy-button"
# 復元ボタンが存在し、押せることを確認
- tapOn :
id : "restore-button"
- assertVisible : "復元"
ここで効いてくるのが runFlow の条件付き実行です。通知や ATT(トラッキング許可)のシステムダイアログは、OS やテスト環境によって出たり出なかったりします。when.visible で「出たときだけ許可を押す」と書いておくと、ダイアログの有無でテストが落ちなくなります。システムダイアログの揺らぎを回避できるわけです。これは私が最初に大きくハマった箇所で、ダイアログを無条件で tapOn していた頃は、二回目以降の実行で要素が見つからず赤くなっていました。
課金サンドボックスでの落とし穴
ペイウォールの「購入する」まで自動で押し切りたくなりますが、実際の StoreKit / Google Play のサンドボックス購入ダイアログは、パスワード入力や Face ID を求める場面があり、E2E で安定して突破するのは現実的ではありません。
私の運用では、購入ボタンの「手前」までを Maestro で守り、実際の決済成立は RevenueCat のサンドボックスや手動テストに任せています。E2E が保証するのは「ペイウォールが正しい条件で出て、ボタンが押せる状態にある」ことです。ここが壊れると無料ユーザーが課金画面にすらたどり着けないため、守る価値が最も高い境界線になります。
テスト用アカウントのメールアドレスなどは、フロー内に直書きせず環境変数で渡します。
# env を使う例
- inputText : ${MAESTRO_TEST_EMAIL}
# 実行時に注入
MAESTRO_TEST_EMAIL = "tester@example.com" maestro test .maestro/paywall-flow.yaml
CI で PR ごとに回す
ローカルで通っても、手元で実行し忘れたら意味がありません。GitHub Actions に組み込み、プルリクエストのたびに自動で回します。エミュレータをワークフロー内で立てて、ビルド済み APK に対してフローを実行する構成です。
# .github/workflows/e2e.yml
name : E2E (Maestro)
on :
pull_request :
branches : [ main ]
jobs :
maestro :
runs-on : ubuntu-latest
timeout-minutes : 20
steps :
- uses : actions/checkout@v4
- name : Maestro をインストール
run : |
curl -fsSL "https://get.maestro.mobile.dev" | bash
echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
- name : Android エミュレータでフローを実行
uses : reactivecircus/android-emulator-runner@v2
with :
api-level : 34
arch : x86_64
script : |
adb install -r ./build/app-release.apk
maestro test .maestro/
maestro test .maestro/ とディレクトリを渡すと、その中の全フローを順に実行します。smoke.yaml を必ず最初に置き、落ちたら早く止めることで、CI の待ち時間を短く保てます。ビルド成果物の APK は、別ジョブで Expo / EAS から取得してアーティファクトとして渡すか、内部配布ビルドを使ってください。
実行時間の目安として、私の小さなアプリではスモークと主要動線の2本で 3〜5分に収まっています。この程度なら PR を止めるストレスにならず、むしろ「赤くなったら何かを壊した」という安心材料になります。
どこまでテストして、どこで止めるか
E2E は書けば書くほど壊れやすく、メンテナンス負債になります。私が個人開発で採用している線引きは、「壊れると売上か信頼が即座に毀損する動線」だけを対象にすることです。具体的には、次の4点に絞っています。
アプリが起動できること
オンボーディングを抜けられること
ペイウォールが正しい条件で出ること
復元ボタンが押せること
この4点に絞ると、フローは数本に収まり、メンテナンスも軽く保てます。
逆に、細かな UI の見た目や、めったに通らない設定画面の分岐までを E2E で追うのは推奨しません。そこはコンポーネント単位のテストや手動確認のほうが費用対効果が高い領域です。AI で素早く作り変えるアプリだからこそ、機械に守らせる範囲を意図的に小さく、鋭く保つことが、長く運用するコツだと考えています。
まず最初の一歩として、起動から最初の価値画面までの1本だけを .maestro/smoke.yaml に置いて、maestro test を一度走らせてみてください。緑が一本灯るだけで、申請前の指でのなぞり確認から解放される手応えが得られるはずです。お読みいただきありがとうございました。