zoukankan      html  css  js  c++  java
  • 10.PO模式优化

    PO模式优化

    退出登录

    退出登录放到哪个页面呢?
    common里面,登录页面,主页?
    common是封装逻辑相关的,跟业务无关
    退出页面不在登录页面里面
    主页,可以考虑,通过观察发现,主页上面的面包屑里面,任务,日程,公告都可以退出登录
    退出登录属于公共部分,在开发中属于页面头部公共部分定义为headline

    最终决定把退出登录放到headline页面

    封装headline类及方法
    观察发现之前在mianpage里面封装的to_schedule方法也是属于headline里面的,所以需要移动到headline里面来

    class MainPage(BasePage):
        pass
    
    class SchedulePage(BasePage):
        def new_schedule(self,sumary,target):
            #点击新建按钮
            self.click(self.new_btn)
            #输入主题
            self.input_text(self.sumary_input,sumary)
            #点击指派
            self.click(self.select_btn)
            time.sleep(5)
            #清除已选
            self.click_multi(self.selected_users)
            #选择目标用户
            self.select_target_by_text(self.target_users,target)
            #点击确认
            self.click(self.confirm_btn)
            #点击保存
            self.click(self.save_btn)
    
    class HeadlinePage(BasePage):
        def logout(self):
            pass
        
        def to_schedule(self):
            #点击日程
            self.click(self.schedule_btn)
            return SchedulePage()
    
    

    这样就又有一个问题,登录跳转到Mainpage页面,然后就无法跳转到schedule页面了,MainPage继承HeadlinePage
    class MainPage(HeadlinePage):
    pass

    发现又报新错误

    Unresolved reference 'HeadlinePage'

    我们把HeadLinePage移到前面即可,同时SchedulePage也可以继承HeadlinePage

    实现HeadlinePage

    #定义业务操作类
    class HeadlinePage(BasePage):
        def logout(self):
            pass
    
        def to_schedule(self):
            #点击日程
            self.click(self.schedule_btn)
            return SchedulePage()
    
    class LoginPage(BasePage):
        def __init__(self):
            super().__init__()
            self._driver.get(host)
        def login(self,email,pwd):
            self.input_text(self.email_input,email)
            self.input_text(self.pwd_input,pwd)
            self.click(self.login_btn)
            return MainPage()#进入主页
    class MainPage(HeadlinePage):
        pass
    
    class SchedulePage(HeadlinePage):
        def new_schedule(self,sumary,target):
            #点击新建按钮
            self.click(self.new_btn)
            #输入主题
            self.input_text(self.sumary_input,sumary)
            #点击指派
            self.click(self.select_btn)
            #清除已选
            self.click_multi(self.selected_users)
            #选择目标用户
            self.select_target_by_text(self.target_users,target)
            #点击确认
            self.click(self.confirm_btn)
            #点击保存
            self.click(self.save_btn)
            return self  # 没有页面切换,只返回自己就可以
    

    图 1

    实现logout

    #头部公共页面
    class HeadlinePage(BasePage):
        def logout(self):
            #点击头像
            self.click(self.avatar)
            #点击退出
            self.click(self.logout_link)
            time.sleep(2)#留时间让系统退出
            return LoginPage()
    

    调试用例发现,new_schedule后无法链式调用logout方法
    分析发现是new_schedule没有返回对象,创建成功后,没有页面跳转,返回自己就可以,return self

    被指派的用户登录进来,直接在主页就可以查看日程

    查看日程

    查看日程,那就需要获取日程对应的文本,回过头来看,发现还没有封装获取列表的文本方法,需要到common中封装一个

        #获取元素的文本
        def get_eles_text(self,locator):
            eles=self._driver.find_elements(*locator)
            return [ele.text for ele in eles]#返回文本列表
    
    class MainPage(HeadlinePage):
        def get_schedules(self):
            return self.get_eles_text(self.schedules)
    

    至此为止,测试库基本封装完了,下面就是实现测试用例了

    实现测试用例

    在tc目录下,新增一个目录D-webUI-login,新增一个模块test_schedule.py编写测试用例
    def test_tc0000051():
    LoginPage().login(g_email, g_pwd).to_schedule().new_schedule('早会', '猪八戒').logout().login(
    'zhubajie@test.com', '123456')

    发现登录是每个业务都要操作的,那把登录作为一个公共方法
    新增一个conftest.py模块,实现登录

    @pytest.fixture()
    def init_admin():
        #登录
        page=LoginPage().login(g_email, g_pwd)
        return page
    

    业务执行完,需要退出浏览器
    把return 改成yield,下面还需要实现退出浏览器的操作
    page.close_browser()
    close方法还没有实现,需要到common里面封装

        def close_browser(self):
            self._driver.quit()
    

    前面使用了单例模式来创建浏览器,当我们退出浏览器,其他业务用例再次打开浏览器,会发现打不开,原因在于退出浏览器,只是把driver驱动退出,self._driver对象引用依然存在,程序就认为你已经打开了浏览器了,想再次打开浏览器就无法打开了
    解决:退出浏览器的同时清空driver对象即可

        def close_browser(self):
            self._driver.quit()
            self._driver=None
    
    def test_tc0000051(init_admin):
        page=init_admin
        schedule_list=page.to_schedule().new_schedule('晚会', '猪八戒').logout().login(
            'zhubajie@test.com', '123456').get_schedules()
        assert '晚会' in schedule_list
    

    在mian.py中执行用例
    pytest.main(['tc','-s','--alluredir=tmp/report','--clean-alluredir','-k test_tc0000051'])

    执行发现登录页面打开很久没有响应
    如果不想一直等下去,就可以设置一个超时时间来控制,超时后就抛出异常
    在创建driver的时候设置
    #设置浏览器加载页面超时时间
    self._driver.set_page_load_timeout(60)
    self._driver.set_script_timeout(60)

    重新执行,执行发现报错了

        def to_schedule(self):
            # 点击日程
    >       self.click(self.schedule_btn)
    E       AttributeError: 'MainPage' object has no attribute 'schedule_btn'
    

    分析发现MainPage确实没有schedule_btn这个属性,而这个属性是在HeadlinePage中,但这个属性又不是由HeadlinePage对象来调用,而是由HeadlinePage的子类MainPage来调用,那怎么解决这个问题呢
    临时解决方案:同时把chedule_btn属性放到MainPage和HeadlinePage中,但是又产生一个新的问题,假如某一天,元素定位改变了,需要同时修改两个地方,假如漏改了,就会出问题
    那怎么办呢?

    获取继承链的属性

        #读取配置文件
        current_class=self.__class__.__name__
        self.locators=read_yml(path)[current_class]
        #动态赋值元素属性
        for key in self.locators:
            self.__setattr__(key,self.locators[key])# 参数:1属性,2属性值
    

    分析发现动态赋值这个地方,只给当前类赋值,继承父类的属性
    要实现赋值当前类,也要继承父类
    调试一下
    test_demo.py模块中进行调试

    class BasePage:
        def __init__(self):
            print(self.__class__.__name__)#打印当前类类名
    
    class FatherPage(BasePage):
        pass
    
    class ChildPage(FatherPage):
        pass
    
    class SubPage(ChildPage):
        pass
    
    if __name__ == '__main__':
        BasePage()
    
    输出:
    D:softpython3.8python.exe "D:/py project/Merchants_combat/day6/pylib/webui/test_demo.py"
    BasePage
    
    输入:
    FatherPage()
    输出:
    FatherPage
    
    把打印换成如下
            print(self.__class__.__base__.__name__)#打印父类名字
    输入:
        FatherPage()
    输出:
    BasePage
    
    把打印换成如下:
    print(self.__class__.mro())#获取继承链表
    输入:
        SubPage()
    输出:
    [<class '__main__.SubPage'>, <class '__main__.ChildPage'>, <class '__main__.FatherPage'>, <class '__main__.BasePage'>, <class 'object'>]
    
    返回是一个列表,需要获取类名,使用列表生成式
    把打印换成如下:
            print([c.__name__ for c in self.__class__.mro()])
    
    输入:
        SubPage()
    输出:
     ['SubPage', 'ChildPage', 'FatherPage', 'BasePage', 'object']   
    

    完整调试代码:

    class BasePage:
        def __init__(self):
            # print(self.__class__.__name__)#打印当前类类名
            # print(self.__class__.__base__.__name__)#打印父类名字
            # print(self.__class__.mro())#获取继承链表
            print([c.__name__ for c in self.__class__.mro()])
    
    class FatherPage(BasePage):
        pass
    
    class ChildPage(FatherPage):
        pass
    
    class SubPage(ChildPage):
        pass
    
    if __name__ == '__main__':
        SubPage()
    

    现在就可以把获取继承链的方法拿到common中使用了
    [c.name for c in self.class.mro()]

            #读取配置文件
            class_names=[c.__name__ for c in self.__class__.mro()]
            for class_name in class_names:
                self.locators=read_yml(path)[class_name]
                #动态赋值元素属性
                for key in self.locators:
                    self.__setattr__(key,self.locators[key])# 参数:1属性,2属性值
    

    但是发现'BasePage', 'object'是不需要进行元素属性赋值的,需要过滤掉,使用切片就可以解决
    class_names=[c.name for c in self.class.mro()][:-2]

    再次测试用例,用例通过

    数据驱动

    case_data/webui_params.yml

    Schedule:
      name:
        - 晚会1
        - 晚会2
        - 晚会3
        - 晚会4
    
    @pytest.mark.parametrize('summary',read_yml('case_data/webui_params.yml')['Schedule']['name'])
    def test_tc0000051(init_admin,summary):
        page=init_admin
        schedule_list=page.to_schedule().new_schedule(summary, '猪八戒').logout().login(
            'zhubajie@test.com', '123456').get_schedules()
        assert summary in schedule_list
        page.logout()
    

    执行第二条用例数据报错了

    summary = '晚会2'
    
        @pytest.mark.parametrize('summary',read_yml('case_data/webui_params.yml')['Schedule']['name'])
        def test_tc0000051(init_admin,summary):
            page=init_admin
    >       schedule_list=page.to_schedule().new_schedule(summary, '猪八戒').logout().login(
                'zhubajie@test.com', '123456').get_schedules()
    
    pylibwebuiusiness.py:22: in to_schedule
        self.click(self.schedule_btn)
    

    原因:logout回到登录页面,to_schedule()是需要在登录后的主页进行操作,但是用例在执行第二条数据的时候,没有执行登录的方法,导致没有跳转到主页,然后to_schedule()找不到元素

    解决:
    需要对初始化环境的逻辑进行优化

    优化环境初始化逻辑和用例setup

    conftest只生成一个页面,然后在用例的setup方法里面登录和登出

    @pytest.fixture(scope='session')
    def init_page():
        #登录
        page=LoginPage()
        yield page
        page.close_browser()
    
    @pytest.fixture()
    def before_test_tc0000051(init_page):
        page=init_page
        main_page=page.login(g_email,g_pwd)
        yield main_page
        main_page.logout()
    
    

    执行发现在指派的地方报错

        @pytest.mark.parametrize('summary',read_yml('case_data/webui_params.yml')['Schedule']['name'])
        def test_tc0000051(before_test_tc0000051,summary):
            page=before_test_tc0000051
    >       schedule_list=page.to_schedule().new_schedule(summary, '猪八戒').logout().login(
                'zhubajie@test.com', '123456').get_schedules()
    
    tcD-webUI-login	est_schedule.py:21: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    pylibwebuiusiness.py:54: in new_schedule
        self.click_multi(self.selected_users)
    pylibwebuicommon.py:33: in click_multi
        ele.click()
    

    分析:点击指派会弹出新页面,需要加载时间,所以需要加硬编码来等待,等待页面稳定
    # 点击指派
    self.click(self.select_btn)
    time.sleep(2)
    # 清除已选
    self.click_multi(self.selected_users)

    E selenium.common.exceptions.ElementClickInterceptedException: Message: element click intercepted: Element ... is not clickable at point (139, 156). Other element would receive the click:

    ...

    同时发现页面元素不可点击,所以需要等待元素可点击再操作
    需要改造click方法

    等待元素可点击

    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    
        def click(self,locator):
            ele=WebDriverWait(self._driver,30).until(
                EC.element_to_be_clickable(locator))
            ele.click()
    
        def input_text(self,locator,text):
            ele=WebDriverWait(self._driver,30).until(
                EC.element_to_be_clickable(locator))
            ele.send_keys(text)
    
  • 相关阅读:
    Atitit 集团与个人的完整入口列表 attilax的完整入口 1. 集团与个人的完整入口列表 1 2. 流量入口概念 2 3. 流量入口的历史与发展 2 1.集团与个人的完整入口列表
    atitit 每季度日程表 每季度流程 v3 qaf.docx Ver history V2 add diary cyar data 3 cate V3 fix detail 3cate ,
    Atitit react 详细使用总结 绑定列表显示 attilax总结 1. 前言 1 1.1. 资料数量在百度内的数量对比 1 1.2. 版本16 v15.6.1 1 1.3. 引入js 2
    Atitit r2017 r3 doc list on home ntpc.docx
    Atitit r2017 ra doc list on home ntpc.docx
    Atiitt attilax掌握的前后技术放在简历里面.docx
    Atitit q2016 qa doc list on home ntpc.docx
    Atitit r7 doc list on home ntpc.docx 驱动器 D 中的卷是 p2soft 卷的序列号是 9AD0D3C8 D:\ati\r2017 v3 r01\
    Atitit 可移植性之道attilax著
    Atitit q2016 q5 doc list on home ntpc.docx
  • 原文地址:https://www.cnblogs.com/xiehuangzhijia/p/15173747.html
Copyright © 2011-2022 走看看