waveさんの技術日誌

wave1008の日記の新館です。

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