Mobile勉強会 Wantedly × チームラボ #5 https://wantedly.connpass.com/event/244011/
CIでAndroidUIテストの様⼦を録画してみた.PCJMFษڧձ8BOUFEMZºνʔϜϥϘNLFFEB
View Slide
About me• mkeeda (向井⽥ ⼀平)• Twitter: @mr_mkeeda• Github: @mkeeda• Android Engineer at Cybozu, Inc2
UIテストあるある• 不安定🤮• CIでだけUIテストが落ちる🤮• エラーがよくわからない🤮• エラーが毎回違う🤮3
UIテストの様⼦を録画してみた• CIのテスト実⾏時は Androidエミュレータの画⾯が⾒れない• UIテストが落ちるときの画⾯の状態を知りたい• Firebase test labは使ってない• テスト失敗時にGithub Actionsの アーティファクトに録画データを残す4
Androidデバイス画⾯収録の基本• 録画開始• adb shell screenrecord <ファイルパス>• ※ファイルパスはAndroidデバイスのパス• 録画停⽌• Ctrl + C (mac は Command + C)• 録画ファイル取得• adb pull <録画ファイルのパス> <ローカルデバイスの保存先パス>5
テストケースごとに録画してみる• ScreenRecordRule を作る• “record_<テストメソッド名>.mp4” というファイル名で保存する6@RunWith(AndroidJUnit4::class)class SampleUiTest {@get:Rulevar activityRule: ActivityScenarioRule =ActivityScenarioRule(LoginActivity::class.java)@get:Rulevar screenRecordRule = ScreenRecordRule()@Testfun test() {// run test}}
テストコードから adb コマンドを実⾏• UiAutomation.executeShellCommand(String command) を使う• ローカルマシンで adb shell をするのと同等7val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation// ը։࢝uiAutomation.executeShellCommand("screenrecord /sdcard/record.mp4")// ըऴྃ (Ctrl + C) ϓϩηεID͕ඞཁuiAutomation.executeShellCommand("kill -SIGINT $screenRecordProcessId")
screenrecord のプロセスを得る8fun executeCommand(cmd: String): String {val parcelFileDescriptor = uiAutomation.executeShellCommand(cmd)return ParcelFileDescriptor.AutoCloseInputStream(parcelFileDescriptor).use { inputStream ->inputStream.readBytes().toString(Charset.defaultCharset())}}fun findProcessIds(processName: String): List {return executeCommand(cmd = "pidof $processName").trim().split(Regex("\\s+")).filter { it.isNotEmpty() }.map {it.toInt()}}val screenRecordProcessIds = findProcessIds(processName = "screenrecord")
ScreenRecordRule9class ScreenRecordRule : TestWatcher() {private val shell = Shell()private var screenRecordProcessIds: List = emptyList()override fun starting(description: Description) {shell.executeCommand(cmd = "screenrecord /sdcard/record_${description.methodName}.mp4", awaitOutput =false)screenRecordProcessIds = shell.findProcessIds(processName = "screenrecord")}override fun finished(description: Description) {// গ͠Ԇ͔ͤͯ͞Βऴྃͤ͞ͳ͍ͱ࠷ޙ·ͰըͰ͖ͳ͍ͱ͖͕͋ͬͨͷͰํͳ͘Thread.sleep(5000)// ͯ͢ͷscreenrecordϓϩηεΛࢭΊΔ// 1σόΠεͰ࣮ߦ͍ͯ͠ΕଞͷςετέʔεͷըΛࢭΊͯ͠·͏Մೳੑ͍ͣscreenRecordProcessIds.forEach { pid ->shell.executeCommand(cmd = "kill -SIGINT $pid", awaitOutput = false)}}}
Github Actionsでの録画データの回収• テストが失敗したときだけ録画をCIのアーティファクトとして保存する10- name: Run android testsuses: reactivecircus/android-emulator-runner@v2with:api-level: 31arch: x86_64disable-animations: truescript: |./gradlew connectedDebugAndroidTest || (mkdir screen_record; adb shell 'lssdcard/record_*.mp4' | tr -d '\r' | xargs -I% adb pull % ./screen_record && exit 1)- name: Save screen recordif: failure()uses: actions/upload-artifact@v2with:name: screen-recordspath: ./screen_record
• adbコマンドを使えばAndroidTestの実⾏の様⼦を 録画できる• 実⾏時の様⼦を知って テスト失敗時の原因追求ができる• 乱⽤するとテスト時間の増⼤に繋がりそう• ScreenRecordRuleのソースコード https://gist.github.com/mkeeda/30b8cfdcec53859a2cd39cac36d7fc9e11まとめTo Be Continued
参考• Android Debug Bridge (adb) | Android Developers https://developer.android.com/studio/command-line/adb• UiAutomation | Android Developers https://developer.android.com/reference/android/app/UiAutomation#executeShellCommand(java.lang.String)• androidx.benchmark.Shell https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt12