waveさんの技術日誌

wave1008の日記の新館です。

Shiratesを使ってみよう - セレクター式 -

Shiratesセレクター式を紹介します。

セレクター式

Shiratesのセレクター式は画面要素をフィルターするための式です。 select関数セレクター式を引数にとり、解析を行い、画面要素を取り出してフィルターし、結果として取得された要素を返します。 セレクター式は1つ以上のフィルター式から成ります。

フィルター式

フィルター フォーマルな書式 省略形 Androidの属性 iOSの属性
text text=text1 text1 text label
textStartsWith textStartsWith=text1 text1* text label
textContains textContains=text1 *text1* text label
textEndsWith textEndsWith=text1 *text1 text label
textMatches textMatches=^text$ n/a text label
literal literal=literal1 'literal1' text label
id id=id1 #id1 resource-id name
access access=access1 @access1 content-desc name
accessStartsWith accessStartsWith=access1 @access1* content-desc name
accessContains accessContains=access1 @*access1* content-desc name
accessEndsWith accessEndsWith=access1 @*access1 content-desc name
accessMatches accessMatches=^access1$ n/a content-desc name
value value=value1 n/a text value
valueStartsWith valueStartsWith=value1 n/a text value
valueContains valueContains=value1 n/a text value
valueEndsWith valueEndsWith=value1 n/a text value
valueMatches valueMatches=^value1$ n/a text value
class class=class1 .class1 class type
focusable focusable=true n/a focusable n/a
scrollable scrollable=true n/a scrollable n/a
visible visible=true n/a n/a visible
xpath xpath=//*[@text='text1'] n/a (arbitrary) (arbitrary)
pos pos=2 [2] n/a n/a
ignoreTypes ignoreTypes=Class1,Class2 n/a class type
image image=image1.png image1.png n/a n/a

フィルターにおける複数値の指定

フィルターは複数の値を (value1|value2)のように丸括弧と"|"(or)演算子で受け取ることができます。

it.select("text=(text1|text2)") // フォーマルな書式

it.select("(text1|text2)")  // 省略形の書式


以下のセレクター式はシンプルですが上記と等価です。

it.select("text1||text2")   // 等価なセレクター式


もっと複雑な状況では"|"が必要になるかもしれません。

it.select("(text1|text2)&&.class1||(@access1|@access2)&&.class2)")

セレクター式の規則

フィルター式は単独でセレクター式として使用できます。

text1


フィルター式は"&&"(AND)演算子で結合することができます。

text1&&.class1&&visible=true


フィルター式は"||"(OR)演算子で結合することができます。

text1||text2||@access1


"&&"は"||"よりも優先されます。

text1&&.class1||@access1

上記は(text=text1 and class=class1) or access=access1の意味となります。 ただし、以下のように丸括弧を使用することはできません(サポートしていません)。

悪い例

(text1&&.class1)||@access1

完全修飾ID

Androidresource-idはパッケージ名に対応したプレフィックスを持っています。例えば設定アプリは"com.android.settings がパッケージ名です。

完全修飾形 省略形
com.android.settings:id/search_bar search_bar

Shiratesでは完全修飾形への自動変換が行われ、かつ可読性が高いことから省略形の使用が推奨されます。 自動変換ではうまくいかない場合は完全修飾形を使用します。

セレクター式のためのプラットフォームアノテーション

プラットフォームアノテーション(@a, @i)を使用するとAndroid用とiOS用のセレクター式を1行で記述することができます。

@a<.android.widget.ImageButton>,@i<.XCUIElementTypeButton>
it.select("@a<.android.widget.ImageButton>,@i<.XCUIElementTypeButton>")

この記述を使用してニックネームを定義することができます。

"[Button1]": "@a<.android.widget.ImageButton>,@i<.XCUIElementTypeButton>"

否定フィルター

複雑な状況においては否定フィルターを使用することができます。

text=text1      // 通常のフィルター
text!=text1     // 否定フィルター

text1       // 通常のフィルター
!text1      // 否定フィルター

accessContains=text1    // 通常のフィルター
accessContains!=text1   // 否定フィルター

@*text1*    // 通常のフィルター
!@*text1*   // 否定フィルター

画像フィルター

画像ファイルを使用して画像マッチング(テンプレートマッチング)を行うことができます。 画像による検証

image=image1.png    // フォーマルな形式
image1.png          // 省略形
image1.png?scale=0.5&threshold=20   // オプションあり(フォーマルな形式)
image1.png?s=0.5&t=20   // オプションあり(省略形)

サンプルコード

サンプルコードの入手

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

SelectorTest2

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

class SelectTest2 : UITest() {

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

        scenario {
            case(1) {
                condition {
                    it.restartApp()
                }.action {
                    it.select("ネットワークとインターネット")
                }.expectation {
                    it.textIs("ネットワークとインターネット")
                }
            }
            case(2) {
                action {
                    it.select("ネットワークと*")
                }.expectation {
                    it.textIs("ネットワークとインターネット")
                }
            }
            case(3) {
                action {
                    it.select("*とインターネット")
                }.expectation {
                    it.textIs("ネットワークとインターネット")
                }
            }
            case(4) {
                action {
                    it.select("textMatches=^ネットワークとインターネット$")
                }.expectation {
                    it.textIs("ネットワークとインターネット")
                }
            }
            case(5) {
                action {
                    it.selectWithScrollDown("デバイス情報||エミュレートされたデバイスについて")
                }.expectation {
                    realDevice {
                        it.textIs("デバイス情報")
                    }.emulator {
                        it.textIs("エミュレートされたデバイスについて")
                    }
                }
            }
        }
    }

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

        scenario {
            case(1) {
                condition {
                    it.restartApp()
                }.action {
                    it.select("#search_action_bar_title")
                }.expectation {
                    it.textIs("設定を検索")
                }
            }
        }
    }

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

        scenario {
            case(1) {
                condition {
                    it.restartApp()
                        .tap("ネットワークとインターネット")
                }.action {
                    it.select("@ネットワークとインターネット")
                }.expectation {
                    it.idIs("collapsing_toolbar")
                }
            }
        }
    }

    @Test
    @Order(40)
    fun selectByClass() {

        scenario {
            case(1) {
                condition {
                    it.restartApp()
                }.action {
                    it.select(".android.widget.ImageButton")
                }.expectation {
                    it.classIs("android.widget.ImageButton")
                }
            }
        }
    }

    @Test
    @Order(50)
    fun selectByXpath() {

        scenario {
            case(1) {
                condition {
                    it.restartApp()
                }.action {
                    it.select("xpath=//*[@text='設定を検索']")
                }.expectation {
                    it.textIs("設定を検索")
                }
            }
        }
    }

    @Test
    @Order(60)
    fun selectByPos() {

        scenario {
            case(1) {
                condition {
                    it.restartApp()
                        .tap("バッテリー")
                }.action {
                    it.select("*バッテリー*&&[1]")
                }.expectation {
                    it.textIs("バッテリー使用量")
                }
            }
            case(2) {
                action {
                    it.select("*バッテリー*&&[2]")
                }.expectation {
                    it.textIs("バッテリーセーバー")
                }
            }
            case(3) {
                action {
                    it.select("*バッテリー*&&[3]")
                }.expectation {
                    it.textIs("バッテリーを長持ちさせ、充電を最適化します")
                }
            }
        }
    }

}

サンプルコードの実行結果

Shiratesを使ってみよう - select関数 -

Shiratesselect関数を紹介します。

画面要素の取得方法

Shiratesでは画面要素を取得するのにAppiumと同様に以下の方法を使用できます。

  • text
  • content-desc
  • resource-id
  • class
  • xpath

テキスト属性(text)による画面要素の取得

Shiratesでは下記のようにAppiumよりも簡素な記述で要素を取得できます。

select("テキスト")

また、スクロールする画面においては以下のように記述することができます。

selectWithScrollDown("テキスト")

アクセシビリティ属性(content-desc)による画面要素の取得

content-descを指定して要素を取得する場合は"@"を付与します。

select("@上へ移動")


select関数の詳細はこちらを参照ください。

サンプルコード

サンプルコードの入手

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

SelectorTest

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

class SelectTest : UITest() {

    @Test
    @Order(10)
    @DisplayName("テキスト属性で要素を取得する")
    fun selectByText() {

        scenario {
            case(1) {
                action {
                    it.select("ネットワークとインターネット")
                }.expectation {
                    it.textIs("ネットワークとインターネット")
                }
            }
            case(2) {
                action {
                    it.selectWithScrollDown("システム")
                }.expectation {
                    it.textIs("システム")
                }
            }
        }
    }

    @Test
    @Order(20)
    @DisplayName("アクセシビリティ属性(content-desc)で要素を取得する")
    fun selectByAccessibility() {

        scenario {
            case(1) {
                condition {
                    it.tapWithScrollDown("システム")
                }.action {
                    it.select("@上へ移動")
                }.expectation {
                    it.accessIs("上へ移動")
                }
            }
        }
    }

}

サンプルコードの実行結果

Shiratesクイックスタート

本記事は github.com の日本語訳です。

ソフトウェアのインストール

前提となる以下のツールをインストールします。

注意事項

ツールが正常に動作しない場合があるので、以下の文字を含むOSのアカウント名は使用しないでください。

  • ASCII文字以外の文字を含む (例:テスト太郎)
  • 空白文字を含む (例:test taro )

IntelliJ IDEA

インストールしていない場合はダウンロードしてインストールしてください。Ultimateは有償製品、Communityはオープンソース製品です。 https://www.jetbrains.com/idea/

Android Studio

インストールしていない場合はダウンロードしてインストールしてください。 https://developer.android.com/studio

Xcodemacのみ)

インストールしていない場合はApp Storeで検索してインストールしてください。

Command Line Tools for Xcodemacのみ)

インストールしていない場合はターミナルを開いて以下のコマンドを実行してください。

xcode-select --install

Homebrew(macのみ)

インストールしていない場合は https://brew.sh/ を参照してインストールしてください。

Node Package Manager (NPM)

インストールしていない場合はインストールしてください。 すでにインストールしている場合、バージョンが古いとAppiumが正常に動作しない場合があるので、比較的新しいバージョンにアップグレードしてください。

macの場合

Homebrewを使用してインストールできます。ターミナルを開いて以下のコマンドを実行してください。 (新規インストールの場合)

brew install node
node -v

(アップグレードの場合)

brew update
brew upgrade node

Windowsの場合

インストールパッケージを入手してインストールしてください。

https://nodejs.org/en/download/

注意事項

インストールしたバージョンが古い場合はAppiumが正常に動作しない場合があるので、比較的新しいバージョンをインストールしてください。

Appium 2.0

インストールしていない場合はインストールしてください。

すでに利用中の場合はバージョンをチェックしてください。

appium -v


Appium 1.xがインストールされている場合はアンインストールしてください。

npm uninstall -g appium


Appium 2.0をnpmでインストールしてください。

npm install -g appium@next
appium -v

補足

  • Appium 2.0はリリース準備中です。2.0をインストールするには"@next"を指定する必要があります。
  • テスト済みの環境を参考にしてください

実行例

appium -v
npm uninstall -g appium
npm install -g appium@next
appium -v

実行結果

wave1008@SNB-M1 ~ % appium -v
2.0.0-beta.33
wave1008@SNB-M1 ~ % npm uninstall -g appium

removed 437 packages, and audited 1 package in 816ms
found 0 vulnerabilities
wave1008@SNB-M1 ~ % npm install -g appium@next

added 426 packages, and audited 427 packages in 16s

62 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
wave1008@SNB-M1 ~ % appium -v                 
2.0.0-beta.35
wave1008@SNB-M1 ~ % 

UIAutomator2 driver

インストールしていない場合はインストールしてください。

appium driver install uiautomator2@2.7.1


すでにインストールしている場合は再インストールしてください。

appium driver list
appium driver uninstall uiautomator2
appium driver install uiautomator2@2.7.1
appium driver list

テスト済みの環境を参考にしてください

XCUITest driver(macのみ)

インストールしていない場合はインストールしてください。

appium driver install xcuitest@4.12.0


すでにインストールしている場合は再インストールしてください。

appium driver list
appium driver uninstall xcuitest
appium driver install xcuitest@4.12.0
appium driver list

テスト済みの環境を参考にしてください

環境変数macの場合)

初期化スクリプト(.zshrcなど)で環境変数を設定してください。

設定例

export ANDROID_SDK_ROOT=/Users/$USER/Library/Android/sdk
export PATH=$ANDROID_SDK_ROOT/tools:$ANDROID_SDK_ROOT/platform-tools:$PATH

設定変更を有効にするにはログオフしてからログオンし直してください。

環境変数Windowsの場合)

  1. Android Studioを開きます。
  2. メニューから Tools > SDK Manager > Appearance & Behavior > System Settings > Android SDKを選択します。
  3. Android SDK Locationの値をコピーします。
  4. システム環境変数ANDROID_SDK_ROOT を設定します。上記でコピーした値を設定します。
  5. システム環境変数Pathを編集し、以下のパスを設定します。
%ANDROID_SDK_ROOT%\platform-tools
%ANDROID_SDK_ROOT%\tools


6. PCを再起動します。

AVD(Android Virtual Device)のセットアップ

デモ用のAVDを作成する

  1. Android Studioを開きます。
  2. メニューから Tools > Device Manager を選択します。
  3. Create Device をクリックします。
  4. Pixel 3a を選択し、Nextをクリックします。
  5. S/API Level 31/Android 12.0 (Google Play) を選択し、Nextをクリックします (電卓アプリをデモで使用するのでGoogle Play Storeが必要となります)。 なお、M1 Macの場合は arm64を、それ以外の場合はx86_64を選択します。
  6. AVDの名前をPixel 3a API 31(Android 12)に設定してFinishをクリックします。

デモンストレーション

デモを実行しましょう

言語設定

shirates-coreに付随しているデモを実行する際はAndroid OSの言語をEnglish(United States)に設定してください。

shirates-coreプロジェクトを開く

  1. shirates-coreプロジェクトのディレクトリをFinderまたはExlorerで開きます。
  2. build.gradle.ktsを右クリックしてIntelliJ IDEAで開きます。

右クリックでのテスト実行を有効にする

  1. IntelliJ IDEA > Preferences (または File > Settings)を選択します。
  2. Build, Execution, Deployment > Build Tools > Gradleを選択します。
  3. Build and run tests usingを IntelliJ IDEAに設定します。
  4. Run tests usingをIntelliJ IDEAに設定します。

AndroidSettingsDemoを実行する

  1. Device ManagerからPixel 3a API 31(Android 12)のAVDを起動します。
  2. IntelliJでshirates-coreプロジェクトを開き、src/test/Kotlin/demo/AndroidSettingsDemoを右クリックしてDebugを選択します。

    3.以下のようなログがIntelliJ IDEAのコンソールに出力されます。

出力されたレポートを確認する

  1. IntelliJ IDEAのコンソールに出力されたログのハイパーリンクをクリックしてログのディレクトリを表示します。
  2. ログとレポートファイルが出力されていることが確認できます。
  3. _Report(simple).htmlを開きます。テストの実行結果をHTMLで確認できます。行をクリックすると対応するスクリーンショット画像がハイライトされます。行をダブルクリックするとイメージが拡大表示されます。
  4. _Report(detail).htmlを開きます。ログタイプがinfoの情報が追加で出力されていることが確認できます。
  5. AndroidSettingsDemo@a.xlsxを開きます。テストの実行結果を表形式で確認できます。
  6. TestList_androidSettingsConfig.xlsxを開きます。一覧でテスト結果を確認できます。

CalculatorDemoを実行する

  1. Device ManagerからPixel 3a API 31(Android 12)のAVDを起動します。
  2. Google Play Storeを開き、Calculator(Google LLC)をインストールします。
  3. IntelliJでshirates-coreプロジェクトを開き、src/test/Kotlin/demo/CalculatorDemoを右クリックしてDebugを選択します。
  4. 電卓のテストが実行されることを確認します。

iOSSettingsDemoを実行する

  1. Xcodeを開きメニューからWindow > Devices and Simulatorsを選択します。iPhone 13 iOS 15.5が存在することを確認します。
  2. IntelliJ IDEAでshirates-coreプロジェクトを開きます。src/test/Kotlin/demo/iOSSettingsDemoを右クリックしてDebugを選択します。
  3. iOS設定画面のテストが実行されることを確認します。

wave1008/shirates-samples-practice1

QiitaにポストしたShiratesの紹介記事です。 qiita.com

この記事で使用しているPractice1のサンプルをGitHubにアップしました。 github.com


git cloneしてお試しください。

git clone https://github.com/wave1008/shirates-samples-practice1.git

Shiratesを使ってみよう - testrunファイル -

前回の記事ではShiratesの環境構築と簡単なテストコードの作成を説明しました。 wave-diary.hatenablog.com

本記事で説明する内容を手元で実行する場合は先に前回の記事に従ってプロジェクトを作成してください。 または完成したプロジェクトをこちらから入手してください。

Shiratesのパラメーター設定

Shiratesは パラメーター構成ファイル で起動パラメーターを設定できます。

testrun.propertiesファイル

testrun.propertiesファイルを編集して以下の内容に書き換えます。

#testrun

## OS --------------------
#os=ios

## Config --------------------
## [Android]
android.configFile=testConfig/settingsConfig.json
android.profile=Android 12

## [iOS]
#ios.configFile=
#ios.profile=

## Stub --------------------
#stubServerUrl=http://stub1

## Test mode --------------------
#noLoadRun=true

## Priority filter --------------------
#must=false
#should=false
#want=false
#none=false

## Log --------------------
logLanguage=ja
#enableSyncLog=false
#enableTestList=false
#enableSpecReport=false
#enableInnerMacroLog=true
#enableInnerCommandLog=true
#enableSilentLog=true
#enableTapElementImageLog=true
#enableXmlSourceDump=false
#enableRetryLog=false
#enableTrace=true
#enableShellExecLog=true
#enableTimeMeasureLog=true
#enableImageMatchDebugLog=true
#testResults=
#testListDir=

## Screenshot --------------------
#screenshotScale=0.333333
#autoScreenshot=false
#onChangedOnly=false
#onCondition=false
#onAction=false
#onExpectation=false
#onExecOperateCommand=false
#onCheckCommand=false
#onScrolling=false
#manualScreenshot=false

## Image Matching --------------------
#imageMatchingScale=
#imageMatchingThreshold=
#imageMatchingCandidateCount=

## Appium --------------------
appiumServerUrl=http://127.0.0.1:4720/
appiumPath=appium
appiumArgs=--session-override --relaxed-security
#appiumArgsSeparator=
#appiumServerStartupTimeoutSeconds=
#appiumSessionStartupTimeoutSeconds=
#implicitlyWaitSeconds=
#appPackageFile=
#appPackageDir=
#packageOrBundleId=
#startupPackageOrBundleId=
#startupActivity=

## TestDriver --------------------
#resuseDriver=
#retryMaxCount=
#retryTimeoutSeconds=
#retryIntervalSeconds=

## App operation --------------------
#appIconName=
#tapAppIconMethod=
#tapAppIconMacro=
#shortWaitSeconds=
#waitSecondsForAnimationComplete=
#waitSecondsOnIsScreen=
#waitSecondsForConnectionEnabled=
#swipeDurationSeconds=
#flickDurationSeconds=
#swipeMarginRatio=
#scrollVerticalMarginRatio=
#scrollHorizontalMarginRatio=
#tapHoldSeconds=
#syncWaitSeconds=

## Custom --------------------
#CustomObject.scan.dir=src/test/kotlin

## Macro --------------------
#MacroObject.scan.dir=src/test/kotlin

## Spec-Report --------------------
#specReport.replace.MANUAL=
#specReport.replace.MANUAL.reason=
#specReport.replace.SKIP=
#specReport.replace.SKIP.reason=

## misc
#android.swipeOffsetY=-20
#ios.swipeOffsetY=-5
#xmlSourceRemovePattern=
#boundsToRectRatio=
#ios.selectIgnoreTypes=XCUIElementTypeCell,XCUIElementTypeApplication
#android.titleSelector=<#action_bar||#toolbar||#app_bar>:descendant(${title}||@${title})
#ios.titleSelector=<.XCUIElementTypeNavigationBar>:descendant(.XCUIElementTypeStaticText&&${title})
#android.webTitleSelector=.android.webkit.WebView&&${webTitle}
#ios.webTitleSelector=<.XCUIElementTypeWebView>:descendant(${webTitle}&&visible=false)
#jquerySource=

多くのパラメーターは#でコメントアウトされています。必要になった場合は#を削除して設定を有効化します。 パラメーターの詳細についてはParametersを参照してください。

使用頻度の高いパラメーターのセクションを以下に紹介します。

Test Modeセクション

通常モードと無負荷実行モードの切り替えを行うことができます。

## Test mode --------------------
#noLoadRun=true

コメントを解除して

noLoadRun=true

を指定すると無負荷実行モードになります。無負荷実行モードでは実際のテスト実行を行わずテスト仕様書(Spec-Report)のみを出力します。

Logセクション

ログに関する設定を行うことができます。

## Log --------------------
logLanguage=ja
#enableSyncLog=false
#enableTestList=false
#enableSpecReport=false
#enableInnerMacroLog=true
#enableInnerCommandLog=true
#enableSilentLog=true
#enableTapElementImageLog=true
#enableXmlSourceDump=false
#enableRetryLog=false
#enableTrace=true
#enableShellExecLog=true
#enableTimeMeasureLog=true
#enableImageMatchDebugLog=true
#testResults=
#testListDir=

logLanguage
ログの言語です。デフォルトは英語です。日本語に設定する場合は

logLanguage=ja

を指定します。

testResults
ログの出力ディレクトリを変更する場合は書き換えます。デフォルトはダウンロードディレクトリ下のTestResultsフォルダです。

Appiumセクション

Appiumに関する設定を行うことができます。(Appiumのより詳細な設定はtestConfigファイルで行います。)

## Appium --------------------
appiumServerUrl=http://127.0.0.1:4720/
appiumPath=appium
appiumArgs=--session-override --relaxed-security
#appiumArgsSeparator=
#appiumServerStartupTimeoutSeconds=
#appiumSessionStartupTimeoutSeconds=
#implicitlyWaitSeconds=
#appPackageFile=
#appPackageDir=
#packageOrBundleId=
#startupPackageOrBundleId=
#startupActivity=

appiumServerUrl
Appiumで使用するURLを変更したい場合は書き換えます。たとえばポート番号を4721に変更したい場合は以下のように書き換えます。

appiumServerUrl=http://127.0.0.1:4721/

appiumArgs
Appiumの起動オプションを変更したい場合は書き換えます。

日本語モードでのテストの実行

日本語に設定した状態で前回の記事で作成したプログラムを実行すると以下のように出力されます。

比較のために英語モードでの出力を以下に記載します。

これらの出力は単一のテストコードから出力しています。

このようにShiratesは日本語による可読性の高いログの出力をサポートしています。