通过元素定位其各种层级关系元素的工具

# element_relation_locator.py
# 通过元素定位其各种层级关系元素的工具

from appium.webdriver.common.appiumby import AppiumBy

class ElementRelationLocator:
    """元素关系定位器类,用于通过元素定位其各种层级关系的元素"""
    
    def __init__(self, driver):
        """初始化元素关系定位器
        
        Args:
            driver: Appium WebDriver实例
        """
        self.driver = driver
    
    def get_element(self, locator_type, locator_value):
        """根据定位器获取目标元素
        
        Args:
            locator_type: 定位器类型,如AppiumBy.ID, AppiumBy.XPATH等
            locator_value: 定位器值
            
        Returns:
            WebElement: 找到的元素
        """
        return self.driver.find_element(locator_type, locator_value)
    
    def get_parent_element(self, element):
        """获取元素的父元素
        
        Args:
            element: 当前元素
            
        Returns:
            WebElement: 父元素
        """
        # 获取当前元素的ID或XPath
        element_id = element.get_attribute('resource-id')
        element_class = element.get_attribute('class')
        element_text = element.get_attribute('text')
        
        # 构建XPath来查找父元素
        if element_id:
            xpath = f"//*[@resource-id='{element_id}']/.."
        elif element_text:
            xpath = f"//*[@text='{element_text}']/.."
        else:
            xpath = f"//{element_class}/.."
        
        return self.driver.find_element(AppiumBy.XPATH, xpath)
    
    def get_grandparent_element(self, element):
        """获取元素的祖父元素(父元素的父元素)
        
        Args:
            element: 当前元素
            
        Returns:
            WebElement: 祖父元素
        """
        # 获取当前元素的ID或XPath
        element_id = element.get_attribute('resource-id')
        element_class = element.get_attribute('class')
        element_text = element.get_attribute('text')
        
        # 构建XPath来查找祖父元素
        if element_id:
            xpath = f"//*[@resource-id='{element_id}']/../.."
        elif element_text:
            xpath = f"//*[@text='{element_text}']/../.."
        else:
            xpath = f"//{element_class}/../.."
        
        return self.driver.find_element(AppiumBy.XPATH, xpath)
    
    def get_sibling_elements(self, element):
        """获取元素的所有同级元素(包括自身)
        
        Args:
            element: 当前元素
            
        Returns:
            list: 同级元素列表
        """
        parent = self.get_parent_element(element)
        # 获取父元素的所有直接子元素
        children = parent.find_elements(AppiumBy.XPATH, './*')
        return children
    
    def get_following_sibling_elements(self, element):
        """获取元素之后的同级元素(不包括自身)
        
        Args:
            element: 当前元素
            
        Returns:
            list: 后续同级元素列表
        """
        # 获取当前元素的ID或XPath
        element_id = element.get_attribute('resource-id')
        element_class = element.get_attribute('class')
        element_text = element.get_attribute('text')
        
        # 构建XPath来查找后续同级元素
        if element_id:
            xpath = f"//*[@resource-id='{element_id}']/following-sibling::*"
        elif element_text:
            xpath = f"//*[@text='{element_text}']/following-sibling::*"
        else:
            xpath = f"//{element_class}[1]/following-sibling::*"  # 假设是第一个匹配的元素
        
        return self.driver.find_elements(AppiumBy.XPATH, xpath)
    
    def get_preceding_sibling_elements(self, element):
        """获取元素之前的同级元素(不包括自身)
        
        Args:
            element: 当前元素
            
        Returns:
            list: 之前同级元素列表
        """
        # 获取当前元素的ID或XPath
        element_id = element.get_attribute('resource-id')
        element_class = element.get_attribute('class')
        element_text = element.get_attribute('text')
        
        # 构建XPath来查找之前同级元素
        if element_id:
            xpath = f"//*[@resource-id='{element_id}']/preceding-sibling::*"
        elif element_text:
            xpath = f"//*[@text='{element_text}']/preceding-sibling::*"
        else:
            xpath = f"//{element_class}[1]/preceding-sibling::*"  # 假设是第一个匹配的元素
        
        return self.driver.find_elements(AppiumBy.XPATH, xpath)
    
    def get_child_elements(self, element):
        """获取元素的所有直接子元素
        
        Args:
            element: 当前元素
            
        Returns:
            list: 子元素列表
        """
        return element.find_elements(AppiumBy.XPATH, './*')
    
    def get_grandchild_elements(self, element):
        """获取元素的所有孙子元素(子元素的子元素)
        
        Args:
            element: 当前元素
            
        Returns:
            list: 孙子元素列表
        """
        return element.find_elements(AppiumBy.XPATH, './*/*')
    
    def get_all_descendants(self, element, max_depth=None):
        """获取元素的所有后代元素(递归获取所有层级的子元素)
        
        Args:
            element: 当前元素
            max_depth: 最大递归深度,如果为None则无限递归
            
        Returns:
            list: 所有后代元素列表
        """
        descendants = []
        
        def _collect_descendants(current_element, current_depth):
            # 如果达到最大深度,停止递归
            if max_depth is not None and current_depth > max_depth:
                return
            
            children = self.get_child_elements(current_element)
            for child in children:
                descendants.append(child)
                _collect_descendants(child, current_depth + 1)
        
        _collect_descendants(element, 1)
        return descendants
    
    def print_element_info(self, element, prefix=""):
        """打印元素信息
        
        Args:
            element: 要打印信息的元素
            prefix: 前缀字符串,用于缩进显示
        """
        element_id = element.get_attribute('resource-id') or "无"
        element_class = element.get_attribute('class') or "无"
        element_text = element.get_attribute('text') or "无"
        element_desc = element.get_attribute('content-desc') or "无"
        element_bounds = element.get_attribute('bounds') or "无"
        
        print(f"{prefix}元素信息:")
        print(f"{prefix}  ID: {element_id}")
        print(f"{prefix}  Class: {element_class}")
        print(f"{prefix}  Text: {element_text}")
        print(f"{prefix}  Content-Desc: {element_desc}")
        print(f"{prefix}  Bounds: {element_bounds}")
        print(f"{prefix}")
    
    def traverse_element_tree(self, start_element, max_depth=None):
        """遍历元素树,打印所有层级的元素信息
        
        Args:
            start_element: 开始遍历的元素
            max_depth: 最大遍历深度,如果为None则无限递归
        """
        def _traverse(element, current_depth, max_depth):
            # 打印当前元素信息
            prefix = "  " * current_depth
            self.print_element_info(element, prefix)
            
            # 如果达到最大深度,停止递归
            if max_depth is not None and current_depth >= max_depth:
                return
            
            # 递归遍历子元素
            children = self.get_child_elements(element)
            for child in children:
                _traverse(child, current_depth + 1, max_depth)
        
        print("\n开始遍历元素树:")
        _traverse(start_element, 0, max_depth)
        print("元素树遍历完成\n")
    
    def find_element_in_hierarchy(self, start_element, target_id=None, target_text=None, target_class=None):
        """在元素层级中查找特定元素
        
        Args:
            start_element: 开始查找的元素
            target_id: 目标元素的resource-id
            target_text: 目标元素的text
            target_class: 目标元素的class
            
        Returns:
            WebElement or None: 找到的元素,如果没找到返回None
        """
        # 先检查当前元素是否匹配
        if self._element_matches(start_element, target_id, target_text, target_class):
            return start_element
        
        # 递归检查子元素
        children = self.get_child_elements(start_element)
        for child in children:
            result = self.find_element_in_hierarchy(child, target_id, target_text, target_class)
            if result:
                return result
        
        return None
    
    def _element_matches(self, element, target_id=None, target_text=None, target_class=None):
        """检查元素是否匹配给定的条件
        
        Args:
            element: 要检查的元素
            target_id: 目标resource-id
            target_text: 目标text
            target_class: 目标class
            
        Returns:
            bool: 如果元素匹配条件返回True,否则返回False
        """
        if target_id and element.get_attribute('resource-id') != target_id:
            return False
        if target_text and element.get_attribute('text') != target_text:
            return False
        if target_class and element.get_attribute('class') != target_class:
            return False
        return True

# 使用示例
"""
# 假设driver已经初始化
# driver = webdriver.Remote(...)  
# 
# # 创建ElementRelationLocator实例
# locator = ElementRelationLocator(driver)
# 
# # 定位一个起始元素
# target_element = locator.get_element(AppiumBy.ID, "com.example.app:id/target_element")
# 
# # 打印元素信息
# locator.print_element_info(target_element)
# 
# # 获取并打印父元素信息
# parent = locator.get_parent_element(target_element)
# print("父元素信息:")
# locator.print_element_info(parent)
# 
# # 获取并打印祖父元素信息
# grandparent = locator.get_grandparent_element(target_element)
# print("祖父元素信息:")
# locator.print_element_info(grandparent)
# 
# # 获取并打印同级元素
# siblings = locator.get_sibling_elements(target_element)
# print(f"找到{len(siblings)}个同级元素:")
# for i, sibling in enumerate(siblings):
#     print(f"\n同级元素 #{i+1}:")
#     locator.print_element_info(sibling)
# 
# # 获取并打印后续同级元素
# following_siblings = locator.get_following_sibling_elements(target_element)
# print(f"找到{len(following_siblings)}个后续同级元素:")
# for i, sibling in enumerate(following_siblings):
#     print(f"\n后续同级元素 #{i+1}:")
#     locator.print_element_info(sibling)
# 
# # 获取并打印子元素
# children = locator.get_child_elements(parent)
# print(f"找到{len(children)}个子元素:")
# for i, child in enumerate(children):
#     print(f"\n子元素 #{i+1}:")
#     locator.print_element_info(child)
# 
# # 遍历元素树(限制最大深度为2)
# locator.traverse_element_tree(parent, max_depth=2)
# 
# # 在元素层级中查找特定元素
# found_element = locator.find_element_in_hierarchy(parent, target_text="确定")
# if found_element:
#     print("找到目标元素:")
#     locator.print_element_info(found_element)
# else:
#     print("未找到目标元素")
"""

# 注意事项:
# 1. 当元素没有唯一标识符时,定位可能会不准确,特别是在多个相同class的元素情况下
# 2. 遍历大型元素树可能会比较耗时,建议使用max_depth参数限制遍历深度
# 3. XPath定位在某些情况下可能不如原生定位方式高效,可以根据具体场景选择合适的方法
# 4. 在Android系统中,有些元素属性可能不可用或为空,代码中已经做了相应的处理
# 5. 实际使用时,建议根据Appium服务器和设备性能调整超时时间和重试逻辑
posted @ 2025-11-13 21:27  zhangdingqu  阅读(9)  评论(0)    收藏  举报