How to use Screen Nickname in Shirates - Part2 -
Tags: appium, android, ios, testautomation
Published: 2022-10-16 14:12:24

This article is an introduction of how to use [Shirates](https://dev.to/wave1008/introducing-shirates-a-mobile-testing-automation-tool-3nmc), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i4hxpnztn0dwr3grmhgg.png)


### System Screen/Language & input Screen/Languages Screen
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdj65dwinpokygljijc3.png)

### [Android Settings Top Screen].json
```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]"
  }
}
```

#### 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.

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

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](https://github.com/ldi-github/shirates-core/blob/main/doc/markdown/basic/selector_and_nickname/relative_selector/relative_selector.md) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktgdttb0dliw2nt3295g.png)

`"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.

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

`:label` is relative selector that selects next label on Widget flow. See [Relative selector(Widget flow based)](https://github.com/ldi-github/shirates-core/blob/main/doc/markdown/basic/selector_and_nickname/relative_selector/relative_selector_flow.md)

`:leftImage` is relative selector that selects left image. See [Relative selector(Direction based)](https://github.com/ldi-github/shirates-core/blob/main/doc/markdown/basic/selector_and_nickname/relative_selector/relative_selector_direction.md).

---

### [System Screen].json

```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]": ""
  }

}
```

### [Languages & input Screen].json
```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]": ""
  }

}
```

### [Languages Screen].json

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

  "include": [
  ],

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

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

    "[Add a language]": ""
  }

}
```

### 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**.


```kotlin
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]")
                }
            }
        }
    }

}
```

---

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3xdrho2y4o9bo42univ.png)

#### `AndroidSettingsTest@a.xlsx`


![Comparison Spec-Report](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2xkgs2tj2x1ivjmnr6ha.png)

---

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