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):
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:
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.