解决iframe对selenium定位元素的干扰问题

解决iframe对selenium定位元素的干扰问题

    • 场景还原
    • 解决思路

场景还原

解决iframe对selenium定位元素的干扰问题_第1张图片
我们在通过selenium的find_element方法定位页面元素的时候会发现一个问题,我的定位条件没有问题,定位元素也是非隐藏元素,可代码执行时总提示Message: no such element: Unable to locate element,百思不得其解时,最后发现元素嵌套在了iframe当中,无法直接定位。
这个时候我们通常的解决办法就是,查看元素所在的iframe,然后调用selenium的switch_to.frame()跳转到所在的iframe。由于iframe的特性,当存在多层嵌套iframe时,我们还需要一层一层的去跳进跳出,比较繁琐。

解决思路

一、思路构图
解决iframe对selenium定位元素的干扰问题_第2张图片

把页面中的所有iframe看做一棵树,0代表default_content(),1、2、3节点为并列同层iframe,0、1、4节点为嵌套iframe。元素定位时,当前iframe可能是树的任意位置。采用N次先序遍历的方式,对所有iframe进行遍历检查。具体步骤如下:
1.优先从当前iframe节点第一次遍历,这样可以提升查找效率,此时顺序为1-4-5;
2.将遍历过的iframe存储在一个已遍历集合当中,在下一轮遍历时跳过这些节点;
3.当上一轮遍历未发现目标元素时,返回页面顶层,也就是根节点0;
4.从根节点查找其下子节点,并剔除其中已遍历节点,返回结果为2、3;
4.针对2、3节点进行递归循环查找,最终遍历顺序为1-4-5-0-2-6-3-7-8。

二、方法实现

过滤隐藏iframe

    def elementisvisable(self,element:WebElement):
        '''
        判断元素是否可见,在selenium的判断的基础上增加了一些特殊的判断。判断了元素尺寸。
        :param element: 元素对象
        :return: boolean
        '''
        try:
            if not element.is_displayed():
                return False
            size = element.size
            if not int(size['height']) and not int(size['width']):
                return False
            loc = element.location
            if int(loc['x']) < -9000 and int(loc['y']) < -9000:
                return False
            if not element.is_enabled():
                return False
        except:
            return False
        return True

获取子节点iframe

    def get_visable_child_iframe(self,iframes_id):
        '''
        获取当前iframe下所有的子iframe,过滤其中的隐藏iframe,将可见iframe存入集合并返回
        :param iframes_id: 已遍历过的iframe集合
        :return: 可见且未遍历的iframe集合
        '''
        iframes = self.driver.find_elements_by_xpath('//iframe')
        visable_iframe = []
        for iframe in iframes:
            if self.elementisvisable(iframe):
                if iframe.id not in iframes_id:
                    visable_iframe.append(iframe)
        if visable_iframe:
            return visable_iframe

递归树定位元素

    def findelement(self,by=None,value=None,starttime=None,outtiom=None,iframes_id:list=None,lastrun=True):
        '''
        从当前iframe层查找元素,如果查找不到则优先遍历其所有子iframe,如果其子iframe还无法定位到元素,则再从顶层进行遍历
        :param by: 默认用xpath的方法去找,也可以根据自己的需求定义
        :param value: xpath,或者是用其他方式查找元素依赖的参数
        :param starttime: 超时监控的起始时间
        :param outtiom: 一个作用是等待元素出现,第二是配置了超时时间,不能低于2s,有可能会导致元素获取不到
        :param iframes_id: 以list的形式存储已遍历过的iframe的id
        :param lastrun: 两个作用:1.默认返回顶层iframe再查找一次元素对象;
                                  2.当发现未存储在iframes_id里的iframe时,针对该iframe遍历其树下所有iframe
        '''
        while True:
            visable_elements = [] #存储可见的元素
            elements = self.driver.find_elements(by=by,value=value)
            for element in elements:
                if self.elementisvisable(element):
                    visable_elements.append(element)
            if visable_elements:
                return visable_elements[0] #如果查找到目标元素,跳出递归循环
            else:
                visable_child_iframes = self.get_visable_child_iframe(iframes_id=iframes_id)#查看当前iframe下所有的可见子iframe
                if visable_child_iframes:
                    lastrun=True
                    for iframe in visable_child_iframes:
                        self.driver.switch_to.frame(iframe)
                        iframes_id.append(iframe.id)#将遍历过的iframe的id存储在集合中
                        return self.findelement(by=by,value=value,starttime=starttime,outtiom=outtiom,iframes_id=iframes_id,lastrun=lastrun)
                if lastrun:
                    lastrun=False
                    self.driver.switch_to.default_content()
                    return self.findelement(by=by,value=value, starttime=starttime, outtiom=outtiom, iframes_id=iframes_id,lastrun=lastrun)
            nowtime = int(time.time())
            if nowtime - starttime>=outtiom:
                raise exceptions.NoSuchElementException('未找到元素:{}'.format(value))

定位元素底层封装方法

    def getelement(self,value=None,by=By.XPATH,outtime=30,checkpage=True):
        '''
        返回xpath指向的元素,如果指向多个可见的,只返回第一个,能自动过滤网页中的隐藏元素,并且带超时等待
        :param value: xpath,或者是用其他方式查找元素依赖的参数
        :param by: 默认用xpath的方法去找,也可以根据自己的需求定义
        :param outtime: 一个作用是等待元素出现,第二是配置了超时时间,不能低于2s,有可能会导致元素获取不到
        :param checkpage: 控制单个方法是否使用检查页面源码变化
        :param proxy: 控制单个方法是否使用代理判断是否 xhr 请求都被响应
        :return: webelemnet元素
        '''
        self.waithtml(outtime=1)#等待页面稳定
        self.waitpagestable(checkpage)
        starttime = int(time.time())
        return self.findelement(by=by,value=value,starttime=starttime,outtiom=outtime,iframes_id=[])

你可能感兴趣的:(自动化测试,selenium)