取り組みの背景:アップロードは成功したのに、誰にも届かない
TestFlight で最初に面食らったのは、審査でも証明書でもありませんでした。
ビルドのアップロードは緑のチェックマークで完了しました。App Store Connect のビルド一覧にも、ちゃんと新しい番号が並んでいます。それなのに、招待したテスターの TestFlight アプリには何も表示されませんでした。
原因は、ビルド番号の横に小さく出ていた黄色い「コンプライアンスがありません」の一言でした。輸出コンプライアンスの申告が未回答のあいだ、そのビルドは誰にも配布されません。エラーではないので、探しに行かない限り気づけません。
TestFlight でつまずく場所は、だいたいこういう「失敗として通知されない箇所」に集中しています。以下では手順を追いながら、そのたぐいの落とし穴を先回りして潰していきます。Rork および Rork Max で生成したアプリを前提にしていますが、Expo ベースのプロジェクトであれば考え方はそのまま流用できます。
TestFlight の基礎知識:内部と外部で何が変わるのか
TestFlight には性質のまったく異なる2つの配布経路があります。ここを取り違えると、リリース計画が数日ずれます。
項目 内部テスト 外部テスト
人数上限 100名 10,000名
対象者の条件 App Store Connect にユーザー登録された人のみ(Account Holder / Admin / App Manager / Developer / Marketing のいずれかのロール) メールアドレスまたは公開リンクがあれば誰でも
Apple の事前レビュー 不要 各バージョンの初回ビルドで必要(おおむね1〜2日)
配布までの時間 処理完了後すぐ(数分〜数十分) レビュー通過後
主な用途 自分と協力者の動作確認、修正の即時検証 実ユーザー環境での検証、リリース前の反応収集
内部テスターの上限を25名と書いている情報が今も多く残っていますが、これは古い数字です。現在は100名まで登録できます(1人あたり最大30台のデバイス)。個人開発の規模であれば内部枠が足りなくなることはまずないので、「協力者を App Store Connect のユーザーとして招く手間を許容できるか」だけが実質的な判断軸になります。
私自身は、修正を回している期間はすべて内部テストで済ませて、外部テストはリリース候補が固まってから一度だけ通す運用にしています。外部テストは初回ビルドごとにレビューが挟まるため、細かい修正を毎回外部に流すとレビュー待ちが積み上がってしまいます。
前提条件と、最初に決めるルート選択
必要なものは3つです。Apple Developer Program の登録(年間 $99)、Rork Max のサブスクリプション(ネイティブ iOS アプリを生成する場合)、そして Mac です。
Mac が必要かどうかは、後述するルート選択で変わります。ここが最初の分岐点になります。
EAS Submit ルート Xcode Organizer ルート
Mac の要否 不要(クラウドビルド) 必須
証明書・プロファイル EAS が自動生成・自動更新 手動で作成し Xcode に登録
向いているケース Expo 構成のまま出す、CI に載せたい、Mac を持っていない ネイティブコードを手で触っている、Xcode 側の設定を細かく制御したい
初回の所要時間 おおむね20〜40分(うち大半はビルド待ち) おおむね60〜90分(証明書作業が支配的)
Rork が生成するプロジェクトは Expo 構成を保っていることが多く、その場合は EAS Submit ルートのほうが明確に負担が軽くなります。証明書とプロビジョニングプロファイルという、TestFlight で最も脱落が多い工程をまるごと省略できるためです。
以下では EAS ルートを主線として書き、Xcode ルートは差分だけ補足します。
ステップ1:App Store Connect でアプリ枠を作る
appstoreconnect.apple.com にログインし、「マイApp」→「+」→「新規App」と進みます。
入力項目のうち、後から変更できないものが1つだけあります。Bundle ID です。ここだけは慎重に扱ってください。プラットフォーム、名前、SKU は後から調整できますが、Bundle ID を間違えると新しいアプリ枠を作り直すことになります。
Rork Max でエクスポートしたプロジェクトの Bundle ID は、app.json(または app.config.js)の ios.bundleIdentifier に入っています。ネイティブプロジェクトとして書き出した場合は Info.plist の CFBundleIdentifier を見てください。
{
"expo" : {
"name" : "MyApp" ,
"slug" : "my-app" ,
"version" : "1.0.0" ,
"ios" : {
"bundleIdentifier" : "com.yourname.myapp" ,
"buildNumber" : "1"
}
}
}
App Store Connect 側とこの文字列が1文字でも違うと、アップロード時に弾かれます。コピーして貼り付けるのが確実です。
ステップ2:ビルドを作ってアップロードする
EAS ルート
eas.json に production プロファイルと submit の設定を書きます。appleId などを直接書きたくない場合は省略でき、その場合は実行時に対話で聞かれます。
{
"cli" : { "version" : ">= 5.0.0" },
"build" : {
"production" : {
"ios" : {
"resourceClass" : "m-medium"
}
}
},
"submit" : {
"production" : {
"ios" : {
"ascAppId" : "1234567890"
}
}
}
}
ascAppId は App Store Connect のアプリ URL に含まれる数値です(.../apps/1234567890/... の部分)。あとは2コマンドで完了します。
# ビルド(証明書がなければ対話で自動生成される)
eas build --platform ios --profile production
# 直前のビルドを App Store Connect に送る
eas submit --platform ios --latest
初回は Apple ID とアプリ固有パスワード(App-Specific Password)を求められます。二要素認証を有効にしている Apple ID では通常のパスワードは通らないため、appleid.apple.com で事前に発行しておくと止まらずに済みます。
Xcode ルート
Rork Max から「Export」→「Xcode Project」で書き出し、.xcodeproj を開きます。ターゲットの「Signing & Capabilities」で Team を選び、Bundle Identifier が App Store Connect の登録と一致しているか確認します。
デバイス選択を「Any iOS Device (arm64)」にしてから「Product」→「Archive」を実行します。Organizer が開いたら「Distribute App」→「App Store Connect」→「Upload」と進み、署名は「Automatically manage signing」に任せるのが無難です。
証明書を手で作る場合は、Xcode の「Settings」→「Accounts」→「Manage Certificates...」から Apple Distribution 証明書を作成し、developer.apple.com/account の「Certificates, Identifiers & Profiles」→「Profiles」で App Store Connect 用のプロビジョニングプロファイルを生成します。
輸出コンプライアンスの申告(ここで止まる人が最も多い)
冒頭に書いた「アップロードは通ったのに配布されない」の正体がこれです。App Store Connect は、アプリが非適用暗号化(non-exempt encryption)を使っているかどうかの申告を求めます。未回答のあいだ、そのビルドはテスターに配布されません。
毎回ダッシュボードで答えるのは忘れるので、app.json に恒久的に書いてしまうのが確実です。
{
"expo" : {
"ios" : {
"bundleIdentifier" : "com.yourname.myapp" ,
"infoPlist" : {
"ITSAppUsesNonExemptEncryption" : false
}
}
}
}
ここで問われているのは「暗号化を使っているか」ではなく「適用除外にあたらない 暗号化を使っているか」です。HTTPS 通信や、iOS が標準で提供する暗号 API の利用は適用除外にあたるため、大半のアプリは false で問題ありません。
独自の暗号方式を実装している場合(自前の End-to-End 暗号プロトコルを持つメッセージングアプリなど)は true にします。その場合は追加の質問が続き、多くのケースで輸出関連の書類提出を求められます。判断に迷う要素があるなら、false と書く前に自分のアプリが何を使っているかを一度洗い出してください。ここは誤申告すると面倒な種類の項目です。
アップロード後の処理には通常5〜15分、混雑時は30分〜1時間かかります。
ステップ3:TestFlight でテスターに配る
App Store Connect で対象アプリの「TestFlight」タブを開きます。
内部テスト は、「内部グループ」でグループを選び、対象ビルドを有効にしてテスターを追加するだけです。招待された人はメールのリンクから TestFlight アプリを開いて参加できます。前提として、その人が App Store Connect のユーザーとして登録されている必要があります(「ユーザーとアクセス」から先に招待しておきます)。
外部テスト は「外部グループ」を作成し、ビルドを追加してから「テスターを送信」でレビューに出します。
このとき、「テスト内容(What to Test)」の記入が実質的に必須 です。空欄や「バグ修正」だけの一行だと、レビューで差し戻されることがあります。何を試してほしいのか、どの機能が新しいのかを具体的に書いてください。この欄はテスターの TestFlight アプリにもそのまま表示されるため、フィードバックの質にも直結します。
外部テスターの招待方法は個別のメール招待と公開リンクの2通りです。公開リンクは拡散しやすい反面、誰が入ってきたか把握しづらく、フィードバックの文脈が読めなくなりがちです。最初は個別招待から始めることをお勧めします。
ステップ4:フィードバックとビルド有効期限の管理
TestFlight の利点は、テスターがアプリ内から直接フィードバックを送れることです。App Store Connect の「TestFlight」→「フィードバック」で、スクリーンショット付きの報告とクラッシュレポートを確認できます。
クラッシュの報告が届かない場合は、テスター側の設定を疑ってください。iPhone の「設定」→「プライバシーとセキュリティ」→「解析と改善」→「Appデベロッパと共有」がオフだと、クラッシュ情報は送られません。
ビルドは90日で期限切れになる
見落としやすいのがこれです。TestFlight のビルドには90日の有効期限 があります。期限を過ぎるとテスターはそのビルドを起動できなくなり、「Beta Has Expired」と表示されます。
ベータ期間を長めに取る予定なら、90日を1サイクルとして新しいビルドを流し込む前提でスケジュールを組んでください。長期のクローズドベータで「急に全員が使えなくなった」という事故は、ほぼこれが原因です。
アップデートの配布
修正したら、app.json の ios.buildNumber を増やして再ビルドします。バージョン番号(1.0)は据え置き、ビルド番号だけを上げるのが TestFlight での通常の運用です。
# buildNumber をインクリメントしてから
eas build --platform ios --profile production --auto-submit
--auto-submit を付けるとビルド完了後に自動でアップロードまで進みます。修正を細かく回す期間はこれが効きます。TestFlight で新ビルドを有効化すると、テスターに自動で通知が届きます。
App Store 提出前の最終チェックリスト
TestFlight での検証が一巡したら、正式申請の前に以下を確認します。
クラッシュ率 :TestFlight のクラッシュレポートで、主要な操作フローにクラッシュが出ていないことを確認します。目安はクラッシュ率1%未満です。
画面サイズと OS バージョン :iPhone SE のような小さい画面、最新 iPhone、iPad(対応する場合)で基本動作を確認します。サポート下限に指定した iOS バージョンでの起動確認も忘れずに。
ネットワーク条件 :Wi-Fi だけでなく、モバイル回線と低速回線でも動くか確認します。Xcode の Network Link Conditioner や、iOS の「デベロッパ」設定から低速環境を再現できます。
権限の説明文 :カメラ・マイク・位置情報などを使う場合、NSCameraUsageDescription などの説明文が具体的に書かれているか確認します。「カメラを使います」のような内容の薄い文言は、審査で指摘されることがあります。
プライバシーポリシーとスクリーンショット :ポリシー URL の設定と、必要な画面サイズ分のスクリーンショット(iPhone 6.9インチ、6.7インチなど)を揃えます。
つまずいたときの切り分け
症状 まず疑う箇所 対処
アップロードは成功したがテスターに見えない 輸出コンプライアンス未申告 ビルド一覧の黄色い警告を確認。`ITSAppUsesNonExemptEncryption` を app.json に追加して再ビルド
ビルドが「処理中」から進まない Apple 側の処理遅延 通常30分〜1時間。数時間かかる場合もあるため翌日まで待つ。[Apple System Status](https://developer.apple.com/system-status/) で障害の有無を確認
テスターの TestFlight にアプリが出てこない Apple ID の不一致 / レビュー未完了 招待したメールアドレスとテスターの Apple ID が一致しているか確認。外部テストならレビュー通過を待つ
「Beta Has Expired」と表示される ビルドの90日期限 新しいビルドをアップロードして有効化する
クラッシュしているのに報告が届かない テスター端末の共有設定 「解析と改善」→「Appデベロッパと共有」をオンにしてもらう
アップロード時に Bundle ID で弾かれる app.json と App Store Connect の不一致 両方の文字列を並べて突き合わせる。大文字小文字も区別される
個人開発者としての運用メモ
複数のアプリを並行して回していると、TestFlight で一番コストがかかるのは技術的な作業ではなく、テスターとの往復 だと感じています。
初期の頃は「気づいたことがあれば何でも教えてください」と伝えて配っていました。返ってくるのは「なんとなく重い気がする」「使いにくい」といった、こちらが動けない粒度の感想がほとんどでした。テスターが悪いのではなく、何を見ればいいのか伝えていなかったのが原因です。
いまは「テスト内容」の欄に、試してほしい動線を3つまでに絞って番号付きで書くようにしています。「①初回起動から設定完了まで ②オフラインで開いたときの挙動 ③課金画面を閉じたあとの戻り先」といった具合です。範囲を狭めるほど、返ってくる報告は具体的になりました。
もうひとつ変えたのは、内部テストの使い方です。以前は自分ひとりで実機確認して、問題なさそうなら外部に流していました。いまは外部に出す前に必ず内部テストへ一度上げ、自分の実機に TestFlight 経由でインストールして触ります。開発ビルドでは通っていたものが、リリースビルドの最適化で挙動を変えることがあるためです。数分で済む工程ですが、これを挟むようになってから外部テスト初回での差し戻しが減りました。
ベータ期間そのものも、以前より短く取るようになりました。長く走らせても報告は最初の数日に集中し、そのあとは静かになります。90日の有効期限を意識しながら、1〜2週間で区切って次のビルドに進むほうが、結果的に修正の回転が速くなりました。
まとめ
TestFlight でつまずく箇所は、証明書やレビューよりも「失敗として通知されない設定漏れ」に偏っています。輸出コンプライアンスの未申告、ビルドの90日期限、テスト内容の空欄。どれもエラーメッセージが出ないまま配布を止めます。
これから最初のビルドを上げるなら、まず app.json に ITSAppUsesNonExemptEncryption: false を書き足すところから始めてください。1行で、最も頻度の高い停止要因を恒久的に取り除けます。
沈黙して止まる箇所さえ押さえておけば、TestFlight は驚くほど素直に動いてくれます。あなたのベータが静かに詰まらずに進むことを願っています。