如何提升WebUI自动化的稳定性

如何提升WebUI自动化的稳定性

一、导致UI自动化不稳定的因素

1.web页面的多变
在自动化的实际应用场景中,经常性会碰到一类问题,UI界面经常性的变动,导致页面元素对象的维护成本大大增加。

2.页面隐藏元素
受前端影响,页面元素的定位在采用相对定位时可能会定位到隐藏元素,导致脚本的不稳定性

3.页面元素加载不稳定
受机器本身及网络状态影响,UI界面的响应时间存在不稳定

4.系统业务复杂
相比繁杂的业务场景,重复简单的业务参加肯定更适于参与自动化测试。随着业务复杂度的提升,脚本的复杂程度也随之提升,中间的业务逻辑更容易出现问题。

5.AJAX请求问题
ajax请求会导致三种情况的问题:元素没有加载出来,不在DOM结构里面;元素不可见,在DOM结构里面;元素可见,可点击,但是页面上其他位置还没完全加载出来,点击这个元素之后一点反应都没有,下一步操作就会报错。

6.环境测试数据准备问题
自动化测试其实就是将功能操作通过脚本自动执行起来,同样会涉及测试数据清理,测试数据准备的问题

二、解决问题的各种方法

1.从自动化框架解决问题

1.1 设置显示等待

重写selenium的底层接口,加入显示等待,设置等待条件及异常处理

WebDriverWait(driver,timeout,poll_frequency=0.3,ignored_exceptions=None)
driver:浏览器驱动
timeout:最长超过时间,默认以秒为单位
poll_frequency:监测的时间间隔,默认为0.3秒
ignored_exceptions:超时后的异常信息,默认情况下抛NoSuchElementException异常

1.2 等待页面加载稳定

重写selenium的底层接口,定位元素前需要先等待页面加载稳定,页面加载稳定的判断条件为”页面源码在限定时间内不在发生变化”

begin = int(time.time())
source = driver.page_source
 while True:
     time.sleep(0.3)
     nowsource = driver.page_source
     if source == nowsource:
         break
     else:
         now = int(time.time())
         if now - begin > 3:
             break
     source = nowsource
try:
    WebDriverWait(self.driver, outtime).until(
        lambda driver: driver.execute_script('return document.readyState') == 'complete')
except exceptions.TimeoutException:
    pass

1.3 过滤隐藏元素

重写selenium的底层接口,在selenium判断的基础上,过滤掉页面特殊的隐藏元素,增强脚本容错性

try:
     if not element.is_displayed():
         return False
     size = element.size
     if not int(size['height']) or not int(size['width']):
         return False
     loc = element.location
     if int(loc['x']) < -9000 or int(loc['y']) < -9000:
         return False
     if not element.is_enabled():
         return False
 except:
     return False
 return True

1.4 设置xhr请求等待

重写selenium的底层接口,在脚本开启代理的情况下,判断xhr请求是否都已经响应,此处可作为选配仅在脚本开启代理时作判断

def getxhrcount():
    r = requests.get(f'http://{ query_site}/xhr')
    if r.status_code == 200:
        return float(r.text)
    else:
        raise ConnectionError(f"访问链接 http://{ query_site}/xhr 出错")


def getxhrtime():
    r = requests.get(f'http://{ query_site}/xhrtime')
    if r.status_code == 200:
        return float(r.text)
    else:
        raise ConnectionError(f"访问链接 http://{ query_site}/xhrtime 出错")


def gethttpcount():
    r = requests.get(f'http://{ query_site}/http')
    if r.status_code == 200:
        return float(r.text)
    else:
        raise ConnectionError(f"访问链接 http://{ query_site}/http 出错")


def gethttptime():
    r = requests.get(f'http://{ query_site}/httptime')
    if r.status_code == 200:
        return float(r.text)
    else:
        raise ConnectionError(f"访问链接 http://{ query_site}/httptime 出错")

start = time.time()
while True:
    xhrnum = getxhrcount()
    oldtime = getxhrtime()
    # xhr 请求有可能是一个接一个发送,所以需要隔一段时间再判断
    time.sleep(0.2)
    newxhrnum = getxhrcount()
    newtime = getxhrtime()
    if xhrnum == 0 and newxhrnum == 0 and oldtime == newtime:
        if logger:
            logger.debug(f'xhr请求最后的响应时间戳为:{ newtime}')
        return True
    else:
        if time.time() - start > outtime:
            if logger:
                logger.warning(f'等待xhr请求响应超时,超时时间为{ outtime}s')
            return False
    time.sleep(0.5)

1.5 集成requests模块

传统的WebUI框架结构是测试报告模块+日志模块+项目配置文件模块+unittest模块+selenium模块,可以在此基础上集成requests模块。requests模块提供接口请求、获取响应内容、解析响应内容的基本封装。

class Session(object):
    '''Session类'''
    def __init__(self):
        self.session = None
        header={ 'User-Agent': real_confing.get('REQUEST','User-Agent'),
                'Accept': real_confing.get('REQUEST','Accept'),
                'Accept-Encoding': real_confing.get('REQUEST','Accept-Encoding'),
                'Accept-Language': real_confing.get('REQUEST','Accept-Language'),
                'Connection': real_confing.get('REQUEST','Connection')
                }
        self.session = requests.Session()
        self.session.headers.update(header)

    def get(self,url,params):
        return self.session.get(url=url,params=params)

    def post(self,url,params,data=None,json=None):
        return self.session.post(url=url,data=data,json=json,params=params)
class page():
    ''' 页面对象的基类,里面封装了一些常用的方法 '''
    def __init__(self,session: Session = None):
        self.session = session
        
    def request_http(self,params,type='post'):
        apiinfo = params['apiinfo']
        del params['apiinfo']
        del params['self']
        try:
            url = params['url']
        except:
            url = None
        del params['url']
        if type=='post':
            if url:
                session = self.post(url=url + apiinfo, params=params)
            else:
                session = self.post(url=real_confing.get('TEST', 'url') + apiinfo, params=params)
        elif type=='get':
            if url:
                session = self.get(url=url  + apiinfo, params=params)
            else:
                session = self.get(url=real_confing.get('TEST', 'url') + apiinfo, params=params)
        else:
        	raise EOFError('请求类型错误')
        return session

    def extractor_json(self,resource,expressioins,num:int=1,default=None):
        ''' JSON提取器,对传入的json数据通过json表达式提取出目标数据 :param resource: 源数据 :param jsonpath: jsonpath表达式 :param num: 返回参数,0代表随机,-1代表所有,正整数代表第几个 :param default: 默认值 '''
        if type(resource)==str:
            resource = json.loads(resource)
        res = jsonpath.jsonpath(resource,expressioins)
        if res:
            if num==-1:
                return res
            elif num==0:
                return res[random.randint(0,len(res)-1)]
            elif num>0:
                return res[num-1]
        else:
            if default:
                return default

    def extractor_regular(self,resource,expressions,num:int=-1,flags=0,default=None):
        ''' 正则提前器,对传入的数据通过正则表达式提取出目标数据 :param resource: 源数据 :param expressions: 正则表达式 :param num: 返回参数,0代表随机,-1代表所有,正整数代表第几个 :param default: 默认值 '''
        res = re.findall(expressions,resource,flags=flags)
        if res:
            if num==-1:
                return res
            elif num==0:
                return res[random.randint(0,len(res)-1)]
            elif num>0:
                return res[num-1]
        else:
            if default:
                return default

2.从设计模式解决问题

2.1 采用POM设计模式

POM(Page Object Model)是一种设计思想,将被测系统中的单个页面当作一个对象,页面的元素和元素直接的操作方法就是页面对象的属性和行为,基本采用用类的方法来组织我们的页面。主要优势在于:
a.页面元素在发生变化时,我们仅需从底层修改对应页面对象,大大降低了后期的维护成本;
b.将复杂的业务逻辑划分成多个细小单元,通过组织这些细小单元形成我们的业务代码,增强代码的复用性,提高效率
c.统一代码风格,减少个人不良的代码习惯对脚本稳定性的影响

2.2 通过requests模块完成环境数据准备

根据接口信息,设计接口对象库,再根据接口对象库封装接口操作,通过调用接口操作完成环境测试测试数据的清理与准备。如此设计相比传统通过UI界面准备数据优势:
a.提高脚本执行效率
b.减少脚本在非用例验证点的地方提前报错,提升脚本容错率
c.增强脚本可操性,便捷获取业务系统的一些关键数据,减少UI操作

class api_resources(page):

    def new_edata(self,apiinfo='/edatasubject/project.do',action=None,data=None,url=None):
        ''' :param apiinfo: 接口地址* :param action: save* :param data: {"caption":"标题","ds":"连接池名称","desc":"描述"} '''
        params = dict(locals())
        self.request_http(params)
    原文作者:窗台上的一朵花
    原文地址: https://blog.csdn.net/qq_40788412/article/details/114583132
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞