Shiratesを使ってみよう - 画面ニックネーム -(その2)
こちらの記事の翻訳です。
サンプルコードの入手
本記事で説明するサンプルコードの完成版はこちらから入手してください。 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ファイルで定義することができます。画面ニックネームはテストコードを読みやすく生産性を高いものにします。