iOSのXCUITestをCI上で実機向けビルドする
タイトルの通りのことをするときに、署名で無駄にハマったので、メモしておきます。
前提として、
- fastlane matchで証明書やプロファイルの管理をしている。
- ローカルではシミュレータ向けでも実機向けでもビルド可能。
- CIではシミュレータ向けにはビルド・実行可能
結論
-
#{app_identifier}.#{test_target_scheme}.xctrunner
をindentifierに登録 -
fastlane match development -a #{app_identifier}.#{test_target_scheme}.xctrunner
- development向けプロファイルをmatch管理下で作成する。
- Xcodeで、test用ターゲットのmanual signingを有効化し、matchしたプロファイルを設定
- エラーが出ても無視
- ci上で
fastlane match development --readonly && fastlane scan --build_for_testing
ごく当たり前の話+αですが、いくつかのポイントでハマってしまいました。
(fastlaneのコマンドはlaneやMatchfile/Scanfile等を作って実行してるので不足があります)
起きるエラー
下記のように、テスト用のビルドを行うときにプロファイルがない、というエラーが起きました。
[04:04:41]: ▸ /Users/runner/work/my-repo/my-repo/MyApp.xcodeproj: error: No profiles for 'net.wasnot.sample.MyApp-UITests.xctrunner' were found: Xcode couldn't find any iOS App Development provisioning profiles matching 'net.wasnot.sample.MyApp-UITests.xctrunner'. Automatic signing is disabled and unable to generate a profile. To enable automatic signing, pass -allowProvisioningUpdates to xcodebuild. (in target 'MyApp_UITests' from project 'MyApp')
[04:04:41]: ▸ 2024-03-26 04:04:41.480 xcodebuild[7012:36009] Writing error result bundle to /var/folders/f8/xn9w26457_v2fc5qzhbrlyj00000gn/T/ResultBundle_2024-26-03_04-04-0041.xcresult
[04:04:42]: ▸ xcodebuild: error: Failed writing xctestrun file: The folder “MyApp_iphoneos17.0-arm64.xctestrun” doesn’t exist..
ここではMyApp
というのがプロジェクト名や、アプリターゲット・scheme名で、MyApp_UITests
というのがUIテスト用のターゲット・scheme名です。
MyApp用のprofileは存在して、fastlane match
でインストールできているのに、MyApp_UITests用のものが存在しないのが問題でした。
ハマり1: XCTestでも署名は必要
ローカルでの実機インストールではwildcardプロファイルが使われる?
手元でXCTestを実機インストールしている際には、プロファイル等は特に不要で、dev portal上にも特にidentifier/profileは追加されていなかったため、不要と勘違いしてしまいました。
よくあることかと思いますが、ローカル環境だと無料プランでもXcodeにappleアカウントでログインしていると、XC wildcard
のようなidentifierが適用されて、profileも一時的なものが作られて?、わざわざidentifyやprofileを用意しなくても動くようです。
XCTest用のRunnerも署名されていた
そして、XCTestRunnerは通常はテスト内容を示すxmlファイルだけだったりするので、わざわざ署名しないでも動いている、と誤解してしまっていました。
XCTestを実行するためのRunnerも通常のアプリのようにパッケージ・署名され、実機にインストールされる必要があるようです。
また、Test用ターゲットでは署名がAutomatically設定のままで、ローカルではエラー等も出ていないため、気付くのが遅れてしまいました。
その点は一応、CI上ではautomatically signingが動かないので、-allowProvisioningUpdates
をつけなさい、と言う警告が出ていました。
この警告でももっと素早く気付くことができたかなと思います。
参考
証明書関連、CIやテスター文脈ではよくある話で、automatically signingだとidentifier/profileがプロジェクト作成時にしか作成されないため、ダミープロジェクトを作るべし、としているドキュメントがいくつかありました。
今回は目的と異なりますが、参考にはなりました。
- Real Device Configuration - Appium XCUITest Driver
- No profiles for '' were found: Xcode couldn't find any iOS App Development provisioning profiles matching '' – MagicPodヘルプセンター
- iOS実機にうまく接続できない場合の回避策 – MagicPodヘルプセンター
ハマり2: identifierに勝手にxctrunnerが付与される
これが本題ですが、Xcode 11ころからXCUITest用のtargetではidentifierに暗黙で.xctrunner
というsuffixが付与されるようです。
それだけならいいのですが、XcodeのTarget編集画面ではxctrunnerは見えず、証明書もxctrunner付きのidentifierのものだと、エラーとして表示されてしまうようです。
Fastlane match for UITest Target · fastlane/fastlane · Discussion #20064
Xcode 15でも変わらず、エラー表示になりますが、ビルドや実行はローカルでも特に問題なくできます。
気持ち悪いですが仕方ないですね。。
参考
Xcodeのバグ?仕様変更を認める問い合わせはいくつかありました。
日本語の記事は見つからなかったので、皆さんはこまっていないかもしれません。
- xcode 11 changes the bundle id of WDA · Issue #13086 · appium/appium
- xcode - Automatic appended .xctrunner in my XCUITest bundle id while running on device - Stack Overflow
解決法
1. dev portalでidentifierを登録
UITestターゲット用のidentifierをapple developer portal上でapp idとして追加します。
この際、net.wasnot.sample.MyApp-UITests
のように#{app_indentifier}.#{UITest-Target}
ではなく、
net.wasnot.sample.MyApp-UITests.xctrunner
のようにsuffixに.xctrunner
をつける必要があります。
これは、上でも書いたようにXcodeが勝手にsuffixを追加するためで、UITestのターゲットで設定変更した場合も追加する必要があるので注意です。
2. fastlane matchを使用してprofileを作成
cliやlaneを用いて、identifierに対応するprofileをmatch管理下で作ります。
CLIの場合
$ fastlane match development -a net.wasnot.sample.MyApp-UITests.xctrunner
Fastfileの場合
lane :test_lane do |options|
match(
app_identifier: ["#{app_identifier}.MyApp-UITests.xctrunner"],
type: 'development',
readonly: false, # 初回登録時はfalseにするが、ciからはtrueで呼び出す
)
end
3. Xcodeのsigningに設定する
XcodeでUITestsターゲットの署名設定をautomatically -> manualに変更し、作成したmatch development
から始まるprofileを選択します。
この際、上でも言及した通り、ターゲット上で設定できるidentifierと異なるため、選択肢に出ない、エラー表示になる、と言う問題がありますが、ビルド時や実機テスト時には問題ないので無視します。
下記のようなエラー表示になりますが、問題ありません。
4. CI上でmatchおよびbuildを行う
あとは自分の行いたい実機向けビルドを行うだけです。
自分の場合の例を示して終わりにします。
desc "Bundle XCTestRun for execute on Firebase Test Lab"
lane :bundle_xctestrun do |options|
match(
# 実機向けに必要なすべてのidentifierのプロファイルをmatchする
app_identifier: [
app_identifier,
app_extension_identifier,
"#{app_identifier}.MyApp-UITests.xctrunner"
],
type: 'development',
readonly: true,
)
scan(
scheme: 'MyApp',
clean: true,
skip_detect_devices: true,
build_for_testing: true,
sdk: 'iphoneos', # 実機向けにビルドする
should_zip_build_products: true
)
end
別件: CI上でのmatchエラー
ちなみに今回はGithub Actionsを使用していましたが、そもそもfastlane matchが失敗してしまう問題がありました。
これは下記箇所があったためでした。
- match設定(Matchfile)のgit_urlにgit schemeを使っていた
- git_basic_authorizationをruby上で作成、設定していた
修正したら直りました。
git schemeの修正
githubの仕様変更への対応もれの可能性があります。
(circleciやローカルでは動いていたので謎です)
-git_url "git@github.com:xxx/my-certificates.git"
+git_url "https://github.com/xxx/my-certificates.git"
auth headerの作成
こちらは、もっと理由がわからないので、たまたまかもしれないです。
詳しく調べていません、すみません。
ダメだった
username = ENV['USERNAME']
personal_github_access_token = ENV["PERSONAL_GITHUB_ACCESS_TOKEN"]
authorization_token_str = "#{username}:#{personal_github_access_token}"
basic_authorization_token = Base64.strict_encode64(authorization_token_str)
match(
git_basic_authorization:basic_authorization_token,
type: "development",
app_identifier: app_identifiers(),
readonly: true
)
動いた
- name: calculate env
run: |
echo "MATCH_GIT_BASIC_AUTHORIZATION=$(echo -n ${{secrets.GH_USERNAME}}}:${{ secrets.GH_ACCESS_TOKEN }} | base64)" >> $GITHUB_ENV
- name: fastlane match
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
run: fastlane match development
参考
matchのtokenの設定方法として参考になりました。
Discussion