DEV Community

wave1008
wave1008

Posted on • Updated on

How to use Screen Nickname in Shirates - Part2 -

This article is an introduction of how to use Shirates, a mobile testing automation tool.

Material

You can get complete sample project from [https://github.com/wave1008/shirates-samples-nicknames].


Example 2: Android Settings

This sample shows how to use screen nickname using four screens in Android Settings app.

Settings Screen

Settings App

System Screen/Language & input Screen/Languages Screen

Image description

[Android Settings Top Screen].json

{
  "key": "[Android Settings Top Screen]",

  "identity": "#recycler_view",
  "satellites": ["Battery", "Accessibility", "Passwords & accounts", "Tips & support"],

  "selectors": {
    "[Account Avatar]": "#account_avatar",
    "[Settings]": "#homepage_title",

    "[Search Button]": "<#search_action_bar>:inner(1)",
    "[Search settings]": "#search_action_bar_title",

    "[Network & internet]": "",
    "{Network & internet}": "[Network & internet]:label",
    "[Network & internet Icon]": "[Network & internet]:leftImage",

    "[Connected devices]": "",
    "{Connected devices}": "[Connected devices]:label",
    "[Connected devices Icon]": "[Connected devices]:leftImage",

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

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

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

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

    "[Sound & vibration]": "",
    "{Sound & vibration}": "[Sound & vibration]:label",
    "[Sound & vibration Icon]": "[Sound & vibration]:leftImage",

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

    "[Wallpaper & style]": "",
    "{Wallpaper & style}": "[Wallpaper & style]:label",
    "[Wallpaper & style Icon]": "[Wallpaper & style]:leftImage",

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

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

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

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

    "[Safety & emergency]": "",
    "{Safety & emergency}": "[Safety & emergency]:label",
    "[Safety & emergency Icon]": "[Safety & emergency]:leftImage",

    "[Passwords & accounts]": "",
    "{Passwords & accounts}": "[Passwords & accounts]:label",
    "[Passwords & accounts Icon]": "[Passwords & accounts]:leftImage",

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

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

    "[About emulated device]": "",
    "{About emulated device}": "[About emulated device]:label",
    "[About emulated device Icon]": "[About emulated device]:leftImage",

    "[About phone]": "",
    "{About phone}": "[About phone]:label",
    "[About phone Icon]": "[About phone]:leftImage",

    "[Tips & support]": "",
    "{Tips & support}": "[Tips & support]:label",
    "[Tips & support Icon]": "[Tips & support]:leftImage"
  },

  "scroll": {
    "start-elements": "",
    "end-elements": "{Tips & support}",
    "overlay-elements": "[Search Button][Search settings]"
  }
}
Enter fullscreen mode Exit fullscreen mode

identity with satellites

Android Settings app has scrollable view and has no fixed identifier that displayed always. In this case, you can use identity with satellites to identify displayed screen.

  "identity": "#recycler_view",
  "satellites": ["Battery", "Accessibility", "Passwords & accounts", "Tips & support"],
Enter fullscreen mode Exit fullscreen mode

Above example shows that "identity": "#recycler_view" is always displayed(but not unique identifier), "satellites" is a list of members that any of members are displayed on the Android Settings view. "identity"+"satellites" is recognized as unique identifier of the screen.

Relative Selector

Appium locators seems to assume that every element has unique attribute or to be implemented. But in the real world, especially for scrolling views, there are many elements that has no unique identifier.

In Shirates, we introduced Relative Selector to resolve this theme. When a UI element group has at least one element that has unique identifier, you can get elements around it relatively.

See following image.

Battery

"Battery" is static content and unique.
"100%" is dynamic content and has no unique identifier.
Battery Icon has no unique identifier.

In this case, you can define selector nickname as follows.

    "[Battery]": "",
    "{Battery}": "[Battery]:label",
    "[Battery Icon]": "[Battery]:leftImage",
Enter fullscreen mode Exit fullscreen mode

:label is relative selector that selects next label on Widget flow. See Relative selector(Widget flow based)

:leftImage is relative selector that selects left image. See Relative selector(Direction based).


[System Screen].json

{
  "key": "[System Screen]",

  "include": [
  ],

  "identity": "[<-][System]",

  "selectors": {
    "[<-]": "@Navigate up",
    "[System]": "@System",

    "[Languages & input]": "",
    "{Languages & input}": "[Languages & input]:belowLabel",

    "[Gestures]": "",

    "[Date & time]": "",
    "{Date & time}": "[Date & time]:belowLabel",

    "[Backup]": "",

    "[System update]": "",
    "{System update}": "[System update]:belowLabel",

    "[Rules]": "",
    "{Rules}": "[Rules]:belowLabel",

    "[Multiple users]": "",
    "{Multiple users}": "[Multiple users]:belowLabel",

    "[Developer options]": "",
    "{Developer options}": "[Developer options]:belowLabel",

    "[Reset options]": ""
  }

}
Enter fullscreen mode Exit fullscreen mode

[Languages & input Screen].json

{
  "key": "[Languages & input Screen]",

  "include": [
  ],

  "identity": "[<-][Languages & input]",

  "selectors": {
    "[<-]": "@Navigate up",
    "[Languages & input]": "@Languages & input",

    "[Languages]": "",
    "{Languages}": "[Languages]:belowLabel",
    "[Languages Icon]": "[Languages]:leftImage",

    "[Keyboards]": "",

    "[On-screen keyboard]": "",
    "{On-screen keyboard}": "[On-screen keyboard]:belowLabel",

    "[Physical keyboard]": "",
    "{Physical keyboard}": "[Physical keyboard]:belowLabel",

    "[Tools]": "",

    "[Spell checker]": "",
    "{Spell checker}": "[Spell checker]:belowLabel",

    "[Personal dictionary]": "",
    "{Personal dictionary}": "[Personal dictionary]:belowLabel",

    "[Pointer speed]": "",

    "[Text-to-speech output]": ""
  }

}
Enter fullscreen mode Exit fullscreen mode

[Languages Screen].json

{
  "key": "[Languages Screen]",

  "include": [
  ],

  "identity": "[<-][Languages]",

  "selectors": {
    "[<-]": "@Navigate up",
    "[Languages]": "@Languages",

    "[Add a language]": ""
  }

}
Enter fullscreen mode Exit fullscreen mode

AndroidSettingsTest

The following is Android Settings test code without or with Screen Nickname file. In case of without Screen Nickname file, you have to use low level identifier such as content-desc. On the other hand, in case of with Screen Nickname file, you can use abstract Screen Nickname/Selector Nickname.

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("Settings")
                        .existWithScrollDown("Battery||Accessibility||Passwords & accounts||Tips & support")
                }.action {
                    it.tapWithScrollDown("System")
                }.expectation {
                    it.exist("@Navigate up")
                        .exist("@System")
                }
            }
            case(2) {
                action {
                    it.tap("Languages & input")
                }.expectation {
                    it.exist("@Navigate up")
                        .exist("@Languages & input")
                }
            }
            case(3) {
                action {
                    it.tap("Languages")
                }.expectation {
                    it.exist("@Navigate up")
                        .exist("@Languages")
                }
            }
        }
    }

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

        scenario {
            case(1) {
                condition {
                    it.tapAppIcon("Settings")
                        .screenIs("[Android Settings Top Screen]")
                }.action {
                    it.tapWithScrollDown("[System]")
                }.expectation {
                    it.screenIs("[System Screen]")
                        .exist("[<-]")
                        .exist("[System]")
                }
            }
            case(2) {
                action {
                    it.tap("[Languages & input]")
                }.expectation {
                    it.screenIs("[Languages & input Screen]")
                        .exist("[Languages]")
                }
            }
            case(3) {
                action {
                    it.tap("[Languages]")
                }.expectation {
                    it.screenIs("[Languages Screen]")
                }
            }
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

Test Result

Look and compare without Screen Nickname and with Screen Nickname. You can find that with Screen Nickname is easier to understand.

_Report(simple).html

Comparison Html

AndroidSettingsTest@a.xlsx

Comparison Spec-Report


Conclusion

In Shirates, you can define Screen Nickname as JSON file. Screen Nickname makes test codes readable and productive.

Top comments (0)