PO

作者: 吱吱菌啦啦 | 来源:发表于2022-05-07 11:25 被阅读0次
    PO:Page Object,po是一种设计模式,提供通用的方法,简单来说就是分层设计。
    场景:我们最初做UI自动化测试时,一个python文件可以实现整个流程的操作,那么会带来的问题有:
    • 代码可读性差,一旦有一个元素的位置发生改动,改起来特别费劲;
    • 代码维护性差,时间久了看不懂,别人也看不懂;
    • 可扩展性弱,比如登录,这个页面需要登录,到了另外一个页面可能还需要登录,需要再写一遍。

    要解决这种问题,我们需要把场景抽象化,通过PO思想进行封装。把操作细节封装成方法,对外只提供方法,不提供细节。当需要修改时只改操作细节,不改对外暴露的方法。但是,不是所有页面都封装,只封装主要模块,不封装次要模块。


    截图.png
    PO原则
    • 用一个方法代替页面的服务
    • 对外提供的方法不暴露细节
    • 对外提供的方法中不包含断言
    • 如果页面a导航到页面b,page a应该return page b
    • 不为页面的所有元素封装方法
    • 当出现正确的和错误的结果时,我们封装时要分开写,不要揉到一起写

    举个例子,以企业微信后台为例,先构思一下用例:


    image.png

    用PO思想,我需要创建几个文件:

    • main_page.py:主页的po,提供主页的方法,不提供细节
    • base_page.py:抽离通用方法的po
    • add_member_page.py:添加成员页po,继承base_page,对应具体添加成员的操作细节
    • contact_page.py:通讯录页的po,继承base_page,对应具体通讯录的操作细节
    • test_add_member.py:添加成员的测试用例及断言

    main_page.py

    from time import sleep
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from test_selenium.work_weixin.api.add_member_page import AddMemberPage
    from test_selenium.work_weixin.api.base_page import BasePage
    from test_selenium.work_weixin.api.contact_page import ContactPage
    
    class MainPage(BasePage):
        """
        首页
        """
        _base_url = 'https://work.weixin.qq.com/wework_admin/frame#index'
        def goto_contact(self):
            """
            跳转通讯录页面
            :return:
            """
            return ContactPage()
    
        def goto_add_member(self):
            """
            跳转添加成员页面
            :return:
            """
            self.driver.find_element(By.XPATH, '//*[@id="_hmt_click"]/div[1]/div[4]/div[2]/a[1]/div/span[2]').click()
            sleep(1)
            return AddMemberPage(self.driver)
    

    base_page.py

    from time import sleep
    import yaml
    from selenium import webdriver
    from selenium.webdriver.chrome.webdriver import WebDriver
    from selenium.webdriver.opera.options import Options
    
    class BasePage:
        """
        封装所有页面对象通用的操作,如driver的实例化
        """
        _base_url = None
        def __init__(self, base_driver=None):
            """
            子类没有构造函数,在实例化过程中会自动调用父类的构造函数,所以每个PO在实例化过程中都会执行构造函数的逻辑,这样就会初始化多个driver
            所以需要避免driver的重复实例化
            """
            # 如果base_driver为真,就不需要重复driver实例化操作
            if base_driver:
                self.driver =base_driver
            # 如果base_driver为None,就需要对driver实例化
            else:
                # 不加self是局部变量
                self.driver = webdriver.Chrome()
                self.driver.implicitly_wait(5)
                # url优化
                if self._base_url != None:
                    self.driver.get(self._base_url)
                    cookie = yaml.safe_load(open('../data/login_cookie.yml'))
                    print(cookie)
                    # 种cookie
                    for c in cookie:
                        self.driver.add_cookie(c)
                    sleep(2)
                    self.driver.get(self._base_url)
    
        def find(self, by, locaotor):
            """
            封装find_element操作
            :param by:
            :param locaotor:
            :return:
            """
            res = self.driver.find_element(by, locaotor)
            print(f"找到的元素为{res}")
            return res
    

    add_member_page.py

    from time import sleep
    from selenium.webdriver.chrome.webdriver import WebDriver
    from selenium.webdriver.common.by import By
    from test_selenium.work_weixin.api.base_page import BasePage
    from test_selenium.work_weixin.api.contact_page import ContactPage
    
    class AddMemberPage(BasePage):
        """
        添加成员页
        """
        _base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'
    
        def goto_contact(self):
            """
            跳转通讯录页面
            :return:
            """
            return ContactPage()
    
        def add_member(self, username, acctid, phonenum):
            """
            添加成员操作
            :return:
            """
            self.find(By.ID, 'username').send_keys(username)
            self.find(By.ID, 'memberAdd_acctid').send_keys(acctid)
            self.find(By.ID, 'memberAdd_phone').send_keys(phonenum)
            self.driver.find_element(By.CSS_SELECTOR, '.js_btn_save').click()
            return ContactPage(self.driver)
    
        def add_member_fail(self):
            """
            添加成员操作
            :return:
            """
            self.find(By.ID, 'username').send_keys('')
            self.find(By.ID, 'memberAdd_acctid').send_keys('111')
            self.find(By.ID, 'memberAdd_phone').send_keys('13600001243')
            error_list = self.driver.find_elements(By.CSS_SELECTOR, '.ww_inputWithTips_tips')
            print(error_list)
    
            # 寻找所有的错误信息,如果不为空,则返回
            err_message = [ele.text for ele in error_list if ele.text != ""]
            return err_message
            return ContactPage(self.driver)
    

    contact_page.py

    from test_selenium.work_weixin.api.base_page import BasePage
    class ContactPage(BasePage):
        """
        通讯录列表页
        """
        _base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'
    
        def goto_add_member(self):
            """
            添加成员
            :return:
            """
            # 如果出现A到B,B到A循环导入的场景,需要把其中一个的导包放在方法里
            from test_selenium.work_weixin.api.add_member_page import AddMemberPage
    
            return AddMemberPage()
    
        def member_list(self, member_name):
            """
            成员列表list
            :return:
            """
            return ['member_name']
    

    test_add_member.py

    from test_selenium.work_weixin.api.add_member_page import AddMemberPage
    from test_selenium.work_weixin.api.contact_page import ContactPage
    from test_selenium.work_weixin.api.main_page import MainPage
    
    class TestAddMember:
        """
        添加成员的测试用例
        """
        def setup_class(self):
            """
            页面实例化MainPage
            :return:
            """
            self.main = MainPage()
    
        def test_add_member(self):
            """
            1.在首页,点击添加成员按钮,跳转到添加成员页面
            2.在添加成员页面,填写成员信息,点击保存
            3.在通讯录页面查看信息是否添加成功
    
            :return:
            """
            # # 方法一
            # # 页面实例化,调用过于复杂
            # main = MainPage()
            # add_member_page = AddMemberPage()
            # contact_page = ContactPage()
            #
            # # 1.在首页,点击添加成员按钮,跳转到添加成员页面
            # main.goto_add_member()
            #
            # # 2在添加成员页面,填写成员信息,点击保存
            # add_member_page.add_member()
            #
            # # 3.在通讯录页面查看成员是否添加成功
            # contact_page.member_list()
    
            # 方法二
            username, acctid, phonenum = "吱吱3", '3', '18700001234'
    
            # 通过前面return实例对象,可以直接用方法.方法的方式,实现链式调用
            res = self.main.goto_add_member().add_member(username, acctid, phonenum).member_list('member_name')
    
            # 断言与手工测试一致,断言添加的成员是否在结果中,如果在表示用例通过,否则失败
            assert 'member_name' in res
    
        def test_add_member_fail(self):
            """
            反向用例,成员名称为空
            :return:
            """
            error_list = self.main.goto_add_member().add_member_fail()
            assert "请填写姓名" in error_list
    
        def teardown_class(self):
            self.main.driver.quit()
    
    

    源码彩蛋》》》
    链接: https://pan.baidu.com/s/14p0Q38whTbjlRwlMZA-iVg
    提取码: 7wzh

    相关文章

      网友评论

          本文标题:PO

          本文链接:https://www.haomeiwen.com/subject/ypjiyrtx.html