【PYTHON】使用Appium自动化工具定位页面元素的方法

1 项目背景

数据采集业务中,某视频平台的数据大多数是用户上传真实场景的视频,质量较高;而该平台只提供移动端应用内访问,不支持使用浏览器访问,也未提供桌面端,数据获取难度较大。在该项目中,我们积累较多移动端应用数据采集的经验,此次从元素定位的角度出发,对比不同的元素定位方法的使用场景和优劣势分析;

2 定位固定属性元素

如下图,返回按钮是典型的固定元素;图中左侧窗口为应用当前画面,中间窗口为该画面对应源码,右侧窗口为选中元素的属性:

请添加图片描述
以当前页面的返回按钮为例,我们可以通过右侧窗口展示的元素属性,用不同方法定位到该元素。

2.1 通过 accessibility id 定位

def __traverse__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    while True:
        log.info(label + 'locate element start!')
        # 定位固定属性元素
        accessibilityid = '返回'
        element = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value=accessibilityid)
        description = element.get_attribute('content-desc')
        log.info(label + 'find element by accessibility id: {}'.format(description))

        break

    return status

结果如下:
请添加图片描述

2.2 通过 uiautomator 定位

def __traverse__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    while True:
        log.info(label + 'locate element start!')
        # 定位固定属性元素
        uiautomator = 'new UiSelector().description("返回")'
        element = self.driver.find_element(by=AppiumBy.ANDROID_UIAUTOMATOR, value=uiautomator)
        description = element.get_attribute('content-desc')
        log.info(label + 'find element by uiautomator: {}'.format(description))

        break

    return status

结果如下:
请添加图片描述

2.3 通过 classname 定位

def __traverse__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    while True:
        log.info(label + 'locate element start!')
        # 定位固定属性元素
        classname = 'android.widget.Button'
        elements = self.driver.find_elements(by=AppiumBy.CLASS_NAME, value=classname)
        log.info(label + 'find button number: {}'.format(len(elements)))
        for element in elements:
            description = element.get_attribute('content-desc')
            log.info(label + 'button[{}]: {}'.format(elements.index(element), description))

        break

    return status

结果如下:
请添加图片描述

2.4 通过 xpath 定位

def __traverse__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    while True:
        log.info(label + 'locate element start!')
        # 定位固定属性元素
        xpath = '//android.widget.Button[@content-desc="返回"]'
        element = self.driver.find_element(by=AppiumBy.XPATH, value=xpath)
        description = element.get_attribute('content-desc')
        log.info(label + 'find element by xpath: {}'.format(description))

        xpath = '//android.widget.Button[contains(@content-desc, "回")]'
        element = self.driver.find_element(by=AppiumBy.XPATH, value=xpath)
        description = element.get_attribute('content-desc')
        log.info(label + 'find element by xpath contains: {}'.format(description))

        break

    return status

结果如下:
请添加图片描述

2.5 优缺点

通过xpath定位元素提供更高的灵活性,不仅支持精确匹配,也支持通过文字的匹配等进行模糊查询,相对定位速度略低;其他几种方式在页面比较简单时,定位速度较快,但在复杂页面中,某些属性无值,这时属性就无法使用了。

3 定位固定位置元素

3.1 案例

有些页面元素在页面发生跳转/滑动等变化时,它的位置保持不变,或者元素很难通过元素属性定位,此时可以通过坐标定位该元素,如下:
请添加图片描述

上图中“听全部”的图标不方便通过属性定位,若它在下滑过程中,位置始终保持不变,则可以通过坐标定位到它,如下图:
请添加图片描述

上图是通过Appium Inspector调试时唤出的十字标,移动鼠标即可实时获取该位置的坐标,可以控制鼠标与页面元素进行交互;代码如下:

def __click_element__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    try:
        # 点击
        actions = ActionChains(self.driver)
        action_builder = ActionBuilder(self.driver, mouse=PointerInput(POINTER_TOUCH, "touch"))
        action_builder.pointer_action.move_to_location(444, 114)
        action_builder.pointer_action.pointer_down()
        action_builder.pointer_action.release()
        actions.w3c_actions = action_builder
        actions.perform()
        
        pass
    except Exception:
        log.error(label + traceback.format_exc())
        status = -1
    
    return status

3.2 优缺点

通过这种方式只能与元素进行交互,无法获取元素具体属性。

4 定位固定图案元素

4.1 案例

有些页面元素位置会随着操作改变,甚至同一页面不同时间,元素位置也会改变,若元素图案固定,则可以通过图案定位该元素,如下:

请添加图片描述

上图中,点赞的位置会随着点赞数不断变化,不同文章点赞的位置也不相同;文本也会随着时间推移发生变化,但是图标不会改变,此时,我们可以使用图标页面进行匹配,获取元素位置:

 def __locate_by_image__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    try:
        # # 调试过程中获取点赞页面的图片
        # # 截图
        # image = cv2.imdecode(np.frombuffer(base64.b64decode(self.driver.get_screenshot_as_base64()), dtype=np.uint8), cv2.IMREAD_COLOR)
        # # 保存截图
        # cv2.imwrite('./store/output/{}.png'.format(datetime.now().strftime('%Y%m%d.%H%M%S')), image)

        # 加载编辑得到的图标
        with open('./store/input/images/like.icon.png', 'rb') as reader:
            image_base64 = base64.b64encode(reader.read()).decode('utf-8')
        
        element = self.driver.find_element(by=AppiumBy.IMAGE, value=image_base64)

        # 点击元素
        element.click()

        pass
    except Exception:
        log.error(label + traceback.format_exc())
        status = -1

    return status
    
def __traverse__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    while True:
        log.info(label + 'locate element start!')
        # 定位固定图案元素 - 移动坐标
        if self.__locate_by_image__():
            break
        log.info(label + 'locate by image pass')

        break

    return status

4.2 优缺点

通过图案进行定位解决通过元素属性无法获取元素位置的的问题,且可以直接与元素交互;但是它除了交互之外无法获取元素属性,且对图像精度要求较高,抗干扰能力较弱;

5 定位固定图标元素

5.1 案例

有些页面会根据画面的整体颜色或图像分辨率填充画面,此时很难通过固定图案定位元素,如下:
在这里插入图片描述

在上面的图片中,图标相同,但是背景色差异巨大,此时无法通过固定图案定位元素;若直接使用坐标定位,需要判断页面结构是否发生变化(比如进入博主主页)以免发出错误的点击。
此时可以通过灰度图消除背景差异,通过灰度图与图标做相似度判断,进而获取图标位置,并与之交互,如下:
在这里插入图片描述

上图中,我们可以通过截图,获取灰度图,保存图标,使用图标与新页面进行灰度图对比,获取图标位置:

def __locate_by_gray_image__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    try:
        # # 调试过程中获取分享页面的图片
        # # 截图
        # image = cv2.imdecode(np.frombuffer(base64.b64decode(self.driver.get_screenshot_as_base64()), dtype=np.uint8), cv2.IMREAD_COLOR)
        # # 保存截图
        # cv2.imwrite('./store/output/{}.png'.format(datetime.now().strftime('%Y%m%d.%H%M%S')), image)
        # # 将截图转换为灰度图
        # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        # # 保存灰度图
        # cv2.imwrite('./store/output/{}.gray.png'.format(datetime.now().strftime('%Y%m%d.%H%M%S')), gray)

        # 截图
        image = cv2.imdecode(np.frombuffer(base64.b64decode(self.driver.get_screenshot_as_base64()), dtype=np.uint8), cv2.IMREAD_COLOR)
        # 将截图转换为灰度图
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        # 读取弹窗图标的灰度图
        icon = cv2.imread('./store/input/images/share-gray.png', cv2.IMREAD_GRAYSCALE)
        # 使用模板匹配
        result = cv2.matchTemplate(gray, icon, cv2.TM_CCOEFF_NORMED)
        # 获取匹配结果
        _, max_value, _, max_location = cv2.minMaxLoc(result)
        # 获取可信值
        self.confidence = max_value
        # 计算图标的中心位置
        self.location = (max_location[0] + icon.shape[1] // 2, max_location[1] + icon.shape[0] // 2)
        
        pass
    except Exception:
        log.error(label + traceback.format_exc())
        status = -1

    return status
    
def __traverse__(self) -> int:
    label = '{} '.format(multiprocessing.current_process().name)

    status = 0
    while True:
        log.info(label + 'locate element start!')
        # 定位固定图标元素
        if self.__locate_by_gray_image__():
            break
        log.info(label + 'locate by gray image pass, location: {}, confidence: {:.2f}%'.format(self.location, self.confidence * 100))

        break

    return status

结果如下(基本与使用Appium Inspector获取的坐标一样):
请添加图片描述
请添加图片描述

5.2 优缺点

通过图标进行定位削弱页面元素结构不变但位置/背景颜色等变化对元素定位带来的干扰,使用起来几乎无限制,自由度较高;但是它依赖OpenCV,不再兼容旧版Appium搭建的环境,可能会面临一些未知的环境和实现的冲突。

写在最后

您的鼓励是我持续更新的动力,感谢您的支持~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值