[软件自动化] 怎么对Android平台上的列表进行自动化操作

Posted by Liler on July 20, 2022

在对Android平台上的APP进行自动化时,经常遇到列表,例如菜单列表、信息流列表等。 为什么大部分信息用列表的形式出现呢?我想这个手机的屏幕有关,手机屏幕基本上又小 又细长,为了让信息适应这样的屏幕,用列表显示信息就正常不过了。

既然APP里面的信息大部分都是通过列表显示的,那么对APP进行自动化时就必须考虑怎么自动化 列表了。显然自动化操作列表对于自动化APP是比较重要的,大部分时间都是操作列表。

那么怎样对列表进行操作呢?仔细观察后,你就会知道列表有两种形式: 纵向列表横向列表 。 纵向列表比较常见,大部分比较多的信息以这种形式展示。横向列表与纵向列表除了方向上面的 不同外,其他基本上是一样的,本文只关注纵向列表。

这些列表在本文中统称为 数据列表 (Datalist).

看下纵向列表的图片(以Twitter为例):

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)

这些纵向列表有什么共同特征呢,有什么共同操作呢? 为了了解这些信息,我们用 appium-inspector 来解析页面。

首先来看这些页面包含哪些部分:

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)

通过观察这些图片我们可以知道列表主要包含下面的几个部分:

  • 列表根元素 (root element)

    根元素定义了列表的最大显示区域及其内容。注意上面的最大显示区域的最上面 被Tabs bar元素掩盖了一部分。有些最大显示区域没有被掩盖,有些则被掩盖了 (有的在最上面,有的在最下面)。

  • 列表最外层子元素 (outermost element)

    这个定义了子元素的显示区域及其内容。有些会包含多个子元素,例如Tweet最外层 子元素除了Tweet内容外,还包含统计信息的显示条。

  • 列表最内层子元素 (innermost element)

    这个子元素也叫内容根元素(content root element),定义了信息的显示区域及其内容。 这个元素下面还可能包含多个子信息元素。

  • 一屏信息列表

    顾名思义,就是在某个时间点的一屏幕所显示的所有信息元素。从上到下,可以分为 第一个子元素,第二个子元素,...

    第一个子元素或者最后一个子元素都有可能被其他元素遮盖。

  • 列表掩盖元素列表遮盖元素)(cover element)

    这个元素在列表的最上面或者最下面,它的显示区域与列表根元素的显示区域有重叠。

那么对这些元素或者组成部分进行哪些操作呢?

  • 获取根元素及其显示区域

  • 获取所有的最外层子元素

  • 获取所有的最内层子元素

  • 定义掩盖元素的位置与获取掩盖元素的显示区域

  • 计算去掉遮盖元素显示区域的列表可视显示区域(列表有效显示区域)(列表视窗) (View window)

  • 判断元素是否被部分或全部遮盖

  • 获取元素的文本内容

  • 滑动元素(向上或者向下)

  • 打开某个元素(点击某个元素)(需要定义点击在元素的哪个位置)

  • 一边滑动列表一边做某种操作(重要的操作,毕竟操作列表就是为了做某些操作的)

  • 一边滑动列表一边打开某个元素一边做某种操作(重要的操作)

...

还有一些其他共同操作就不一一列举了。请查看下面的一些代码,可以了解有哪些共同的操作。

Datalist实现代码片段(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

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实现代码片段(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

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

本文关注的是怎么设计自动化操作Android平台上的列表,不关注怎么具体实现。 实际上根据上面的分析与我的实现代码框架,你可以写出属于你的实现代码。

Please leave your comment: