[Software Automation] How to operate list on Android automatically

Posted by Liler on July 20, 2022

We often encounter list when automating APP on Android, for example, menu list, information stream list, and so on. Why a lot of information is displayed in the form of list? I think this is because of the screen of phone, and the screen is small and long, so in order to fit it, the displaying of the information through list is normal and natural.

So most information is displayed by list, we must think about how to operate it when automating APP. Obviously, It's important to operate list automatically, and at most time we operate it.

How to operate it? After obeserving, you will know there are two different lists: Vertical list, and Horizontal list. Vertical list is general, and much infomation is displayed in the form of it. Apart from the difference of the direction, the horizontal list is the same with the vertical list. We just focus on vertical list in this text.

These lists are collectively referred to as data lists in this article(Datalist)

Please see some images of vertical list(Twitter as an example):

Main page Information Stream(Tweets)

Main page Information Stream(Tweet list)

Tweet list

Tweet list(Profile page)

Menu list

Menu list

Search result(tweet list)

Search result(tweet list)

Search result(account list)

Search result(account list)

Are there common characteristics and operations for them? Now, for the sake of finding them, we use appium-inspector to inspect the structure of these pages.

First, Check what parts do these pages include:

Datalist root element

Datalist root element

Datalist item element(outermost element)

Datalist item element(outermost element)

Datalist first item element(Covered by another element)

Datalist first item element(Covered by another element)

Datalist cover element(covered the item element)

Datalist cover element(covers the item element)

Datalist innermost element(content root element)

Datalist innermost element(content root element)

We know the data list consists of the following parts after noticing these images:

  • list root element

    It defines the max display area and its content. Note the top of the above display area is partially covered by the element Tabs bar. Some such display areas are not covered, while others are covered up (covered at the top, or covered at the bottom)

  • list outermost element (list item element)

    This defines the display area and its content of the child element of the above element. Some includes multiple child elements of it, such as the tweet element which is the outermost layer element of the root element, and this tweet element includes the display bar of statistical information except Tweet content.

  • list innermost element)

    This element is also called as content root element, which defined the display area and content of information, and maybe include multiple child elements of information.

  • One screen list

    As the name suggests, it includes all information elements displayed at a screen at a certain time. From top to bottom, it can be divided into: The first child element, the second child element, ...

    The first child element or the last child element may be covered by other element.

  • list cover element

    This element is at the top or bottom of the list, and its display area overlaps with the display area of the list root element.

So what operations for these elements or components?

  • Get the root element and its display area

  • Get all the outermost layer elements

  • Get all the innermost layer elements

  • Define the position of the cover element and get the display area of it

  • Caculate the viewport window of the list by reducing the covering element's display area (the effective display area) (list viewport window)

  • Check whether the item element is partial or all covered

  • Get text content of the item element

  • Slide the element (up or down)

  • Open an item element (click it) (need to define which position of the clicking on the element)

  • Do some operation while while sliding list (important operation, after all we operate list in order to do some operation)

  • Do some operation after opening an item element while sliding list (important operation)

...

Some other common operations are not listed one by one. Please check the following code to learn what common operations are available.

Datalist implementation code episode(the configure in the class Datalist):

import random
import time

try:
    from appium.webdriver.common.appiumby import AppiumBy as By
except ModuleNotFoundError:
    from appium.webdriver.common.mobileby import MobileBy as By
from collections.abc import Iterable
from .widget import Widget
from utils.common import is_cmd


class Datalist(Widget):
    # list root element
    root_element_xpath = ''
    root_element_locators = ()

    # list item element
    outermost_item_element_xpath = f'{root_element_xpath}/*'
    outermost_item_element_locators = (
        (By.XPATH, outermost_item_element_xpath),
    )
    innermost_item_element_locators = ()

    # list content element
    content_root_element_locators = innermost_item_element_locators

    # View window cover element
    # If locatos of it is not blank, but cannot find any element, then ignore cover element
    # in order to be compatible with this type list which has no cover element
    cover_element_locators = ()
    cover_element_position = 'bottom'   # or top
    cover_element_fixed = True

    # if the the view port is changed at the beginning of swipe,
    # the swipe will affect the postion and height of view port,
    # then should get the view port every time
    is_view_port_changed = False

    # There are some type of elements whick cannot be swiped
    skip_elements_locators = []

    pname = 'Data list'

    tap_duration = 400
    duration = 4000
    x_delta = 100
    y_delta = 2
    slide_times = 10
    # If the length between start_y and end_y is less or equal to the value, then skip this element
    skip_length = 50
    item_element_level = 'outermost'    # or innermost, for getting item element

The methods and variables of class Datalist:

▾+Datalist : class
  +__init__(self, driver, logger=None, timeout=None) : member
  +get_attribute_of_content_element_from_root(self, atrribute, locators, content_root_element, ename=None) : member
  +get_content_element_from_root(self, locators, content_root_element, ename=None) : member
  +get_content_elements_from_root(self, locators, content_root_element, ename=None) : member
  +get_content_root_elements(self) : member
  +get_content_root_elements_in_view_window(self, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +get_cover_element(self) : member
  +get_cover_element_dimensions(self) : member
  +get_elements_id_of_skip_item_by_locators(self) : member
  +get_first_item_text(self) : member
  +get_innermost_elements(self) : member
  +get_innermost_elements_in_view_window(self, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +get_item_element_dimensions_in_view_window(self, item_element) : member
  +get_item_elements_in_view_window(self, element_level=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +get_list_view_window(self, root_element=None) : member
  +get_one_item_all_text(self, text_type='text', index=0, element_level=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +get_one_item_element_in_view_window(self, index=0, element_level=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +get_outermost_elements(self, timeout=None, post_cmd=None) : member
  +get_outermost_elements_in_view_window(self, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +get_root_element(self) : member
  +get_root_element_window(self, root_element=None) : member
 ▸+is_item_element_display(self, item_element) : member
  +is_skip_item_by_length_from_view_window(self, item_element, skip_length=None) : member
  +is_skip_item_by_locators(self, item_element, skip_elements_ids) : member
  +tap_first_item_in_view_window(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +tap_item_element(self, item_element, position='left', duration=None, x_delta=None, y_delta=None) : member
  +tap_last_item_in_view_window(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
  +tap_one_item_in_view_window(self, index=0, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
   [variables]
  +content_root_element_locators
  +cover_element_fixed
  +cover_element_locators
  +cover_element_position
  +duration
  +innermost_item_element_locators
  +is_view_port_changed
  +item_element_level
  +outermost_item_element_locators
  +outermost_item_element_xpath
  +pname
  +root_element_locators
  +root_element_xpath
  +skip_elements_locators
  +skip_length
  +slide_times
  +tap_duration
  +x_delta
  +y_delta

VerticalDatalist implementation code episode(the configure in the class VerticalDatalist):

class VerticalDatalist(Datalist):
    # list root element
    root_element_xpath = ''
    root_element_locators = ()

    # list item element
    outermost_item_element_xpath = f'{root_element_xpath}/*'
    outermost_item_element_locators = (
        (By.XPATH, outermost_item_element_xpath),
    )
    innermost_item_element_locators = ()

    # list content element
    content_root_element_locators = innermost_item_element_locators

    # View window cover element
    # If locatos of it is not blank, but cannot find any element, then ignore cover element
    # in order to be compatible with this type list which has no cover element
    cover_element_locators = ()
    cover_element_position = 'bottom'   # or top
    cover_element_fixed = True

    # if the the view port is changed at the beginning of swipe,
    # the swipe will affect the postion and height of view port,
    # then should get the view port every time
    is_view_port_changed = False

    # There are some type of elements whick cannot be swiped
    skip_elements_locators = []

    pname = 'Vertical Data list'

    tap_duration = 400
    duration = 4000
    x_delta = 100
    y_delta = 2
    slide_times = 10
    # If the length between start_y and end_y is less or equal to the value, then skip this element
    skip_length = 50
    item_element_level = 'outermost'    # or innermost, for getting item element

The methods and variables of class VerticalDatalist:

▾+VerticalDatalist : class
   +__init__(self, driver, logger=None, timeout=None) : member
   +find_and_click_element_by_text(self, text, locators, results=None, is_case_sensisive=False) : member
   +find_element_by_text(self, text, locators, results=None, is_case_sensisive=False) : member
   +flick_item_vertically(self, item_element, position='left', swipe_from='top', swipe_to='top', x_delta=None, y_delta=None, skip_length=None) : member
   +get_element_from_elements(self, elements, locators, text, is_case_sensisive=False) : member
   +get_text_from_elements(self, elements, locators) : member
   +random_slide_list(self, slide_times=None, position='left', swipe_from_top_chance=5, swipe_from_bottom_chance=5, swipe_to_top_chance=10, swipe_to_bottom_chance=5, swipe_to='top', item_index=None, duration=None, x_delta=None, y_delta=None, skip_length=None, cmd=None, cmd_kwargs=None, cmd_results=None, is_check_cmd_result=False, is_fix_swip_bug=True, is_random_item_index=False, slide_method_swipe_chance=5, slide_method_flick_chance=5, when_run_cmd='before', is_sleep_after_slide=False, sleep_time=None) : member
   +slide_item_vertically(self, item_element, position='left', swipe_from='top', swipe_to='top', duration=None, x_delta=None, y_delta=None, skip_length=None, slide_method='swipe') : member
   +slide_list(self, slide_times=None, position='left', swipe_from='top', swipe_to='top', item_index=None, duration=None, x_delta=None, y_delta=None, skip_length=None, cmd=None, cmd_kwargs=None, cmd_results=None, is_check_cmd_result=False, is_fix_swip_bug=True, is_random_item_index=False, slide_method='swipe', when_run_cmd='before', is_sleep_after_slide=False, sleep_time=None, element_level=None, is_cmd_change_page=False, require_no_elements=False, is_fix_no_moving=True) : member
   +swipe_first_item_bottom_to_bottom(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
   +swipe_first_item_bottom_to_top(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
   +swipe_first_item_top_to_bottom(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
   +swipe_item_bottom_to_bottom(self, item_element, position='left', duration=None, x_delta=None, y_delta=None, skip_length=None) : member
   +swipe_item_bottom_to_top(self, item_element, position='left', duration=None, x_delta=None, y_delta=None, skip_length=None) : member
   +swipe_item_top_to_bottom(self, item_element, position='left', duration=None, x_delta=None, y_delta=None, skip_length=None) : member
   +swipe_item_top_to_top(self, item_element, position='left', duration=None, x_delta=None, y_delta=None, skip_length=None) : member
   +swipe_item_vertically(self, item_element, position='left', swipe_from='top', swipe_to='top', duration=None, x_delta=None, y_delta=None, skip_length=None) : member
   +swipe_last_item_bottom_to_top(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
   +swipe_last_item_top_to_bottom(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
   +swipe_last_item_top_to_top(self, position='left', duration=None, x_delta=None, y_delta=None, is_check_skip_length=False, skip_length=None, is_check_skip_locators=False) : member
    [variables]
   +content_root_element_locators
   +cover_element_fixed
   +cover_element_locators
   +cover_element_position
   +duration
   +innermost_item_element_locators
   +is_view_port_changed
   +item_element_level
   +outermost_item_element_locators
   +outermost_item_element_xpath
   +pname
   +root_element_locators
   +root_element_xpath
   +skip_elements_locators
   +skip_length
   +slide_times
   +tap_duration
   +x_delta
   +y_delta

This article focuses on how to design the solution of operating data list automatically on Android, and does not pay attention to how to implement it. In fact, according to the above analysis and the structure of my implementation code, you can write out it yourself.

Please leave your comment: