Mengotomatiskan UI test iOS dengan XCUITest di Mac khusus
Untuk mengotomatiskan XCUITest, jalankan xcodebuild test dengan destination simulator dan -resultBundlePath, di Mac yang punya sesi pengguna yang sedang login. Buat simulator khusus dengan xcrun simctl. Tambahkan -parallel-testing-enabled YES untuk membagi test ke beberapa klon simulator, dan -retry-tests-on-failure untuk mengulang test yang flaky. Kami menjalankan setiap perintah di sini di Mac dengan Xcode 26.6 dan simulator iOS 26.5.
Yang Anda butuhkan lebih dulu
- Mac dengan Xcode dan minimal satu runtime simulator iOS.
- Target UI test di proyek Anda dan shared scheme yang mengujinya.
- Sesi desktop yang sedang login. UI test mengendalikan aplikasi Simulator, jadi apa pun yang menjalankannya harus hidup di sesi itu. Artinya agen CI dijalankan sebagai LaunchAgent, bukan LaunchDaemon. Template plist dari Buildkite menyebut hal yang sama: mode GUI memungkinkan UI testing Xcode, tapi butuh login.
1. Cek Xcode dan runtime-nya
xcodebuild -version sudo xcodebuild -runFirstLaunch xcrun simctl list runtimes xcrun simctl list devicetypes | grep iPhone
-runFirstLaunch memasang package dan menyetujui lisensi. Jalankan setiap kali selesai memasang atau meng-upgrade Xcode.
2. Buat simulator khusus untuk CI
Perangkat khusus memisahkan CI dari apa pun yang dibuka orang secara manual. bootstatus -b menyalakannya dan menunggu sampai siap.
UDID=$(xcrun simctl create "CI iPhone 17" "iPhone 17" com.apple.CoreSimulator.SimRuntime.iOS-26-5) xcrun simctl bootstatus "$UDID" -b echo "$UDID"
Pakai UDID di destination: -destination "platform=iOS Simulator,id=$UDID". Nama juga bisa dipakai, seperti name=iPhone 17,OS=26.5, tapi dua perangkat bisa punya nama yang sama.
3. Jalankan UI test dengan result bundle
rm -rf build/TestResults.xcresult xcodebuild test \ -project MyApp.xcodeproj \ -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" \ -derivedDataPath build/DerivedData \ -resultBundlePath build/TestResults.xcresult
Result bundle menyimpan setiap hasil test, log, screenshot, dan kegagalan. Baca ringkasannya di command line:
xcrun xcresulttool get test-results summary --path build/TestResults.xcresult
Perintah ini mencetak JSON berisi jumlah test yang lulus, gagal, dan dilewati per perangkat. Lampirkan bundle ke run CI Anda sebagai artifact, di-zip dengan ditto -c -k --keepParent.
4. Build sekali, test berkali-kali
Pisahkan build dari run test. Dengan begitu Anda bisa mengulang sebagian test tanpa kompilasi ulang.
xcodebuild build-for-testing -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData xcodebuild test-without-building -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData \ -only-testing:MyAppUITests/LoginTests/testSignIn
-only-testing menerima Target, Target/Class, atau Target/Class/method. -skip-testing bekerja dengan cara yang sama, tapi kebalikannya.
5. Jalankan test secara paralel
xcodebuild test -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" \ -parallel-testing-enabled YES \ -parallel-testing-worker-count 2 \ -resultBundlePath build/TestResults.xcresult
Xcode mengklon simulator dan menyebar class test ke klon-klon itu. Log kami menunjukkan test berjalan di Clone 1 of iPhone 17. -parallel-testing-enabled mengesampingkan pengaturan di scheme. Di Mac 16 GB, mulai dengan 2 worker dan ukur dulu sebelum menambahnya. Setiap klon adalah simulator penuh di memori. Untuk menguji beberapa jenis perangkat sekaligus, cantumkan beberapa destination dan atur -maximum-concurrent-test-simulator-destinations.
6. Ulangi test yang flaky
xcodebuild test -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" \ -retry-tests-on-failure \ -test-iterations 3 \ -resultBundlePath build/TestResults.xcresult
Menurut xcodebuild -help, test yang gagal diulang sampai jumlah iterasi. Tanpa -test-iterations, maksimumnya 3. Kami mengeceknya dengan test yang gagal sekali lalu lulus. Run itu berakhir dengan TEST SUCCEEDED. Ringkasannya melaporkan 3 run test untuk 2 test.
Jadi retry membuat test yang flaky jadi hijau, dan masalahnya tersembunyi. Baca result bundle di setiap run, dan catat test mana yang butuh retry. Tambahkan -test-repetition-relaunch-enabled YES untuk menjalankan setiap percobaan di proses baru. Untuk memburu test yang flaky, pakai -run-tests-until-failure. Opsi itu tidak bisa digabung dengan -retry-tests-on-failure.
7. Beri batas untuk test yang macet
-test-timeouts-enabled YES \ -default-test-execution-time-allowance 120 \ -maximum-test-execution-time-allowance 300
Tambahkan flag ini ke perintah test. UI test yang menunggu sebuah elemen tanpa akhir akan gagal setelah jatah waktunya habis, dan tidak menahan mesin sampai timeout CI.
8. Jaga simulator tetap bersih di antara run
xcrun simctl shutdown "$UDID" xcrun simctl erase "$UDID" xcrun simctl delete unavailable
Erase mengembalikan isi dan pengaturan simulator ke awal. Delete unavailable menghapus perangkat yang tidak lagi didukung Xcode saat ini. Jalankan setelah setiap upgrade Xcode.
Bagaimana ini bertahan setelah reboot
Simulator yang Anda buat tetap ada setelah reboot. Yang harus kembali adalah agen CI yang menjalankan test. Jalankan agen sebagai LaunchAgent, nyalakan login otomatis, dan nyalakan simulator CI di awal setiap job dengan bootstatus -b. Perintah itu aman dijalankan di perangkat yang sudah menyala.
Error yang kami temui dan cara memperbaikinya
Unable to find a device matching the provided destination specifier. Nama atau OS-nya tidak ada. Cekxcrun simctl list devicesdan daftar runtime.xcodebuild: error: Existing file at -resultBundlePath. Hapus bundle lama sebelum setiap run.Unable to erase contents and settings in current state: Booted. Matikan simulator sebelum menghapus isinya.
Mengapa Mac khusus membantu
UI test menghabiskan banyak waktu sebelum ketukan pertama. Test menunggu build, simulator menyala, dan aplikasi terpasang. Mesin yang tetap menyala menjaga DerivedData dan simulator yang sudah menyala tetap siap. Dalam benchmark kami, kami mem-build aplikasi iOS Wikipedia dengan Xcode 26.6. Job setelah perubahan kecil butuh 27 detik di M6 yang hangat. Di runner macos-26 baru yang di-hosting GitHub, butuh 269 detik. M6 kami punya 12 core CPU, jadi dua klon paralel masih menyisakan ruang untuk build.
Kapan CI hosted sudah cukup
Test suite kecil yang berjalan di beberapa pull request sehari cocok dengan tagihan per menit. Dengan tarif GitHub $0.062 per menit macOS (dicek September 2026), Mac seharga $139 balik modal setelah sekitar 2,242 menit sebulan. Di bawah itu, tetaplah di hosted. Jika Anda harus menguji di banyak iPhone fisik, Anda butuh device cloud. Mac mini menjalankan simulator. Apakah test suite paralel Anda butuh memori lebih dari 16 GB? Tier M5 Pro kami punya 48 atau 64 GB, tapi statusnya pre-order, jadi perkirakan waktu tunggu sekitar satu minggu.
Panduan pipeline CI/CD iOS menunjukkan posisi UI test di pipeline lengkap. Kalkulator menghitung angka Anda sendiri.
Pertanyaan yang sering diajukan
Apakah xcodebuild punya flag untuk mengulang test yang gagal?
+
Ya. -retry-tests-on-failure mengulang test yang gagal, sampai sebanyak -test-iterations atau 3 kali secara default. Flag ini tidak bisa digabung dengan -run-tests-until-failure.
Bagaimana cara menjalankan test XCUITest secara paralel dari command line?
+
Tambahkan -parallel-testing-enabled YES, dan jika perlu -parallel-testing-worker-count. Xcode mengklon simulator dan membagi class test ke klon-klon itu.
Bisakah XCUITest berjalan di Mac headless?
+
XCUITest butuh sesi pengguna yang sedang login, karena UI test mengendalikan aplikasi Simulator. Jalankan agen CI sebagai LaunchAgent dan nyalakan login otomatis.
Bagaimana cara membaca hasil test xcodebuild tanpa membuka Xcode?
+
Tambahkan -resultBundlePath, lalu jalankan xcrun xcresulttool get test-results summary --path pada bundle itu. Perintah ini mencetak jumlahnya sebagai JSON.