Creating screen nickname file

Creating screen nickname file is important to make testing easy.

There are several patterns with defining unique screen identity.

  1. Fixed layout
  2. Scrolling layout with fixed header
  3. Scrolling layout without fixed header

The latter is harder than the former.

Example 1: Fixed layout

Open Android Settings app.

Let’s see [Connected devices Screen] as example.

  1. Create screens directory (if it does not exist).
  2. Create a file with name [Connected devices Screen].json.

  1. Edit content as follows. key must be the same as the file name(without extension). ( See Screen nickname )
{
  "key": "[Connected devices Screen]",

  "identity": "",

  "selectors": {
  }
}
  1. Capture Connected Devices screen in Appium Inspector. (See Using Appium Inspector)

    1. Start appium.
    2. Start Android 14 emulator.
    3. Start Appium Inspector.
    4. Edit capabilities for Android 14 emulator. Click Start Sessionto start session of Settings app.
    5. Tap Connected devices in the emulator.
    6. Click Refresh source & screenshot in Appium Inspector to capture Connected devices screen.
  2. Look at the screen and define selector names. At this point values may be "" (empty).

{
  "key": "[Connected devices Screen]",

  "identity": "",

  "selectors": {
    "[<-]": "",
    "[Connected devices]": "",
    "[+]": "",
    "[Pair new device]": "",
    "[Previously connected devices]": "",
    "[>]": "",
    "[See all]": "",
    "[Connection preferences]": "",
    "{Connection preferences}": "",
    "[i]": "",
    "{Information}": ""
  }
}
  1. Inspect each elements to find unique attribute(s). (See Selector expression)
    • [<-] is determined uniquely by the condition that content-desc is ‘Navigate up’.

      So you can define the selector as follows using accessibility filter.
      "[<-]": "@Navigate up"

    • [Connected devices] is uniquely determined by the condition that content-desc is ‘Connected devices’.

      So you can define the selector as follows using accessibility filter.
      "[Connected devices]": "@Connected devices"

    • [Pair new device] is uniquely determined by text.

      So you can define the selector as follows using text filter.
      "[Pair new device]": "Pair new device"

      In this case, the value can be omitted because the text is the same as nickname label(without brackets). ( See Selector nickname)
      "[Pair new device]": ""

    • [+] is not uniquely determined by its attributes.

      In this case, consider using Relative selector. You can define the selector as follows using :leftImage. See Relative selector(Direction based)
      "[+]": "[Pair new device]:leftImage"

    • [Connection preferences] is uniquely determined by text.

      So you can define the selector as follows using text filter.
      "[Connection preferences]": ""

    • Bluetooth, Android Auto is dynamic content of [Connection preferences], and is not uniquely determined by its attributes.

      In this case, consider using Relative selector. You can define the selector as follows using :belowLabel. See Relative selector(Direction based)
      "{Connection preferences}": "[Connection preferences]:belowLabel"

    • Inspect the rest of elements and edit all selector nicknames. Finally, you can get selectors as follows.
{
  "key": "[Connected devices Screen]",

  "identity": "",

  "selectors": {
    "[<-]": "@Navigate up",
    "[Connected devices]": "@Connected devices",
    "[+]": "[Pair new device]:leftImage",
    "[Pair new device]": "",
    "[Previously connected devices]": "",
    "[>]": "[See all]:leftImage",
    "[See all]": "",
    "[Connection preferences]": "",
    "{Connection preferences}": "[Connection preferences]:belowLabel",
    "[i]": "{Information}:aboveImage",
    "{Information}": "Visible as *"
  }
}
  1. Set identity combining selector nicknames to make it unique identity.
{
  "key": "[Connected devices Screen]",

  "identity": "[Connected devices][Pair new device][See all]",

  "selectors": {
    "[<-]": "@Navigate up",
    "[Connected devices]": "@Connected devices",
    "[+]": "[Pair new device]:leftImage",
    "[Pair new device]": "",
    "[Previously connected devices]": "",
    "[>]": "[See all]:leftImage",
    "[See all]": "",
    "[Connection preferences]": "",
    "{Connection preferences}": "[Connection preferences]:belowLabel",
    "[i]": "{Information}:aboveImage",
    "{Information}": "Visible as *"
  }
}
  1. Create ConnectedDevicesTest.kt under kotlin/exercise directory.
package exercise

import org.junit.jupiter.api.Test
import shirates.core.configuration.Testrun
import shirates.core.driver.commandextension.*
import shirates.core.testcode.UITest

@Testrun("testConfig/android/androidSettings/testrun.properties")
class ConnectedDevicesTest : UITest() {

    @Test
    fun test1() {

        scenario {
            case(1) {
                condition {
                    it.tap("Connected devices")
                }.expectation {
                    it.screenIs("[Connected devices Screen]")
                        .exist("[<-]").accessIs("Navigate up")
                        .exist("[Connected devices]").accessIs("Connected devices")
                        .exist("[+]").classIs("android.widget.ImageView")
                        .exist("[Pair new device]").textIs("Pair new device")
                        .exist("[Previously connected devices]").textIs("Previously connected devices")
                        .exist("[>]").classIs("android.widget.ImageView")
                        .exist("[See all]").textIs("See all")
                        .exist("[Connection preferences]").textIs("Connection preferences")
                        .exist("{Connection preferences}").textIs("Bluetooth, Android Auto")
                        .exist("[i]").classIs("android.widget.ImageView")
                        .exist("{Information}").textStartsWith("Visible as ")
                }
            }
            case(2) {
                action {
                    it.tap("[<-]")
                }.expectation {
                    it.exist("Network & internet")
                }
            }
        }
    }

}
  1. Run test. You can get a Html-report as follows when configured correctly.


Example 2: Scrolling layout with fixed header

Open Android Settings app.

Let’s see [Network & internet Screen] as example.

Optimizing end of scroll Optimizing end of scroll

This screen is scrollable and has fixed header area <#collapsing_toolbar>. In this case, you can define identity as follows.

{
  "key": "[Network & internet Screen]",

  "identity": "#collapsing_toolbar&&@Network & internet",

This is enough, but you can simply use title selector. ( See title selector)

{
  "key": "[Network & internet Screen]",

  "identity": "~title=Network & internet",


Screen nickname file for [Network & internet Screen] can be described as follows.

[Network & internet Screen].json

{
  "key": "[Network & internet Screen]",

  "include": [
  ],

  "identity": "~title=Network & internet",

  "selectors": {
    "[<-]": "@Navigate up",
    "[Network & internet]": "@Network & internet",

    "[Internet]": "",
    "{Internet}": "[Internet]:label",

    "[Calls & SMS]": "",
    "{Calls & SMS}": "[Calls & SMS]:label",

    "[SIMs]": "",
    "{SIMs}": "[SIMs]:label",

    "[Airplane mode]": "",
    "{Airplane mode switch}": "[Airplane mode]:switch",

    "[Hotspot & tethering]": "",
    "{Hotspot & tethering}": "[Hotspot & tethering]:label",

    "[Data Saver]": "",
    "{Data Saver}": "[Data Saver]:label",

    "[VPN]": "",
    "{VPN}": "[VPN]:label",

    "[Mobile plan]": "",
    "{Mobile plan}": "[Mobile plan]:label",

    "[Private DNS]": "",
    "{Private DNS}": "[Private DNS]:label",

    "[Adaptive connectivity]": "",
    "{Adaptive connectivity}": "[Adaptive connectivity]:label",

  },

  "scroll": {
    "start-elements": "",
    "end-elements": "[Adaptive connectivity]",
    "overlay-elements": ""
  }
}

:label

In this example, relative selector:label is used instead of :belowLabel. :label searches the nearest label in widget flow.

See Relative selector(Widget flow based) .

:switch

Relative selector :switch searches the nearest switch in widget flow.

See Relative selector(Widget flow based) .

“scroll” section

"scroll" section is for optimization (optional).

start-elements and end-elements are for detecting end of scroll efficiently. See end of scroll

overlay-elements is for detecting overlay elements. Elements under overlay-elements are considered as not displayed.


Example 3: Scrolling layout without fixed header

Open Android Settings app.

Let’s see [Android Settings Top Screen] as example.

  1. Open shirates-core project.
  2. Open [Android Settings Top Screen].json under testConfig/android/androidSettings/screens.
{
  "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 & privacy]": "",
    "{Security & privacy}": "[Security & privacy]:label",
    "[Security & privacy Icon]": "[Security & 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",

    "[Digital Wellbeing & parental controls]": "",
    "{Digital Wellbeing & parental controls}": "[Digital Wellbeing & parental controls]:label",
    "[Digital Wellbeing & parental controls Icon]": "[Digital Wellbeing & parental controls]: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",

    "[:Summary]": ":belowLabel"
  },

  "scroll": {
    "header-elements": "[Search Button][Search settings]",
    "overlay-elements": "",
    "start-elements": "[Network & internet]",
    "end-elements": "{Tips & support}"
  }
}


“identity” and “satellites”

If you can always access elements that consist of screen identity, you should use these elements for identity. The former Example 1 and Example 2 are in this case.

If you can not always access such elements, you should consider another workaround.

Optimizing end of scroll

In case of [Android Settings Top Screen], you can scroll up or down.

This screen does not have fixed header area. While scrolling there is no position-fixed element for "identity". In this case you can use "satellites" to complement unique key for screen identification. "satellites is a list of selector.

In [Android Settings Top Screen].json, you can specify "identity" and "satellites" as follows.

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

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

This means that the screen is considered as "[Android Settings Top Screen]" when the screen has "#recycler_view" and has at least one of "Battery", "Accessibility" , "Passwords & accounts" or "Tips & support". The complex key(consists of identity and satellite key) must be unique over screens.

Link