waveさんの技術日誌

wave1008の日記の新館です。

ShiratesにおけるAndroid/iOSのテストのジョブスクリプト作成

shirates-samples-job1

github.com

Android/iOSのテストを実行するためのスクリプトの例です。(Mac用のみ)

スクリプトファイル

  • runtest-all.sh
  • runtest-android.sh
  • runtest-ios.sh

事前準備

https://github.com/ldi-github/shirates-core/blob/main/doc/markdown/quick-start.md

実行対象のテストの指定

build.gradle.ktstasks.testを以下のように実装します。

tasks.test {
    useJUnitPlatform()
    jvmArgs = listOf(
        "--add-exports", "java.desktop/sun.awt.image=ALL-UNNAMED"
    )

    // Filter test methods
    val envIncludeTestMatching = System.getenv("includeTestsMatching") ?: "*"
    val list = envIncludeTestMatching.split(",").map { it.trim() }
    filter {
        for (item in list) {
            println("includeTestMatching($item)")
            includeTestsMatching(item)
        }
    }
}

環境変数includeTestsMatchingで実行したいテストを指定できます。

特定のテストを指定

export includeTestsMatching="product1.Test1,product1.Test2,product1.Test3"

全てのテストを指定

export includeTestsMatching="*"

runtest-android.sh

export SR_os="android"
export SR_testrunFile="testConfig/testrun.properties"
export SR_configFile="testConfig/androidSettingsConfig.json"
export SR_profile="Pixel 3a API 31(Android 12)"
export includeTestsMatching="product1.Test1"
SCRIPT_DIR=$(cd $(dirname $0); pwd)
cd $SCRIPT_DIR
echo "/// Starting test"
sh ./gradlew cleanTest test -x compileKotlin --info

runtest-ios.sh

export SR_os="ios"
export SR_testrunFile="testConfig/testrun.properties"
export SR_configFile="testConfig/iOSSettingsConfig.json"
export SR_profile="iPhone 14(16.2)"
export includeTestsMatching="product1.Test1"
SCRIPT_DIR=$(cd $(dirname $0); pwd)
cd $SCRIPT_DIR
echo "/// Starting test"
sh ./gradlew cleanTest test -x compileKotlin --info

SR_ prefix

SR_ プレフィックスの付いた環境変数はShiratesのパラメーターとしてルーティングされます。 こちらも参照 running_with_gradle

runtest-all.sh

sh ./runtest-android.sh
sh ./runtest-ios.sh

全てのテストを実行する手順

  1. このプロジェクトをIntelliJ IDEAで開く
  2. runtest-all.shを右クリックしてRunを選択する

以下のように実行されます。

wave1008@SNB-M1 shirates-samples-job1 % /bin/zsh /Users/wave1008/github/wave1008/shirates-samples-job1/runtest-all.sh
/// Starting test
Initialized native services in: /Users/wave1008/.gradle/native
Initialized jansi services in: /Users/wave1008/.gradle/native
Found daemon DaemonInfo{pid=13483, address=[7fab0fd4-dbb5-43cd-b6d4-1dfab7cf4b54 port:59314, addresses:[/127.0.0.1]], state=Idle, lastBusy=1679146708316, context=DefaultDaemonContext[uid=53663b64-db53-4db7-9a12-073828b9e9bf,javaHome=/Users/wave1008/Library/Java/JavaVirtualMachines/azul-17.0.3/Contents/Home,daemonRegistryDir=/Users/wave1008/.gradle/daemon,pid=13483,idleTimeout=10800000,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.base/java.nio.charset=ALL-UNNAMED,--add-opens,java.base/java.net=ALL-UNNAMED,--add-opens,java.base/java.util.concurrent.atomic=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]} however its context does not match the desired criteria.
Java home is different.
Wanted: DefaultDaemonContext[uid=null,javaHome=/Users/wave1008/Library/Java/JavaVirtualMachines/corretto-17.0.4/Contents/Home,daemonRegistryDir=/Users/wave1008/.gradle/daemon,pid=13510,idleTimeout=null,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.base/java.nio.charset=ALL-UNNAMED,--add-opens,java.base/java.net=ALL-UNNAMED,--add-opens,java.base/java.util.concurrent.atomic=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]
Actual: DefaultDaemonContext[uid=53663b64-db53-4db7-9a12-073828b9e9bf,javaHome=/Users/wave1008/Library/Java/JavaVirtualMachines/azul-17.0.3/Contents/Home,daemonRegistryDir=/Users/wave1008/.gradle/daemon,pid=13483,idleTimeout=10800000,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.base/java.nio.charset=ALL-UNNAMED,--add-opens,java.base/java.net=ALL-UNNAMED,--add-opens,java.base/java.util.concurrent.atomic=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]

  Looking for a different daemon...
The client will now receive all logging from the daemon (pid: 12653). The daemon log file: /Users/wave1008/.gradle/daemon/7.4.2/daemon-12653.out.log
Starting 4th build in daemon [uptime: 9 mins 36.423 secs, performance: 100%, non-heap usage: 39% of 256 MiB]
Using 10 worker leases.
Now considering [/Users/wave1008/github/wave1008/shirates-samples-job1, /Users/wave1008/Downloads/shirates-samples-job1] as hierarchies to watch
Watching the file system is configured to be enabled if available
File system watching is active
Invalidating in-memory cache of /Users/wave1008/github/wave1008/shirates-samples-job1/.gradle/7.4.2/fileHashes/fileHashes.bin
Invalidating in-memory cache of /Users/wave1008/github/wave1008/shirates-samples-job1/.gradle/7.4.2/fileHashes/resourceHashesCache.bin
Starting Build
Settings evaluated using settings file '/Users/wave1008/github/wave1008/shirates-samples-job1/settings.gradle.kts'.
Projects loaded. Root project using build file '/Users/wave1008/github/wave1008/shirates-samples-job1/build.gradle.kts'.
Included projects: [root project 'shirates-samples-job1']

> Configure project :
Evaluating root project 'shirates-samples-job1' using build file '/Users/wave1008/github/wave1008/shirates-samples-job1/build.gradle.kts'.
Invalidating in-memory cache of /Users/wave1008/github/wave1008/shirates-samples-job1/.gradle/buildOutputCleanup/outputFiles.bin
Invalidating in-memory cache of /Users/wave1008/.gradle/caches/journal-1/file-access.bin
Invalidating in-memory cache of /Users/wave1008/.gradle/caches/7.4.2/fileHashes/fileHashes.bin
Invalidating in-memory cache of /Users/wave1008/.gradle/caches/7.4.2/fileHashes/resourceHashesCache.bin
Using Kotlin Gradle Plugin gradle71 variant
kotlin scripting plugin: created the scripting discovery configuration: kotlinScriptDef
kotlin scripting plugin: created the scripting discovery configuration: testKotlinScriptDef
Invalidating in-memory cache of /Users/wave1008/.gradle/caches/7.4.2/kotlin-dsl/executionHistory.bin
Caching disabled for Kotlin DSL accessors for root project 'shirates-samples-job1' because:
  Build cache is disabled
Skipping Kotlin DSL accessors for root project 'shirates-samples-job1' as it is up-to-date.
All projects evaluated.
includeTestMatching(product1.Test1)
Selected primary task 'cleanTest' from project :
Selected primary task 'test' from project :
Tasks to be executed: [task ':cleanTest', task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':inspectClassesForKotlinIC', task ':compileTestKotlin', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test']
Tasks that were excluded: [task ':compileKotlin']
:cleanTest (Thread[Execution worker for ':',5,main]) started.
Invalidating in-memory cache of /Users/wave1008/github/wave1008/shirates-samples-job1/.gradle/7.4.2/executionHistory/executionHistory.bin

> Task :cleanTest
Caching disabled for task ':cleanTest' because:
  Build cache is disabled
Task ':cleanTest' is not up-to-date because:
  Task has not declared any outputs despite executing actions.
:cleanTest (Thread[Execution worker for ':',5,main]) completed. Took 0.006 secs.
destroyer locations for task group 0 (Thread[Execution worker for ':',5,main]) started.
destroyer locations for task group 0 (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:compileJava (Thread[Execution worker for ':',5,main]) started.

> Task :compileJava NO-SOURCE
Skipping task ':compileJava' as it has no source files and no previous output files.
:compileJava (Thread[Execution worker for ':',5,main]) completed. Took 0.001 secs.
:processResources (Thread[Execution worker for ':',5,main]) started.

> Task :processResources NO-SOURCE
Skipping task ':processResources' as it has no source files and no previous output files.
:processResources (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:classes (Thread[Execution worker for ':',5,main]) started.

> Task :classes UP-TO-DATE
Skipping task ':classes' as it has no actions.
:classes (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:jar (Thread[Execution worker for ':',5,main]) started.

> Task :jar UP-TO-DATE
Caching disabled for task ':jar' because:
  Build cache is disabled
Skipping task ':jar' as it is up-to-date.
:jar (Thread[Execution worker for ':',5,main]) completed. Took 0.001 secs.
:inspectClassesForKotlinIC (Thread[Execution worker for ':',5,main]) started.

> Task :inspectClassesForKotlinIC UP-TO-DATE
Caching disabled for task ':inspectClassesForKotlinIC' because:
  Build cache is disabled
Skipping task ':inspectClassesForKotlinIC' as it is up-to-date.
:inspectClassesForKotlinIC (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:compileTestKotlin (Thread[Execution worker for ':',5,main]) started.

> Task :compileTestKotlin UP-TO-DATE
Caching disabled for task ':compileTestKotlin' because:
  Build cache is disabled
Skipping task ':compileTestKotlin' as it is up-to-date.
:compileTestKotlin (Thread[Execution worker for ':',5,main]) completed. Took 0.041 secs.
:compileTestJava (Thread[Execution worker for ':',5,main]) started.

> Task :compileTestJava NO-SOURCE
Skipping task ':compileTestJava' as it has no source files and no previous output files.
:compileTestJava (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:processTestResources (Thread[Execution worker for ':',5,main]) started.

> Task :processTestResources NO-SOURCE
Skipping task ':processTestResources' as it has no source files and no previous output files.
:processTestResources (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:testClasses (Thread[Execution worker for ':',5,main]) started.

> Task :testClasses UP-TO-DATE
Skipping task ':testClasses' as it has no actions.
:testClasses (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:test (Thread[Execution worker for ':',5,main]) started.
Gradle Test Executor 4 started executing tests.

> Task :test
Caching disabled for task ':test' because:
  Build cache is disabled
Task ':test' is not up-to-date because:
  Output property 'binaryResultsDirectory' file /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test/binary has been removed.
  Output property 'binaryResultsDirectory' file /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test/binary/output.bin has been removed.
  Output property 'binaryResultsDirectory' file /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test/binary/output.bin.idx has been removed.
file or directory '/Users/wave1008/github/wave1008/shirates-samples-job1/build/classes/java/test', not found
Starting process 'Gradle Test Executor 4'. Working directory: /Users/wave1008/github/wave1008/shirates-samples-job1 Command: /Users/wave1008/Library/Java/JavaVirtualMachines/corretto-17.0.4/Contents/Home/bin/java -Dorg.gradle.internal.worker.tmpdir=/Users/wave1008/github/wave1008/shirates-samples-job1/build/tmp/test/work -Dorg.gradle.native=false --add-exports java.desktop/sun.awt.image=ALL-UNNAMED @/Users/wave1008/.gradle/.tmp/gradle-worker-classpath8117995844171373059txt -Xmx512m -Dfile.encoding=UTF-8 -Duser.country=JP -Duser.language=ja -Duser.variant -ea worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 4'
Successfully started process 'Gradle Test Executor 4'

Test1 STANDARD_OUT
    Importing environment variable. SR_testrunFile=testConfig/testrun.properties
    lineNo      logDateTime     testCaseId      logType group   message
    1   2023/03/18 22:38:34.564 {}      [info]  ()      Importing environment variables.
    2   2023/03/18 22:38:34.722 {}      [info]  ()      SR_profile=Pixel 3a API 31(Android 12)
    3   2023/03/18 22:38:34.722 {}      [info]  ()      SR_configFile=testConfig/androidSettingsConfig.json
    4   2023/03/18 22:38:34.723 {}      [info]  ()      SR_os=android
    5   2023/03/18 22:38:34.723 {}      [info]  ()      SR_testrunFile=testConfig/testrun.properties
    lineNo      logDateTime     testCaseId      logType group   message
    1   2023/03/18 22:38:34.723 {}      [-]     ()      ----------------------------------------------------------------------------------------------------
    2   2023/03/18 22:38:34.724 {}      [-]     ()      ///
    3   2023/03/18 22:38:34.724 {}      [-]     ()      /// Shirates 3.1.1
    4   2023/03/18 22:38:34.724 {}      [-]     ()      ///
    5   2023/03/18 22:38:34.725 {}      [-]     ()      powered by Appium (io.appium:java-client:8.1.1)
    6   2023/03/18 22:38:34.725 {}      [-]     ()      ----------------------------------------------------------------------------------------------------
    7   2023/03/18 22:38:34.725 {}      [-]     (parameter)     testClass: product1.Test1
    8   2023/03/18 22:38:34.726 {}      [-]     (parameter)     sheetName: Test1
    9   2023/03/18 22:38:34.726 {}      [-]     (parameter)     logLanguage: 

Test1 > test1() STANDARD_OUT
    10  2023/03/18 22:38:34.733 {}      [info]  ()      ----------------------------------------------------------------------------------------------------
    11  2023/03/18 22:38:34.734 {}      [info]  ()      Test function: test1 [test1()]
    Importing environment variable. SR_testrunFile=testConfig/testrun.properties
    12  2023/03/18 22:38:34.734 {}      [info]  ()      Importing environment variables.
    13  2023/03/18 22:38:34.734 {}      [info]  ()      SR_profile=Pixel 3a API 31(Android 12)
    14  2023/03/18 22:38:34.734 {}      [info]  ()      SR_configFile=testConfig/androidSettingsConfig.json
    15  2023/03/18 22:38:34.735 {}      [info]  ()      SR_os=android
    16  2023/03/18 22:38:34.735 {}      [info]  ()      SR_testrunFile=testConfig/testrun.properties
    17  2023/03/18 22:38:35.368 {}      [info]  ()      Initializing with testrun file.(testConfig/testrun.properties)
    18  2023/03/18 22:38:35.423 {}      [info]  ()      Logging to file:////Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1/
    Copying jar content _ReportStyle.css to /Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1
    19  2023/03/18 22:38:35.452 {}      [info]  ()      Loading config.(configFile=/Users/wave1008/github/wave1008/shirates-samples-job1/testConfig/androidSettingsConfig.json, profileName=Pixel 3a API 31(Android 12))
    20  2023/03/18 22:38:35.480 {}      [info]  ()      Loading screen files.(directory=/Users/wave1008/github/wave1008/shirates-samples-job1/testConfig/screens)
    21  2023/03/18 22:38:35.490 {}      [info]  ()      Screen files loaded.(2 files)
    22  2023/03/18 22:38:35.506 {}      [info]  ()      Scanning macro under '/Users/wave1008/github/wave1008/shirates-samples-job1/src/test/kotlin'
    23  2023/03/18 22:38:35.507 {}      [info]  ()      Initializing TestDriver.(profileName=Pixel 3a API 31(Android 12))
    24  2023/03/18 22:38:35.508 {}      [info]  ()      noLoadRun: false
    25  2023/03/18 22:38:35.508 {}      [info]  ()      boundsToRectRatio: 1
    26  2023/03/18 22:38:35.508 {}      [info]  ()      reuseDriver: true
    27  2023/03/18 22:38:35.509 {}      [info]  ()      autoScreenshot: true
    28  2023/03/18 22:38:35.509 {}      [info]  ()      onChangedOnly: true
    29  2023/03/18 22:38:35.509 {}      [info]  ()      onCondition: true
    30  2023/03/18 22:38:35.510 {}      [info]  ()      onAction: true
    31  2023/03/18 22:38:35.510 {}      [info]  ()      onExpectation: true
    32  2023/03/18 22:38:35.510 {}      [info]  ()      onExecOperateCommand: true
    33  2023/03/18 22:38:35.511 {}      [info]  ()      onCheckCommand: true
    34  2023/03/18 22:38:35.511 {}      [info]  ()      onScrolling: true
    35  2023/03/18 22:38:35.511 {}      [info]  ()      manualScreenshot: true
    36  2023/03/18 22:38:35.511 {}      [info]  ()      retryMaxCount: 1
    37  2023/03/18 22:38:35.512 {}      [info]  ()      retryIntervalSeconds: 2.0
    38  2023/03/18 22:38:35.512 {}      [info]  ()      shortWaitSeconds: 1.5
    39  2023/03/18 22:38:35.512 {}      [info]  ()      waitSecondsOnIsScreen: 15.0
    40  2023/03/18 22:38:35.513 {}      [info]  ()      waitSecondsForLaunchAppComplete: 15.0
    41  2023/03/18 22:38:35.513 {}      [info]  ()      waitSecondsForAnimationComplete: 0.5
    42  2023/03/18 22:38:35.513 {}      [info]  ()      waitSecondsForConnectionEnabled: 8.0
    43  2023/03/18 22:38:35.514 {}      [info]  ()      swipeDurationSeconds: 3.0
    44  2023/03/18 22:38:35.514 {}      [info]  ()      flickDurationSeconds: 0.3
    45  2023/03/18 22:38:35.514 {}      [info]  ()      swipeMarginRatio: 0.1
    46  2023/03/18 22:38:35.514 {}      [info]  ()      scrollVerticalMarginRatio: 0.2
    47  2023/03/18 22:38:35.515 {}      [info]  ()      scrollHorizontalMarginRatio: 0.2
    48  2023/03/18 22:38:35.515 {}      [info]  ()      tapHoldSeconds: 0.2
    49  2023/03/18 22:38:35.515 {}      [info]  ()      tapAppIconMethod: auto
    50  2023/03/18 22:38:35.515 {}      [info]  ()      tapAppIconMacro: 
    51  2023/03/18 22:38:35.516 {}      [info]  ()      syncWaitSeconds: 1.8
    52  2023/03/18 22:38:35.516 {}      [info]  ()      Searching device for the profile. (profileName=Pixel 3a API 31(Android 12))
    53  2023/03/18 22:38:35.601 {}      [info]  ()      Connected device found. (Pixel_3a_API_31_Android_12_:5554, Android 12, emulator-5554)
    54  2023/03/18 22:38:35.646 {}      [info]  ()      appium --session-override --relaxed-security --log /Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1/appium_2023-03-18_223835605.log --port 4720
    55  2023/03/18 22:38:38.730 {}      [info]  ()      Appium Server started. (pid=13530, port=4720)
    56  2023/03/18 22:38:39.736 {}      [info]  ()      Connecting to Appium Server.(http://127.0.0.1:4720/)

Test1 > test1() STANDARD_ERROR
    Cleaning up unclosed ZipFile for archive /Users/wave1008/Downloads/TestResults/androidSettingsConfig/TestList_androidSettingsConfig.xlsx

Test1 > test1() STANDARD_OUT
    57  2023/03/18 22:38:45.337 {}      [info]  ()      [Health check] start
    58  2023/03/18 22:38:45.340 {}      [info]  (syncCache)     Syncing (1)
    59  2023/03/18 22:38:46.199 {}      [info]  (syncCache)     elapsed=0.858, syncWaitSeconds=15.0
    60  2023/03/18 22:38:46.701 {}      [info]  (syncCache)     Syncing (2)
    61  2023/03/18 22:38:46.807 {}      [info]  (syncCache)     Synced. (elapsed=1.466, currentScreen=?)
    62  2023/03/18 22:38:46.813 {}      [info]  ()      tap<.label>
    63  2023/03/18 22:38:48.240 {}      [info]  (syncCache)     Syncing (1)
    64  2023/03/18 22:38:48.323 {}      [info]  (syncCache)     Synced. (elapsed=0.083, currentScreen=?)
    65  2023/03/18 22:38:48.496 {}      [info]  ()      [Health check] end
    66  2023/03/18 22:38:48.501 {}      [info]  ()      implicitlyWaitSeconds: 5.0
    67  2023/03/18 22:38:48.574 {}      [info]  ()      (settings) always_finish_activities: 0
    68  2023/03/18 22:38:48.598 {}      [info]  ()      Searching device for the profile. (profileName=Pixel 3a API 31(Android 12))
    69  2023/03/18 22:38:48.678 {}      [info]  ()      Connected device found. (Pixel_3a_API_31_Android_12_:5554, Android 12, emulator-5554)
    70  2023/03/18 22:38:48.679 {}      [info]  ()      AppiumDriver initialized.
    71  2023/03/18 22:38:48.679 {}      [-]     (parameter)     testrun: testConfig/testrun.properties
    72  2023/03/18 22:38:48.680 {}      [-]     (parameter)     testConfigName: androidSettingsConfig(/Users/wave1008/github/wave1008/shirates-samples-job1/testConfig/androidSettingsConfig.json)
    73  2023/03/18 22:38:48.680 {}      [-]     (parameter)     profileName: Pixel 3a API 31(Android 12)
    74  2023/03/18 22:38:48.680 {}      [-]     (parameter)     appIconName: Settings
    75  2023/03/18 22:38:48.681 {}      [-]     ()      (capabilities)
    76  2023/03/18 22:38:48.681 {}      [-]     (parameter)     appium:newCommandTimeout: 300
    77  2023/03/18 22:38:48.681 {}      [-]     (parameter)     appium:takesScreenshot: true
    78  2023/03/18 22:38:48.682 {}      [-]     (parameter)     appium:warnings: {}
    79  2023/03/18 22:38:48.682 {}      [-]     (parameter)     appium:deviceApiLevel: 31
    80  2023/03/18 22:38:48.682 {}      [-]     (parameter)     appium:automationName: UiAutomator2
    81  2023/03/18 22:38:48.683 {}      [-]     (parameter)     appium:locationContextEnabled: false
    82  2023/03/18 22:38:48.683 {}      [-]     (parameter)     appium:deviceScreenSize: 1080x2220
    83  2023/03/18 22:38:48.683 {}      [-]     (parameter)     appium:deviceManufacturer: Google
    84  2023/03/18 22:38:48.683 {}      [-]     (parameter)     appium:udid: emulator-5554
    85  2023/03/18 22:38:48.684 {}      [-]     (parameter)     appium:pixelRatio: 2.75
    86  2023/03/18 22:38:48.684 {}      [-]     (parameter)     platformName: ANDROID
    87  2023/03/18 22:38:48.684 {}      [-]     (parameter)     appium:networkConnectionEnabled: true
    88  2023/03/18 22:38:48.684 {}      [-]     (parameter)     appium:locale: US
    89  2023/03/18 22:38:48.685 {}      [-]     (parameter)     appium:deviceScreenDensity: 440
    90  2023/03/18 22:38:48.685 {}      [-]     (parameter)     appium:viewportRect: {left=0, top=66, width=1080, height=2022}
    91  2023/03/18 22:38:48.685 {}      [-]     (parameter)     appium:language: en
    92  2023/03/18 22:38:48.685 {}      [-]     (parameter)     appium:avd: Pixel_3a_API_31_Android_12_
    93  2023/03/18 22:38:48.686 {}      [-]     (parameter)     appium:deviceModel: sdk_gphone64_arm64
    94  2023/03/18 22:38:48.686 {}      [-]     (parameter)     appium:platformVersion: 12
    95  2023/03/18 22:38:48.686 {}      [-]     (parameter)     appium:databaseEnabled: false
    96  2023/03/18 22:38:48.686 {}      [-]     (parameter)     appium:deviceUDID: emulator-5554
    97  2023/03/18 22:38:48.687 {}      [-]     (parameter)     appium:statBarHeight: 66
    98  2023/03/18 22:38:48.687 {}      [-]     (parameter)     appium:webStorageEnabled: false
    99  2023/03/18 22:38:48.687 {}      [-]     (parameter)     appium:appActivity: com.android.settings.Settings
    100 2023/03/18 22:38:48.687 {}      [-]     (parameter)     appium:deviceName: emulator-5554
    101 2023/03/18 22:38:48.687 {}      [-]     (parameter)     appium:javascriptEnabled: true
    102 2023/03/18 22:38:48.688 {}      [-]     (parameter)     appium:appPackage: com.android.settings
    103 2023/03/18 22:38:48.688 {}      [-]     ()      settings
    104 2023/03/18 22:38:48.765 {}      [-]     (parameter)     always_finish_activities: 0
    105 2023/03/18 22:38:48.765 {}      [-]     ()      (others)
    106 2023/03/18 22:38:48.766 {}      [-]     (parameter)     isEmulator: true
    107 2023/03/18 22:38:48.766 {}      [-]     (parameter)     hasOsaihuKeitai: false
    108 2023/03/18 22:38:48.767 {}      [info]  ()      Setup executed. (duration: 14.0 sec)
    109 2023/03/18 22:38:48.770 {}      [info]  ()      Running scenario ..................................................
    110 2023/03/18 22:38:48.771 {}      [info]  ()      Startup package: com.android.settings
    111 2023/03/18 22:38:48.771 {test1} [SCENARIO]      (scenario)      test1()
    112 2023/03/18 22:38:49.245 {test1} [screenshot]    (screenshot)    screenshot
    113 2023/03/18 22:38:49.246 {test1} [operate]       (launchApp)     Launch app <Settings>
    114 2023/03/18 22:38:49.435 {test1} [info]  (syncCache)     Syncing (1)
    115 2023/03/18 22:38:50.091 {test1} [info]  (syncCache)     Synced. (elapsed=0.656, currentScreen=?)
    116 2023/03/18 22:38:50.093 {test1-1}       [CASE]  (case)  (1)
    117 2023/03/18 22:38:50.094 {test1-1}       [CONDITION]     (condition)     condition
    118 2023/03/18 22:38:50.094 {test1-1}       [info]  ()      Pixel 3a API 31(Android 12)
    119 2023/03/18 22:38:50.095 {test1-1}       [ACTION]        (action)        action
    120 2023/03/18 22:38:50.095 {test1-1}       [operate]       (pressHome)     Press OS home
    121 2023/03/18 22:38:50.131 {test1-1}       [info]  (syncCache)     Syncing (1)
    122 2023/03/18 22:38:51.119 {test1-1}       [info]  (syncCache)     elapsed=0.988, syncWaitSeconds=1.8
    123 2023/03/18 22:38:51.621 {test1-1}       [info]  (syncCache)     Syncing (2)
    124 2023/03/18 22:38:51.662 {test1-1}       [info]  (syncCache)     Synced. (elapsed=1.531, currentScreen=[Android Home Screen])
    125 2023/03/18 22:38:53.572 {test1-1}       [screenshot]    (screenshot)    screenshot
    126 2023/03/18 22:38:53.573 {test1-1}       [EXPECTATION]   (expectation)   expectation
    127 2023/03/18 22:38:53.575 {test1-1}       [branch]        (os)    android {
    128 2023/03/18 22:38:53.578 {test1-1}       [OK]    (screenIs)      [Android Home Screen] is displayed
    129 2023/03/18 22:38:53.578 {test1-1}       [branch]        (os)    } android
    130 2023/03/18 22:38:53.579 {test1-1}       [info]  ()      Scenario executed. (duration: 4.8 sec)
    131 2023/03/18 22:38:53.580 {}      [info]  ()      Test function executed. (duration: 18.8 sec)
    132 2023/03/18 22:38:53.580 {}      [info]  ()      End of Test function: test1 [test1()]

Test1 STANDARD_OUT
    133 2023/03/18 22:38:53.583 {}      [info]  ()      Logging to file:////Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1/
    Copying jar content _ReportScript.js to /Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1
    Copying jar content _ReportStyle.css to /Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1
    Loading: /Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1/TestLog(commandList)_20230318223834.log
    Saved: /Users/wave1008/Downloads/TestResults/androidSettingsConfig/2023-03-18_223834/Test1/Test1@a.xlsx

    134 2023/03/18 22:38:53.949 {}      [info]  ()      Quitting TestDriver.
    135 2023/03/18 22:38:54.051 {}      [info]  ()      Test class executed. (duration: 19.5 sec)

Gradle Test Executor 4 finished executing tests.

> Task :test
Finished generating test XML results (0.001 secs) into: /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test
Generating HTML test report...
Finished generating test html results (0.002 secs) into: /Users/wave1008/github/wave1008/shirates-samples-job1/build/reports/tests/test
:test (Thread[Execution worker for ':',5,main]) completed. Took 20.204 secs.
producer locations for task group 1 (Thread[Execution worker for ':',5,main]) started.
producer locations for task group 1 (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.

BUILD SUCCESSFUL in 20s
5 actionable tasks: 2 executed, 3 up-to-date
Watched directory hierarchies: [/Users/wave1008/github/wave1008/shirates-samples-job1, /Users/wave1008/Downloads/shirates-samples-job1]
/// Starting test
Initialized native services in: /Users/wave1008/.gradle/native
Initialized jansi services in: /Users/wave1008/.gradle/native
Found daemon DaemonInfo{pid=13483, address=[7fab0fd4-dbb5-43cd-b6d4-1dfab7cf4b54 port:59314, addresses:[/127.0.0.1]], state=Idle, lastBusy=1679146708316, context=DefaultDaemonContext[uid=53663b64-db53-4db7-9a12-073828b9e9bf,javaHome=/Users/wave1008/Library/Java/JavaVirtualMachines/azul-17.0.3/Contents/Home,daemonRegistryDir=/Users/wave1008/.gradle/daemon,pid=13483,idleTimeout=10800000,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.base/java.nio.charset=ALL-UNNAMED,--add-opens,java.base/java.net=ALL-UNNAMED,--add-opens,java.base/java.util.concurrent.atomic=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]} however its context does not match the desired criteria.
Java home is different.
Wanted: DefaultDaemonContext[uid=null,javaHome=/Users/wave1008/Library/Java/JavaVirtualMachines/corretto-17.0.4/Contents/Home,daemonRegistryDir=/Users/wave1008/.gradle/daemon,pid=13641,idleTimeout=null,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.base/java.nio.charset=ALL-UNNAMED,--add-opens,java.base/java.net=ALL-UNNAMED,--add-opens,java.base/java.util.concurrent.atomic=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]
Actual: DefaultDaemonContext[uid=53663b64-db53-4db7-9a12-073828b9e9bf,javaHome=/Users/wave1008/Library/Java/JavaVirtualMachines/azul-17.0.3/Contents/Home,daemonRegistryDir=/Users/wave1008/.gradle/daemon,pid=13483,idleTimeout=10800000,priority=NORMAL,daemonOpts=--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.base/java.lang=ALL-UNNAMED,--add-opens,java.base/java.lang.invoke=ALL-UNNAMED,--add-opens,java.base/java.util=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.prefs/java.util.prefs=ALL-UNNAMED,--add-opens,java.base/java.nio.charset=ALL-UNNAMED,--add-opens,java.base/java.net=ALL-UNNAMED,--add-opens,java.base/java.util.concurrent.atomic=ALL-UNNAMED,-XX:MaxMetaspaceSize=256m,-XX:+HeapDumpOnOutOfMemoryError,-Xms256m,-Xmx512m,-Dfile.encoding=UTF-8,-Duser.country=JP,-Duser.language=ja,-Duser.variant]

  Looking for a different daemon...
The client will now receive all logging from the daemon (pid: 12653). The daemon log file: /Users/wave1008/.gradle/daemon/7.4.2/daemon-12653.out.log
Starting 5th build in daemon [uptime: 9 mins 57.527 secs, performance: 100%, non-heap usage: 39% of 256 MiB]
Using 10 worker leases.
Now considering [/Users/wave1008/github/wave1008/shirates-samples-job1, /Users/wave1008/Downloads/shirates-samples-job1] as hierarchies to watch
Watching the file system is configured to be enabled if available
File system watching is active
Starting Build
Settings evaluated using settings file '/Users/wave1008/github/wave1008/shirates-samples-job1/settings.gradle.kts'.
Projects loaded. Root project using build file '/Users/wave1008/github/wave1008/shirates-samples-job1/build.gradle.kts'.
Included projects: [root project 'shirates-samples-job1']

> Configure project :
Evaluating root project 'shirates-samples-job1' using build file '/Users/wave1008/github/wave1008/shirates-samples-job1/build.gradle.kts'.
Using Kotlin Gradle Plugin gradle71 variant
kotlin scripting plugin: created the scripting discovery configuration: kotlinScriptDef
kotlin scripting plugin: created the scripting discovery configuration: testKotlinScriptDef
Caching disabled for Kotlin DSL accessors for root project 'shirates-samples-job1' because:
  Build cache is disabled
Skipping Kotlin DSL accessors for root project 'shirates-samples-job1' as it is up-to-date.
All projects evaluated.
includeTestMatching(product1.Test1)
Selected primary task 'cleanTest' from project :
Selected primary task 'test' from project :
Tasks to be executed: [task ':cleanTest', task ':compileJava', task ':processResources', task ':classes', task ':jar', task ':inspectClassesForKotlinIC', task ':compileTestKotlin', task ':compileTestJava', task ':processTestResources', task ':testClasses', task ':test']
Tasks that were excluded: [task ':compileKotlin']
:cleanTest (Thread[Execution worker for ':',5,main]) started.

> Task :cleanTest
Caching disabled for task ':cleanTest' because:
  Build cache is disabled
Task ':cleanTest' is not up-to-date because:
  Task has not declared any outputs despite executing actions.
:cleanTest (Thread[Execution worker for ':',5,main]) completed. Took 0.006 secs.
destroyer locations for task group 0 (Thread[Execution worker for ':',5,main]) started.
destroyer locations for task group 0 (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:compileJava (Thread[Execution worker for ':',5,main]) started.

> Task :compileJava NO-SOURCE
Skipping task ':compileJava' as it has no source files and no previous output files.
:compileJava (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:processResources (Thread[Execution worker for ':',5,main]) started.

> Task :processResources NO-SOURCE
Skipping task ':processResources' as it has no source files and no previous output files.
:processResources (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:classes (Thread[Execution worker for ':',5,main]) started.

> Task :classes UP-TO-DATE
Skipping task ':classes' as it has no actions.
:classes (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:jar (Thread[Execution worker for ':',5,main]) started.

> Task :jar UP-TO-DATE
Caching disabled for task ':jar' because:
  Build cache is disabled
Skipping task ':jar' as it is up-to-date.
:jar (Thread[Execution worker for ':',5,main]) completed. Took 0.001 secs.
:inspectClassesForKotlinIC (Thread[Execution worker for ':',5,main]) started.

> Task :inspectClassesForKotlinIC UP-TO-DATE
Caching disabled for task ':inspectClassesForKotlinIC' because:
  Build cache is disabled
Skipping task ':inspectClassesForKotlinIC' as it is up-to-date.
:inspectClassesForKotlinIC (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:compileTestKotlin (Thread[Execution worker for ':',5,main]) started.

> Task :compileTestKotlin UP-TO-DATE
Caching disabled for task ':compileTestKotlin' because:
  Build cache is disabled
Skipping task ':compileTestKotlin' as it is up-to-date.
:compileTestKotlin (Thread[Execution worker for ':',5,main]) completed. Took 0.038 secs.
:compileTestJava (Thread[Execution worker for ':',5,main]) started.

> Task :compileTestJava NO-SOURCE
Skipping task ':compileTestJava' as it has no source files and no previous output files.
:compileTestJava (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:processTestResources (Thread[Execution worker for ':',5,main]) started.

> Task :processTestResources NO-SOURCE
Skipping task ':processTestResources' as it has no source files and no previous output files.
:processTestResources (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:testClasses (Thread[Execution worker for ':',5,main]) started.

> Task :testClasses UP-TO-DATE
Skipping task ':testClasses' as it has no actions.
:testClasses (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.
:test (Thread[Execution worker for ':',5,main]) started.
Gradle Test Executor 5 started executing tests.

> Task :test
Caching disabled for task ':test' because:
  Build cache is disabled
Task ':test' is not up-to-date because:
  Output property 'binaryResultsDirectory' file /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test/binary has been removed.
  Output property 'binaryResultsDirectory' file /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test/binary/output.bin has been removed.
  Output property 'binaryResultsDirectory' file /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test/binary/output.bin.idx has been removed.
file or directory '/Users/wave1008/github/wave1008/shirates-samples-job1/build/classes/java/test', not found
Starting process 'Gradle Test Executor 5'. Working directory: /Users/wave1008/github/wave1008/shirates-samples-job1 Command: /Users/wave1008/Library/Java/JavaVirtualMachines/corretto-17.0.4/Contents/Home/bin/java -Dorg.gradle.internal.worker.tmpdir=/Users/wave1008/github/wave1008/shirates-samples-job1/build/tmp/test/work -Dorg.gradle.native=false --add-exports java.desktop/sun.awt.image=ALL-UNNAMED @/Users/wave1008/.gradle/.tmp/gradle-worker-classpath11856233312441846091txt -Xmx512m -Dfile.encoding=UTF-8 -Duser.country=JP -Duser.language=ja -Duser.variant -ea worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 5'
Successfully started process 'Gradle Test Executor 5'

Test1 STANDARD_OUT
    Importing environment variable. SR_testrunFile=testConfig/testrun.properties
    lineNo      logDateTime     testCaseId      logType group   message
    1   2023/03/18 22:38:55.719 {}      [info]  ()      Importing environment variables.
    2   2023/03/18 22:38:55.876 {}      [info]  ()      SR_profile=iPhone 14(16.2)
    3   2023/03/18 22:38:55.876 {}      [info]  ()      SR_configFile=testConfig/iOSSettingsConfig.json
    4   2023/03/18 22:38:55.876 {}      [info]  ()      SR_os=ios
    5   2023/03/18 22:38:55.877 {}      [info]  ()      SR_testrunFile=testConfig/testrun.properties
    lineNo      logDateTime     testCaseId      logType group   message
    1   2023/03/18 22:38:55.877 {}      [-]     ()      ----------------------------------------------------------------------------------------------------
    2   2023/03/18 22:38:55.877 {}      [-]     ()      ///
    3   2023/03/18 22:38:55.877 {}      [-]     ()      /// Shirates 3.1.1
    4   2023/03/18 22:38:55.878 {}      [-]     ()      ///
    5   2023/03/18 22:38:55.878 {}      [-]     ()      powered by Appium (io.appium:java-client:8.1.1)
    6   2023/03/18 22:38:55.879 {}      [-]     ()      ----------------------------------------------------------------------------------------------------
    7   2023/03/18 22:38:55.879 {}      [-]     (parameter)     testClass: product1.Test1
    8   2023/03/18 22:38:55.879 {}      [-]     (parameter)     sheetName: Test1
    9   2023/03/18 22:38:55.879 {}      [-]     (parameter)     logLanguage: 

Test1 > test1() STANDARD_OUT
    10  2023/03/18 22:38:55.887 {}      [info]  ()      ----------------------------------------------------------------------------------------------------
    11  2023/03/18 22:38:55.888 {}      [info]  ()      Test function: test1 [test1()]
    Importing environment variable. SR_testrunFile=testConfig/testrun.properties
    12  2023/03/18 22:38:55.888 {}      [info]  ()      Importing environment variables.
    13  2023/03/18 22:38:55.889 {}      [info]  ()      SR_profile=iPhone 14(16.2)
    14  2023/03/18 22:38:55.889 {}      [info]  ()      SR_configFile=testConfig/iOSSettingsConfig.json
    15  2023/03/18 22:38:55.889 {}      [info]  ()      SR_os=ios
    16  2023/03/18 22:38:55.889 {}      [info]  ()      SR_testrunFile=testConfig/testrun.properties
    17  2023/03/18 22:38:56.381 {}      [info]  ()      Initializing with testrun file.(testConfig/testrun.properties)
    18  2023/03/18 22:38:56.429 {}      [info]  ()      Logging to file:////Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1/
    Copying jar content _ReportStyle.css to /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1
    19  2023/03/18 22:38:56.457 {}      [info]  ()      Loading config.(configFile=/Users/wave1008/github/wave1008/shirates-samples-job1/testConfig/iOSSettingsConfig.json, profileName=iPhone 14(16.2))
    20  2023/03/18 22:38:56.485 {}      [info]  ()      Loading screen files.(directory=/Users/wave1008/github/wave1008/shirates-samples-job1/testConfig/screens)
    21  2023/03/18 22:38:56.494 {}      [info]  ()      Screen files loaded.(2 files)
    22  2023/03/18 22:38:56.503 {}      [info]  ()      Scanning macro under '/Users/wave1008/github/wave1008/shirates-samples-job1/src/test/kotlin'
    23  2023/03/18 22:38:56.504 {}      [info]  ()      Initializing TestDriver.(profileName=iPhone 14(16.2))
    24  2023/03/18 22:38:56.505 {}      [info]  ()      noLoadRun: false
    25  2023/03/18 22:38:56.505 {}      [info]  ()      boundsToRectRatio: 3
    26  2023/03/18 22:38:56.506 {}      [info]  ()      reuseDriver: true
    27  2023/03/18 22:38:56.506 {}      [info]  ()      autoScreenshot: true
    28  2023/03/18 22:38:56.507 {}      [info]  ()      onChangedOnly: true
    29  2023/03/18 22:38:56.507 {}      [info]  ()      onCondition: true
    30  2023/03/18 22:38:56.507 {}      [info]  ()      onAction: true
    31  2023/03/18 22:38:56.508 {}      [info]  ()      onExpectation: true
    32  2023/03/18 22:38:56.508 {}      [info]  ()      onExecOperateCommand: true
    33  2023/03/18 22:38:56.508 {}      [info]  ()      onCheckCommand: true
    34  2023/03/18 22:38:56.509 {}      [info]  ()      onScrolling: true
    35  2023/03/18 22:38:56.509 {}      [info]  ()      manualScreenshot: true
    36  2023/03/18 22:38:56.509 {}      [info]  ()      retryMaxCount: 1
    37  2023/03/18 22:38:56.510 {}      [info]  ()      retryIntervalSeconds: 2.0
    38  2023/03/18 22:38:56.510 {}      [info]  ()      shortWaitSeconds: 1.5
    39  2023/03/18 22:38:56.511 {}      [info]  ()      waitSecondsOnIsScreen: 15.0
    40  2023/03/18 22:38:56.511 {}      [info]  ()      waitSecondsForLaunchAppComplete: 15.0
    41  2023/03/18 22:38:56.511 {}      [info]  ()      waitSecondsForAnimationComplete: 0.5
    42  2023/03/18 22:38:56.511 {}      [info]  ()      swipeDurationSeconds: 3.0
    43  2023/03/18 22:38:56.512 {}      [info]  ()      flickDurationSeconds: 0.3
    44  2023/03/18 22:38:56.512 {}      [info]  ()      swipeMarginRatio: 0.1
    45  2023/03/18 22:38:56.512 {}      [info]  ()      scrollVerticalMarginRatio: 0.2
    46  2023/03/18 22:38:56.513 {}      [info]  ()      scrollHorizontalMarginRatio: 0.2
    47  2023/03/18 22:38:56.513 {}      [info]  ()      tapHoldSeconds: 0.2
    48  2023/03/18 22:38:56.513 {}      [info]  ()      tapAppIconMethod: auto
    49  2023/03/18 22:38:56.514 {}      [info]  ()      tapAppIconMacro: 
    50  2023/03/18 22:38:56.514 {}      [info]  ()      syncWaitSeconds: 1.8
    51  2023/03/18 22:38:56.515 {}      [info]  ()      Searching device for the profile. (profileName=iPhone 14(16.2))
    52  2023/03/18 22:38:59.422 {}      [info]  ()      Device found. (iPhone 14, iOS 16.2, A92DD7F7-7B50-4CA1-8060-72F46D777B94)
    53  2023/03/18 22:38:59.469 {}      [info]  ()      appium --session-override --relaxed-security --log /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1/appium_2023-03-18_223859426.log --port 4720
    54  2023/03/18 22:39:02.563 {}      [info]  ()      Appium Server started. (pid=13667, port=4720)
    55  2023/03/18 22:39:03.572 {}      [info]  ()      Connecting to Appium Server.(http://127.0.0.1:4720/)
    56  2023/03/18 22:39:03.573 {}      [info]  ()      Note: Initializing IOSDriver may take a few minutes to build and install WebDriverAgent.

Test1 > test1() STANDARD_ERROR
    Cleaning up unclosed ZipFile for archive /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/TestList_iOSSettingsConfig.xlsx

Test1 > test1() STANDARD_OUT
    57  2023/03/18 22:39:07.556 {}      [info]  ()      implicitlyWaitSeconds: 5.0
    58  2023/03/18 22:39:07.557 {}      [info]  ()      Searching device for the profile. (profileName=iPhone 14(16.2))
    59  2023/03/18 22:39:09.277 {}      [info]  ()      Device found. (iPhone 14, iOS 16.2, A92DD7F7-7B50-4CA1-8060-72F46D777B94)
    60  2023/03/18 22:39:09.278 {}      [info]  ()      AppiumDriver initialized.
    61  2023/03/18 22:39:09.278 {}      [-]     (parameter)     testrun: testConfig/testrun.properties
    62  2023/03/18 22:39:09.278 {}      [-]     (parameter)     testConfigName: iOSSettingsConfig(/Users/wave1008/github/wave1008/shirates-samples-job1/testConfig/iOSSettingsConfig.json)
    63  2023/03/18 22:39:09.279 {}      [-]     (parameter)     profileName: iPhone 14(16.2)
    64  2023/03/18 22:39:09.279 {}      [-]     (parameter)     appIconName: Settings
    65  2023/03/18 22:39:09.279 {}      [-]     ()      (capabilities)
    66  2023/03/18 22:39:09.280 {}      [-]     (parameter)     appium:networkConnectionEnabled: false
    67  2023/03/18 22:39:09.280 {}      [-]     (parameter)     appium:showXcodeLog: true
    68  2023/03/18 22:39:09.280 {}      [-]     (parameter)     appium:newCommandTimeout: 300
    69  2023/03/18 22:39:09.280 {}      [-]     (parameter)     appium:locale: US
    70  2023/03/18 22:39:09.281 {}      [-]     (parameter)     appium:takesScreenshot: true
    71  2023/03/18 22:39:09.281 {}      [-]     (parameter)     appium:bundleId: com.apple.Preferences
    72  2023/03/18 22:39:09.281 {}      [-]     (parameter)     appium:language: en
    73  2023/03/18 22:39:09.281 {}      [-]     (parameter)     appium:automationName: XCUITest
    74  2023/03/18 22:39:09.282 {}      [-]     (parameter)     appium:locationContextEnabled: false
    75  2023/03/18 22:39:09.282 {}      [-]     (parameter)     appium:platformVersion: 16.2
    76  2023/03/18 22:39:09.282 {}      [-]     (parameter)     appium:useJSONSource: true
    77  2023/03/18 22:39:09.282 {}      [-]     (parameter)     appium:databaseEnabled: false
    78  2023/03/18 22:39:09.283 {}      [-]     (parameter)     appium:udid: A92DD7F7-7B50-4CA1-8060-72F46D777B94
    79  2023/03/18 22:39:09.283 {}      [-]     (parameter)     appium:webStorageEnabled: false
    80  2023/03/18 22:39:09.283 {}      [-]     (parameter)     appium:deviceName: iPhone 14
    81  2023/03/18 22:39:09.283 {}      [-]     (parameter)     appium:javascriptEnabled: true
    82  2023/03/18 22:39:09.283 {}      [-]     (parameter)     platformName: IOS
    83  2023/03/18 22:39:09.284 {}      [-]     (parameter)     appium:appPackage: com.apple.Preferences
    84  2023/03/18 22:39:09.284 {}      [-]     ()      (others)
    85  2023/03/18 22:39:09.285 {}      [-]     (parameter)     isEmulator: true
    86  2023/03/18 22:39:09.285 {}      [-]     (parameter)     hasOsaihuKeitai: false
    87  2023/03/18 22:39:09.286 {}      [info]  ()      Setup executed. (duration: 13.0 sec)
    88  2023/03/18 22:39:09.292 {}      [info]  (syncCache)     Syncing (1)
    89  2023/03/18 22:39:10.109 {}      [info]  (syncCache)     elapsed=0.816, syncWaitSeconds=1.8
    90  2023/03/18 22:39:10.109 {}      [info]  (syncCache)     Syncing (2)
    91  2023/03/18 22:39:11.019 {}      [info]  (syncCache)     elapsed=1.727, syncWaitSeconds=1.8
    92  2023/03/18 22:39:11.020 {}      [info]  (syncCache)     Syncing (3)
    93  2023/03/18 22:39:11.934 {}      [info]  (syncCache)     Synced. (elapsed=2.641, currentScreen=?)
    94  2023/03/18 22:39:11.935 {}      [info]  ()      Running scenario ..................................................
    95  2023/03/18 22:39:11.935 {test1} [SCENARIO]      (scenario)      test1()
    96  2023/03/18 22:39:12.545 {test1} [screenshot]    (screenshot)    screenshot
    97  2023/03/18 22:39:12.546 {test1} [operate]       (launchApp)     Launch app <Settings>
    98  2023/03/18 22:39:13.606 {test1} [info]  (launchApp)     Launching app. (bundleId=com.apple.Preferences)
    99  2023/03/18 22:39:13.608 {test1} [info]  (execute)       xcrun simctl launch A92DD7F7-7B50-4CA1-8060-72F46D777B94 com.apple.Preferences
    100 2023/03/18 22:39:13.822 {test1} [info]  (launchApp)     doUntilTrue(1)
    101 2023/03/18 22:39:13.824 {test1} [info]  (syncCache)     Syncing (1)
    102 2023/03/18 22:39:15.438 {test1} [info]  (syncCache)     elapsed=1.614, syncWaitSeconds=1.8
    103 2023/03/18 22:39:15.439 {test1} [info]  (syncCache)     Syncing (2)
    104 2023/03/18 22:39:16.274 {test1} [info]  (syncCache)     Synchronization timed out (elapsed=2.449 > syncWaitSeconds=1.8, currentScreen=?)
    105 2023/03/18 22:39:18.276 {test1} [info]  (launchApp)     App launched. (com.apple.Preferences)
    106 2023/03/18 22:39:18.481 {test1} [screenshot]    (screenshot)    screenshot
    107 2023/03/18 22:39:18.482 {test1-1}       [CASE]  (case)  (1)
    108 2023/03/18 22:39:18.482 {test1-1}       [CONDITION]     (condition)     condition
    109 2023/03/18 22:39:18.483 {test1-1}       [info]  ()      iPhone 14(16.2)
    110 2023/03/18 22:39:18.483 {test1-1}       [ACTION]        (action)        action
    111 2023/03/18 22:39:18.484 {test1-1}       [operate]       (pressHome)     Press OS home
    112 2023/03/18 22:39:18.958 {test1-1}       [info]  (syncCache)     Syncing (1)
    113 2023/03/18 22:39:29.284 {test1-1}       [info]  (syncCache)     Synchronization timed out (elapsed=10.326 > syncWaitSeconds=1.8, currentScreen=[iOS Home Screen])
    114 2023/03/18 22:39:31.287 {test1-1}       [screenshot]    (screenshot)    screenshot
    115 2023/03/18 22:39:31.289 {test1-1}       [EXPECTATION]   (expectation)   expectation
    116 2023/03/18 22:39:31.291 {test1-1}       [branch]        (os)    ios {
    117 2023/03/18 22:39:31.294 {test1-1}       [OK]    (screenIs)      [iOS Home Screen] is displayed
    118 2023/03/18 22:39:31.294 {test1-1}       [branch]        (os)    } ios
    119 2023/03/18 22:39:31.295 {test1-1}       [info]  ()      Scenario executed. (duration: 22.0 sec)
    120 2023/03/18 22:39:31.296 {}      [info]  ()      Test function executed. (duration: 35.4 sec)
    121 2023/03/18 22:39:31.296 {}      [info]  ()      End of Test function: test1 [test1()]

Test1 STANDARD_OUT
    122 2023/03/18 22:39:31.299 {}      [info]  ()      Logging to file:////Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1/
    Copying jar content _ReportScript.js to /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1
    Copying jar content _ReportStyle.css to /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1
    Loading: /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1/TestLog(commandList)_20230318223855.log
    Saved: /Users/wave1008/Downloads/TestResults/iOSSettingsConfig/2023-03-18_223855/Test1/Test1@i.xlsx

    123 2023/03/18 22:39:31.655 {}      [info]  ()      Quitting TestDriver.
    124 2023/03/18 22:39:32.711 {}      [info]  ()      Test class executed. (duration: 37.0 sec)

Gradle Test Executor 5 finished executing tests.

> Task :test
Finished generating test XML results (0.001 secs) into: /Users/wave1008/github/wave1008/shirates-samples-job1/build/test-results/test
Generating HTML test report...
Finished generating test html results (0.002 secs) into: /Users/wave1008/github/wave1008/shirates-samples-job1/build/reports/tests/test
:test (Thread[Execution worker for ':',5,main]) completed. Took 37.795 secs.
producer locations for task group 1 (Thread[Execution worker for ':',5,main]) started.
producer locations for task group 1 (Thread[Execution worker for ':',5,main]) completed. Took 0.0 secs.

BUILD SUCCESSFUL in 38s
5 actionable tasks: 2 executed, 3 up-to-date
Watched directory hierarchies: [/Users/wave1008/github/wave1008/shirates-samples-job1, /Users/wave1008/Downloads/shirates-samples-job1]
wave1008@SNB-M1 shirates-samples-job1 % 

Android/iOSアプリで不規則なポップアップダイアログを処理する

スマートフォンアプリでは、画面遷移の途中でイレギュラーな画面が挿入されることがよくあります。

ポップアップダイアログ(位置情報の許可、ネットワークエラー、Firebase In-App Messaging、広告など)、機能の使い方を説明するチュートリアル、通知バルーンなどが表示されたり、されなかったりすることがあります。

このようなイレギュラーを処理するためには、条件分岐を実装する必要があります。これはとても面倒な作業です。

AnnoyingEventHandling1.kt

@Test
@Order(10)
fun annoyingEventHandling1() {

    scenario {
        case(1) {
            condition {
                it.macro("[Some Screen]")
                    .ifCanSelect("While using the app") {
                        it.tap()
                    }
            }.action {
                it.tap("[Button1]")
                    .ifCanSelect("While using the app") {
                        it.tap()
                    }
            }.expectation {
                it.screenIs("[Next Screen]")
            }
        }
    }
}

@Test
@Order(20)
fun annoyingEventHandling2() {

    scenario {
        case(1) {
            condition {
                it.macro("[Some Screen2]")
                    .ifCanSelect("While using the app") {
                        it.tap()
                    }
            }.action {
                it.tap("[Button2]")
                    .ifCanSelect("While using the app") {
                        it.tap()
                    }
            }.expectation {
                it.screenIs("[Next Screen]")
            }
        }
    }
}

irregularHandler

イレギュラーに関する処理はirregularHandlerで一元的に記述することができます。

TestClass内の全てのテスト関数に適用するには、setEventHandlers関数をオーバーライドし、context.iregularHandlerに処理を記述します。

irregularHandlerは操作コマンドの実行時に毎回呼び出されるので割り込み処理を実現できます。この仕組みは非常に強力であり、テストコードをシンプルにすることができます。

IrregularHandler1

(kotlin/tutorial/inaction/IrregularHandler1.kt)

@Testrun("testConfig/android/androidSettings/testrun.properties")
class IrregularHandler1 : UITest() {

    override fun setEventHandlers(context: TestDriverEventContext) {

        context.irregularHandler = {
            ifCanSelect("While using the app") {
                it.tap()
            }
        }
    }

    @Test
    @Order(10)
    fun irregularHandler1() {

        scenario {
            case(1) {
                condition {
                    it.macro("[Some Screen]")
                }.action {
                    it.tap("[Button1]")
                }.expectation {
                    it.screenIs("[Next Screen]")
                }
            }
        }
    }

}

注意事項

  • irregularHandlerに大量の処理を記述すると、パフォーマンス上の問題が発生する可能性があります。
  • irregularHandlerの処理の実行中は、デフォルトでロギングが抑制されます。

suppressHandler

suppressHandler関数を使用することで、regularregularHandlerの発射を抑制することができます。

IrregularHandler1

(kotlin/tutorial/inaction/IrregularHandler1.kt)

@Test
@Order(20)
fun suppressHandler() {

    scenario {
        case(1) {
            condition {
                it.macro("[Some Screen]")
            }.action {
                /**
                 * In suppressHandler block,
                 * calling irregular handler is suppressed
                 */
                suppressHandler {
                    it.tap("[Button1]")
                }
            }.expectation {
                it.screenIs("[Next Screen]")
            }
        }
    }
}

disableHandler(), enableHandler()

これらの機能により、irregularHandlerの無効化、有効化を行うことができます。

IrregularHandler1

(kotlin/tutorial/inaction/IrregularHandler1.kt)

@Test
@Order(30)
fun disableHandler_EnableHandler() {

    scenario {
        case(1) {
            condition {
                it.macro("[Some Screen]")
            }.action {
                disableHandler()    // Calling irregular handler is disabled.
                it.tap("[Button1]")
                ifCanSelect("While using the app") {
                    it.tap()
                }
                enableHandler()     // Calling irregular handler is enabled again.
            }.expectation {
                it.screenIs("[Next Screen]")
            }
        }
    }
}

shirates-core モバイルアプリ用結合テストフレームワーク

OSSなので無償で利用できます。ぜひお試しください。

github.com

Shiratesでテスト時にAndroid/iOSの言語設定を変更する

Shiratesを使えばテスト時にAndroid/iOSの言語設定を動的に変更することができます。

例1. Androidで言語を変更する

gist.github.com

例2. iOSで言語を変更する

gist.github.com

shirates-core モバイルアプリ用結合テストフレームワーク

OSSなので無償で利用できます。ぜひお試しください。

github.com

Appium 2.0のリリースはいつ?

Appium 2.0がまもなくリリース!とメディアで発表されたのは2021年のことです、

https://www.publickey1.jp/blog/21/appium_20jonathan_lipps.html

残念ながら2022年10月現在においてAppium 2.0はβ版扱いのままで、未だ正式リリースの発表はありません。現在はどういうステータスなんでしょうか?

https://github.com/appium/appium/discussions/15828

こちらのスレッドによると、以下のような状況のようです。

  • Appium 2.0はソフトウェアはリリース準備できている
  • ドキュメントが完成していないので、完成するまではリリース発表するわけにはいかない
  • Appium 1はもうメンテナンスしてないのでAppium 2を使用すべき

重要なのはこれです。

  • Appium 1はもうメンテナンスしてないのでAppium 2を使用すべき

AndroidiOSの新しいリリースに追従してメンテナンスされているのはAppium 2.0のほうだけであり、Appium 1.xはメンテされていないそうです。

Appium 1.xを使っているけど2.0が正式リリースされるのを待ってから移行しようという方針のプロジェクトは、今すぐ2.0への移行を開始すべきです。

Shiratesを使ってみよう - 画面ニックネーム -(その2)

Shirates画面ニックネームを紹介します。

こちらの記事の翻訳です。

dev.to

サンプルコードの入手

本記事で説明するサンプルコードの完成版はこちらから入手してください。 https://github.com/wave1008/shirates-samples-nicknames


Example 2: Androidの設定アプリ

このサンプルではAndroidの設定アプリの4つの画面を使用して画面ニックネームの使い方を説明します。

設定画面

システム画面/言語と入力画面/言語画面

[Android設定トップ画面].json

{
  "key": "[Android設定トップ画面]",

  "identity": "#recycler_view",
  "satellites": ["バッテリー", "ユーザー補助", "パスワードとアカウント", "ヒントとサポート"],

  "selectors": {
    "[アカウントアバター]": "#account_avatar",
    "[設定]": "#homepage_title",

    "[検索ボタン]": "<#search_action_bar>:inner(1)",
    "[設定を検索]": "#search_action_bar_title",

    "[ネットワークとインターネット]": "",
    "{ネットワークとインターネット}": "[ネットワークとインターネット]:label",
    "[ネットワークとインターネットアイコン]": "[ネットワークとインターネット]:leftImage",

    "[接続済みのデバイス]": "",
    "{接続済みのデバイス}": "[接続済みのデバイス]:label",
    "[接続済みのデバイスアイコン]": "[接続済みのデバイス]:leftImage",

    "[アプリ]": "",
    "{アプリ}": "[アプリ]:label",
    "[アプリアイコン]": "[アプリ]:leftImage",

    "[通知]": "",
    "{通知}": "[通知]:label",
    "[通知アイコン]": "[通知]:leftImage",

    "[バッテリー]": "",
    "{バッテリー}": "[バッテリー]:label",
    "[バッテリーアイコン]": "[バッテリー]:leftImage",

    "[ストレージ]": "",
    "{ストレージ}": "[ストレージ]:label",
    "[ストレージアイコン]": "[ストレージ]:leftImage",

    "[着信音とバイブレーション]": "",
    "{着信音とバイブレーション}": "[着信音とバイブレーション]:label",
    "[着信音とバイブレーションアイコン]": "[着信音とバイブレーション]:leftImage",

    "[ディスプレイ]": "",
    "{ディスプレイ}": "[ディスプレイ]:label",
    "[ディスプレイアイコン]": "[ディスプレイ]:leftImage",

    "[壁紙とスタイル]": "",
    "{壁紙とスタイル}": "[壁紙とスタイル]:label",
    "[壁紙とスタイルアイコン]": "[壁紙とスタイル]:leftImage",

    "[ユーザー補助]": "",
    "{ユーザー補助}": "[ユーザー補助]:label",
    "[ユーザー補助アイコン]": "[ユーザー補助]:leftImage",

    "[セキュリティ]": "",
    "{セキュリティ}": "[セキュリティ]:label",
    "[セキュリティアイコン]": "[セキュリティ]:leftImage",

    "[プライバシー]": "",
    "{プライバシー}": "[プライバシー]:label",
    "[プライバシーアイコン]": "[プライバシー]:leftImage",

    "[位置情報]": "",
    "{位置情報}": "[位置情報]:label",
    "[位置情報アイコン]": "[位置情報]:leftImage",

    "[緊急情報と緊急通報]": "",
    "{緊急情報と緊急通報}": "[緊急情報と緊急通報]:label",
    "[緊急情報と緊急通報アイコン]": "[緊急情報と緊急通報]:leftImage",

    "[パスワードとアカウント]": "",
    "{パスワードとアカウント}": "[パスワードとアカウント]:label",
    "[パスワードとアカウントアイコン]": "[パスワードとアカウント]:leftImage",

    "[Google]": "",
    "{Google}": "[Google]:label",
    "[Google Icon]": "[Google]:leftImage",

    "[システム]": "",
    "{システム}": "[システム]:label",
    "[システムアイコン]": "[システム]:leftImage",

    "[エミュレートされたデバイスについて]": "",
    "{エミュレートされたデバイスについて}": "[エミュレートされたデバイスについて]:label",
    "[エミュレートされたデバイスについてアイコン]": "[エミュレートされたデバイスについて]:leftImage",

    "[デバイス情報]": "",
    "{デバイス情報}": "[デバイス情報]:label",
    "[デバイス情報アイコン]": "[デバイス情報]:leftImage",

    "[ヒントとサポート]": "",
    "{ヒントとサポート}": "[ヒントとサポート]:label",
    "[ヒントとサポートアイコン]": "[ヒントとサポート]:leftImage"
  },

  "scroll": {
    "start-elements": "",
    "end-elements": "{ヒントとサポート}",
    "overlay-elements": "[検索ボタン][設定を検索]"
  }
}

satellites付きidentity

Androidの設定アプリはスクロール可能なビューであり、常に表示される固定された識別子を持ちません。このような場合、satellites付きのidentityを使用して表示された画面を識別することができます。

  "identity": "#recycler_view",
  "satellites": ["バッテリー", "ユーザー補助", "パスワードとアカウント", "ヒントとサポート"],

上記の例では"identity": "#recycler_view"は常に表示されますが、単独でユニークではありません。"satellites"はAndroid設定画面において、そのメンバーのいずれかが表示されるリストです。"identity"+"satellites"は画面のユニーク識別子として認識されます。

相対セレクタ

Appiumは全ての要素がユニークな属性を持つか、そう実装されることを前提としているようです。しかし実際には、特にスクロール可能な画面においては、ユニークな識別子を持たない要素はたくさんあります。

Shiratesでは相対セレクターを導入してこの問題を解決します。UI要素のグループのうち少なくとも1つのメンバーがユニーク識別子を持つ場合、その周辺の要素は相対的に取得することができます。

以下の画像をみてください。

  • "バッテリー"は静的なコンテンツであり、かつユニークです。
  • "100%"は動的なコンテンツであり、ユニーク識別子は持ちません。
  • バッテリーのアイコンはユニーク識別子は持ちません。

このような場合、セレクターニックネームを以下のように定義できます。

    "[バッテリー]": "",
    "{バッテリー}": "[バッテリー]:label",
    "[バッテリーアイコン]": "[バッテリー]:leftImage",

:labelウィジェットフローにおける次の要素を取得する相対セレクターです。Relative selector(Widget flow based)

:leftImageは左に存在する画像を選択する相対セレクターです。Relative selector(Direction based).


[システム画面].json

{
  "key": "[システム画面]",

  "include": [
  ],

  "identity": "[←][システム]",

  "selectors": {
    "[←]": "@上へ移動",
    "[システム]": "@システム",

    "[言語と入力]": "",
    "{言語と入力}": "[言語と入力]:belowLabel",

    "[ジェスチャー]": "",

    "[日付と時刻]": "",
    "{日付と時刻}": "[日付と時刻]:belowLabel",

    "[バックアップ]": "",

    "[システム アップデート]": "",
    "{システム アップデート}": "[システム アップデート]:belowLabel",

    "[ルール]": "",
    "{ルール}": "[ルール]:belowLabel",

    "[複数ユーザー]": "",
    "{複数ユーザー}": "[複数ユーザー]:belowLabel",

    "[開発者向けオプション]": "",
    "{開発者向けオプション}": "[開発者向けオプション]:belowLabel",

    "[リセット オプション]": ""
  }

}

[言語と入力画面].json

{
  "key": "[言語と入力画面]",

  "include": [
  ],

  "identity": "[←][言語と入力]",

  "selectors": {
    "[←]": "@上へ移動",
    "[言語と入力]": "@言語と入力",

    "[言語]": "",
    "{言語}": "[言語]:belowLabel",
    "[言語アイコン]": "[言語]:leftImage",

    "[キーボード]": "",

    "[画面キーボード]": "",
    "{画面キーボード}": "[画面キーボード]:belowLabel",

    "[物理キーボード]": "",
    "{物理キーボード}": "[物理キーボード]:belowLabel",

    "[ツール]": "",

    "[スペルチェック]": "",
    "{スペルチェック}": "[スペルチェック]:belowLabel",

    "[単語リスト]": "",
    "{単語リスト}": "[単語リスト]:belowLabel",

    "[ポインタの速度]": "",

    "[テキスト読み上げの設定]": ""
  }

}

[言語画面].json

{
  "key": "[言語画面]",

  "include": [
  ],

  "identity": "[←][言語]",

  "selectors": {
    "[←]": "@上へ移動",
    "[言語]": "@言語",

    "[言語を追加]": ""
  }

}

AndroidSettingsTest

以下は画面ニックネームファイルなしの場合と、画面ニックネームファイルありの場合のAndroidの設定画面のテストコードです。画面ニックネームファイルなしの場合の場合はcontent-descのような低レベルの識別子を使用する必要があります。一方、画面ニックネームファイルありの場合は、抽象度の高い画面ニックネーム/セレクターニックネームを使用することができます。

package androidSettings

import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import shirates.core.driver.commandextension.*
import shirates.core.testcode.UITest

class AndroidSettingsTest : UITest() {

    @Test
    @Order(10)
    fun withoutNickname() {

        scenario {
            case(1) {
                condition {
                    it.tapAppIcon("設定")
                        .existWithScrollDown("バッテリー||ユーザー補助||パスワードとアカウント||ヒントとサポート")
                }.action {
                    it.tapWithScrollDown("システム")
                }.expectation {
                    it.exist("@上へ移動")
                        .exist("@システム")
                }
            }
            case(2) {
                action {
                    it.tap("言語と入力")
                }.expectation {
                    it.exist("@上へ移動")
                        .exist("@言語と入力")
                }
            }
            case(3) {
                action {
                    it.tap("[言語]")
                }.expectation {
                    it.exist("@上へ移動")
                        .exist("@言語")
                }
            }
        }
    }

    @Test
    @Order(20)
    fun withNickname() {

        scenario {
            case(1) {
                condition {
                    it.tapAppIcon("設定")
                        .screenIs("[Android設定トップ画面]")
                }.action {
                    it.tapWithScrollDown("[システム]")
                }.expectation {
                    it.screenIs("[システム画面]")
                        .exist("[←]")
                        .exist("[システム]")
                }
            }
            case(2) {
                action {
                    it.tap("[言語と入力]")
                }.expectation {
                    it.screenIs("[言語と入力画面]")
                        .exist("[言語]")
                }
            }
            case(3) {
                action {
                    it.tap("[言語]")
                }.expectation {
                    it.screenIs("[言語画面]")
                }
            }
        }
    }

}

テスト結果

画面ニックネームなし画面ニックネームありのテスト結果を確認して比較してみてください。画面ニックネームありの方が理解しやすいことがわかると思います。

_Report(simple).html

AndroidSettingsTest@a.xlsx


まとめ

Shiratesでは画面ニックネームをJSONファイルで定義することができます。画面ニックネームはテストコードを読みやすく生産性を高いものにします。

Shiratesを使ってみよう - 画面ニックネーム -

Shirates画面ニックネームを紹介します。

こちらの記事の翻訳です。 dev.to

画面ニックネーム(Screen Nickname)

Shiratesでは画面ニックネームJSONファイルで定義することができます。画面ニックネームはテストコードを読みやすく生産性を高いものにします。

画面ニックネームの機能

サンプルコードの入手

本記事で説明するサンプルコードの完成版はこちらから入手してください。 https://github.com/wave1008/shirates-samples-nicknames

Example 1: 電卓

  • Appium Inspectorを起動します
  • Calculatorアプリをキャプチャします

  • [電卓メイン画面].jsontestConfig/screens/calculatorディレクトリの下に作成します

  • Appium Inspectorを使用して画面の要素を調査し、画面ニックネームファイルを以下のように編集します

[電卓メイン画面].json

{
  "key": "[電卓メイン画面]",

  "identity": "[AC][()]",

  "selectors": {
    "[計算式]": "#formula",
    "[計算結果]": "#result_final",
    "[計算結果プレビュー]": "#result_preview",

    "[√]": "#op_sqrt",
    "[π]": "#const_pi",
    "[^]": "#op_pow",
    "[!]": "#op_fact",

    "[AC]": "#clr",
    "[()]": "#parens",
    "[%]": "#op_pct",

    "[÷]": "#op_div",
    "[×]": "#op_mul",
    "[-]": "#op_sub",
    "[+]": "#op_add",
    "[=]": "#eq",
    "[⌫]": "#del",

    "[0]": "#digit_0",
    "[1]": "#digit_1",
    "[2]": "#digit_2",
    "[3]": "#digit_3",
    "[4]": "#digit_4",
    "[5]": "#digit_5",
    "[6]": "#digit_6",
    "[7]": "#digit_7",
    "[8]": "#digit_8",
    "[9]": "#digit_9",
    "[.]": "#dec_point"
  }

}

テスト実行時に"[AC][()]"の識別子によって[電卓メイン画面]を識別できるように成ります。 例えば、現在表示中の画面が[電卓メイン画面]であることを以下のように検証することができます。

it.screenIs("[電卓メイン画面]")

CalculatorTest

以下は画面ニックネームなしの場合とありの場合の電卓のテストコードです。画面ニックネームなしの場合、resource-idのような低レベルの識別子を使用する必要があります。一方、画面ニックネームありの場合、抽象化された画面ニックネームセレクターニックネームを使用することができます。

package calculator

import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
import shirates.core.driver.commandextension.*
import shirates.core.testcode.UITest

class CalculatorTest : UITest() {

    @Test
    @Order(10)
    fun withoutNickname() {

        scenario {
            case(1) {
                condition {
                    it.tapAppIcon("電卓")
                }.expectation {
                    it.exist("#clr")
                    it.exist("#parens")
                }
            }
            case(2) {
                action {
                    it.tap("#clr")
                }.expectation {
                    it.select("#formula")
                        .textIsEmpty()
                    it.select("#result_preview")
                        .textIsEmpty()
                }
            }
            case(3) {
                action {
                    it.tap("#digit_1")
                }.expectation {
                    it.select("#formula")
                        .textIs("1")
                    it.select("#result_preview")
                        .textIsEmpty()
                }
            }
            case(4) {
                action {
                    it.tap("#op_add")
                }.expectation {
                    it.select("#formula")
                        .textIs("1+")
                    it.select("#result_preview")
                        .textIsEmpty()
                }
            }
            case(5) {
                action {
                    it.tap("#digit_2")
                }.expectation {
                    it.select("#formula")
                        .textIs("1+2")
                    it.select("#result_preview")
                        .textIs("3")
                }
            }
            case(6) {
                action {
                    it.tap("#eq")
                }.expectation {
                    it.select("#result_final")
                        .textIs("3")
                }
            }
        }
    }

    @Test
    @Order(20)
    fun withNickname() {

        scenario {
            case(1) {
                condition {
                    it.tapAppIcon("電卓")
                }.expectation {
                    it.screenIs("[電卓メイン画面]")
                }
            }
            case(2) {
                action {
                    it.tap("[AC]")
                }.expectation {
                    it.select("[計算式]")
                        .textIsEmpty()
                    it.select("[計算結果プレビュー]")
                        .textIsEmpty()
                }
            }
            case(3) {
                action {
                    it.tap("[1]")
                }.expectation {
                    it.select("[計算式]")
                        .textIs("1")
                    it.select("[計算結果プレビュー]")
                        .textIsEmpty()
                }
            }
            case(4) {
                action {
                    it.tap("[+]")
                }.expectation {
                    it.select("[計算式]")
                        .textIs("1+")
                    it.select("[計算結果プレビュー]")
                        .textIsEmpty()
                }
            }
            case(5) {
                action {
                    it.tap("[2]")
                }.expectation {
                    it.select("[計算式]")
                        .textIs("1+2")
                    it.select("[計算結果プレビュー]")
                        .textIs("3")
                }
            }
            case(6) {
                action {
                    it.tap("[=]")
                }.expectation {
                    it.select("[計算結果]")
                        .textIs("3")
                }
            }
        }
    }
}

テスト結果

画面ニックネームなし画面ニックネームありのテスト結果を確認して比較してみてください。画面ニックネームありの方が理解しやすいことがわかると思います。

_Report(simple).html

CalculatorTest@a.xlsx


まとめ

Shiratesでは画面ニックネームJSONファイルで定義することができます。画面ニックネームはテストコードを読みやすく生産性を高いものにします。