zoukankan      html  css  js  c++  java
  • 38 web自动化 PageObject

    目录

    81节

    1.PageObject页面对象

    2.数据分组

    3.locator分层

    1.PageObject页面对象

    1)将某个网页(或者APP页面)封装成对象

    对象:

       --属性:比如元素定位器、标题、URL(参考DOM)

       --方法:比如元素定位、点击、鼠标拖拽 (动作、操作就是方法)

    上篇博客中,完成LoginPage类的封装,这个类其实就是个页面,在这个页面下有某些行为(操作),某些行为可以是一步操作,也可以是多步操作的集合.

    比如:类里面的login方法,是一个行为,里面又包含多个行为:定位元素-->键盘输入数据(又属于定位元素的子动作),定位元素-->点击登录按钮

    那么思考一个问题:上面的login行为下面的多个行为,需不需要单独再进行封装??

    PageObject设计原则:

    是不是所有的动作都需要单独封装成页面对象的方法?-------------不必要

    ①因为有的动作是用不到的,比如单独定位元素又不进行用户输入,这种场景是不存在的。(定位元素的目的就是进行用户操作的)

    ②页面中有些行为是个整体:比如输入用户名、密码,点击登录,是一系列整体动作;

       如果输入用户名跟密码是分开的,可以将这2步操作进行单独封装再调用,比如:

       

    原则:根据项目中实际应用,用到什么行为,就封装什么方法

    分层思想:便于维护(前端修改了代码,只需要修改少量代码即可)

    2)PageObject有什么好处?---面试

    ①可维护性

        比如:一个页面,前端工程师修改了元素定位方式,那么就不需要修改test_login.py,直接找到pages文件下面对应修改的某个页面,修改对应的元素定位方式即可。

    其他的文件不需要改动。如果不用PO模式,只要用到这个页面的元素定位,就要修改(检查很多文件),代码管理差、可维护性差。

                 又或者测试用例需要重新设计,那么页面对象模型里面的代码就不需要修改。--可维护性高

    ②扩展性

       当有新需求新功能,实现更加方便

      比如:前端新增新的页面功能,原来的功能不需要改,直接在PO模型页面对象pages中去封装新的功能即可

    ③可读性

      PO模式下,比如LoginPage(driver).login(.....),看到这个,就能理解是登录页面的登录操作,(本身就具有注释功能),易理解。

      如果PO模式下,命名无法实现见名知意,可以在封装的函数中添加详细的注释(文档字符串)介绍(但是在代码调用的时候,很少写注释的,只是在封装的函数中写详细的注释):比如

      

    ④可复用性  重复使用(封装函数、类本来就是为了提高可复用性)

    ⑤页面操作 和 测试操作的分离(√)

    3)依据PO模式,对pages中的login.py中的LoginPage类进行分层设计:

    class LoginPage:
        """登录"""
    
        #初始化driver
        def __init__(self,driver):
            self.driver = driver
    
        def login(self,username,password):
            # 访问登录页面
            url = "http://120.78.128.25:8765/Index/login.html"
            self.driver.get(url)
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            self.driver.find_element_by_class_name("btn-special").click()
    
        def enter_username(self,username):
            "输入用户名"
            self.driver.find_element_by_name("phone").send_keys(username)
    
        def enter_password(self,password):
            "输入密码"
            self.driver.find_element_by_name("password").send_keys(password)
    
        def get_error_info(self):
            "获取登录失败的错误信息"
            return self.driver.find_element_by_class_name("form-error-info").text

    test_login.py:

    import pytest
    from middware.handler import HandlerMiddle
    from middware.pages.login import LoginPage
    
    #获取excel中login数据
    data = HandlerMiddle.excel.read_data("login")
    
    class TestLogin():
        """登录功能的测试类"""
    
        @pytest.mark.error
        @pytest.mark.parametrize("test_info",data)
        def test_login_error(self,test_info,driver):
           """登陆失败用例"""
    
           #初始化 操作的页面 对象
           login_page  = LoginPage(driver)
    
           #测试步骤:输入用户名、密码、登录(调用po中的方法)
           login_page.login(username=eval(test_info["data"])["username"],
                            password=eval(test_info["data"])["password"])
           #获取登录失败的错误信息
           actual_result =login_page.get_error_info()
    
    
           # 断言
           expected_result = test_info["expected_result"]
           assert actual_result in expected_result

    由上总结,测试用例(比如:test_login_error(self):)的编程步骤基本都是下面3个步骤

    --1.初始化页面操作

    --2.调用页面逻辑操作,来获取实际结果

    --3.实际结果跟预期结果比对,即断言

    4)链式调用

    上面的test_login.py调用LoginPage中的方法,调用了2次,如下:

     在实际项目,会出现同时调用很多个方法,那么在test_login.py就要一直写调用的语句,比较麻烦,因此可以采取链式调用的方式,简化调用的过程。

    ①链式调用,被调用的函数的返回值(没有跳转页面)需要是它自己self

    修改login.py中的方法,如下:

    """登录页面"""
    
    class LoginPage:
        """登录"""
    
        #初始化driver
        def __init__(self,driver):
            self.driver = driver
    
        def login(self,username,password):
            # 访问登录页面
            url = "http://120.78.128.25:8765/Index/login.html"
            self.driver.get(url)
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            self.driver.find_element_by_class_name("btn-special").click()
    
            return self
    
        def enter_username(self,username):
            "输入用户名"
            self.driver.find_element_by_name("phone").send_keys(username)
            return self
    
        def enter_password(self,password):
            "输入密码"
            self.driver.find_element_by_name("password").send_keys(password)
            return self
    
        def get_error_info(self):
            "获取登录失败的错误信息"
            return self.driver.find_element_by_class_name("form-error-info").text

    注意上面获取登录失败,页面显示的错误信息时,返回值是text文本,不是self.<-------进行断言

    ②test_login.py进行链式调用:

    """登录功能的测试用例"""
    
    import pytest
    from middware.handler import HandlerMiddle
    from middware.pages.login import LoginPage
    
    #获取excel中login数据
    data = HandlerMiddle.excel.read_data("login")
    
    class TestLogin:
        """登录功能的测试类"""
    
        @pytest.mark.error
        @pytest.mark.parametrize("test_info",data)
        def test_login_error(self,test_info,driver):
           """登陆失败用例"""
    
           #初始化 操作的页面 对象
           login_page  = LoginPage(driver)
    
           #测试步骤:输入用户名、密码、登录(调用po中的方法)
           actual_result =login_page.login(username=eval(test_info["data"])["username"],
                            password=eval(test_info["data"])["password"]).get_error_info()
    
           # 断言
           expected_result = test_info["expected_result"]
           assert actual_result in expected_result

    上面的链式调用,表示,先调用login()方法(调用完以后返回的是self),再调用get_error_info()方法

    PO设计原则更新:

    PageObject设计原则:

    1)是不是所有的动作都需要单独封装成页面对象的方法?----不必要

    ①因为有的动作是用不到的,比如单独定位元素又不进行用户输入,这种场景是不存在的。(定位元素的目的就是进行用户操作的)

    ②页面中有些行为是个整体:比如输入用户名、密码,点击登录,是一系列整体动;

    2)封装的页面操作的返回值 (如上面的链式调用的使用)

       ①执行完一步操作还停留在原来的页面,返回值就是self

          执行完一步操作跳转到别的页面,返回值是:其他页面的对象

      ②如果需要获取某个元素或者属性,就直接返回元素本身、属性的值

      ③如果一个操作可能有多个返回结果(self / 跳转别的页面):根据结果封装成多个方法。

         例如:对于LoginPage中的login()方法,如果没有登录成功,返回的就是self,---->封装成login_fail()方法

                     登录成功,发生页面跳转,返回的就是别的页面对象。----->login_success()方法

    以上完成了对login_error()测试用例的分层,那么对于login_success(),,基本PO原则中的3)-③,进行进一步的封装完善。

    5)登录成功用例

    思想梳理:在测试用例login_success()中第一步是要初始化页面,但是需要初始化2个登录的页面(如上面的LoginPage()对象),还需要封装一个登录成功后跳转的页面

                      在pages文件中封装一个index.py,为登录成功的

    ①封装indexpage

    """登录成功页面"""
    
    class IndexPage:
        """登录成功"""
    
        #初始化driver
        def __init__(self,driver):
            self.driver = driver
    
        #获取登录成功的用户名
        def get_account_name(self):
            web_element=self.driver.find_element_by_xpath('//a[@href="/Member/index.html"]')
            return web_element.text

    ②login.py中再封装一个登录成功的方法

    """登录页面"""
    from middware.pages.index import IndexPage
    
    
    class LoginPage:
        """登录"""
    
        #初始化driver
        def __init__(self,driver):
            self.driver = driver
    
        def login_fail(self,username,password):
            # 访问登录页面
            url = "http://120.78.128.25:8765/Index/login.html"
            self.driver.get(url)
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            self.driver.find_element_by_class_name("btn-special").click()
    
            return self
    
        def login_success(self,username,password):
            # 访问登录页面
            url = "http://120.78.128.25:8765/Index/login.html"
            self.driver.get(url)
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            self.driver.find_element_by_class_name("btn-special").click()
    
            return IndexPage(self.driver)
    
        def enter_username(self,username):
            "输入用户名"
            self.driver.find_element_by_name("phone").send_keys(username)
            return self
    
        def enter_password(self,password):
            "输入密码"
            self.driver.find_element_by_name("password").send_keys(password)
            return self
    
        def get_error_info(self):
            "获取登录失败的错误信息"
            return self.driver.find_element_by_class_name("form-error-info").text

     login_success()方法返回的是跳转的页面index_page()

    ③完成TestLogin中:login_success()测试用例

    """登录功能的测试用例"""
    
    import pytest
    from middware.handler import HandlerMiddle
    from middware.pages.login import LoginPage
    from middware.pages.index import IndexPage
    
    #获取excel中login数据
    data = HandlerMiddle.excel.read_data("login")
    
    class TestLogin:
        """登录功能的测试类"""
    
        @pytest.mark.error
        @pytest.mark.parametrize("test_info",data)
        def test_login_error(self,test_info,driver):
           """登陆失败用例"""
    
           #初始化 操作的页面 对象
           login_page  = LoginPage(driver)
    
           #测试步骤:输入用户名、密码、登录(调用po中的方法)
           actual_result =login_page.login_fail(username=eval(test_info["data"])["username"],
                            password=eval(test_info["data"])["password"]).get_error_info()
    
           # 断言
           expected_result = test_info["expected_result"]
           assert actual_result in expected_result
    
        @pytest.mark.success
        @pytest.mark.parametrize("test_info",[{"username":"159********","password":"15","expected_result":"我的帐户[小蜜蜂177872141]"}])
        def test_login_success(self,test_info,driver):
            """登录成功测试用例"""
    
            #初始化页面对象
            login_page = LoginPage(driver)
            index_page = IndexPage(driver)
    
            #执行测试,获取实际结果,
            login_page.login_success(username=eval(test_info["data"])["username"],
                            password=eval(test_info["data"])["password"])
            actual_result = index_page.get_account_name()
    
            #断言
            assert actual_result in test_info["expected_result"]

    有上面的【 login_success()方法返回的是跳转的页面index_page()】,可知PO模式的另一个优势:无需多次初始化页面对象,直接进行链式调用即可。如下:

    测试用例中的login_page对象调用的login_success()方法的返回值就是IndexPage对象,行链式调用:

    """登录功能的测试用例"""
    
    import pytest
    from middware.handler import HandlerMiddle
    from middware.pages.login import LoginPage#获取excel中login数据
    data = HandlerMiddle.excel.read_data("login")
    
    class TestLogin:
        """登录功能的测试类"""
    
        @pytest.mark.error
        @pytest.mark.parametrize("test_info",data)
        def test_login_error(self,test_info,driver):
           """登陆失败用例"""
    
           #初始化 操作的页面 对象
           login_page  = LoginPage(driver)
    
           #测试步骤:输入用户名、密码、登录(调用po中的方法)
           actual_result =login_page.login_fail(username=eval(test_info["data"])["username"],
                            password=eval(test_info["data"])["password"]).get_error_info()
    
           # 断言
           expected_result = test_info["expected_result"]
           assert actual_result in expected_result
    
        @pytest.mark.success
        @pytest.mark.parametrize("test_info",[{"username":"15955100283","password":"Cj900815","expected_result":"我的帐户[小蜜蜂177872141]"}])
        def test_login_success(self,test_info,driver):
            """登录成功测试用例"""
    
            #初始化页面对象
            login_page = LoginPage(driver)
    
            #执行测试,获取实际结果,
            actual_result = login_page.login_success(username=test_info["username"],
                            password=test_info["password"]).get_account_name()
    
            #断言
            assert actual_result in test_info["expected_result"]

    2.数据分组

    ①分组的依据:测试步骤不一致

     只要是测试步骤不一样,就一定要进行数据分组

    ②测试用例的管理:(最好不要用Excel去管理,在这里用excel管理反而比较复杂:比如读取出来是json格式还要进行转化)

     ---1.py文件

     ---2.yaml文件,支持字典格式

    下面用py文件进行测试用例的管理:data目录下-->login_data.py

    """登录测试用例数据"""
    
    #登录失败数据
    data_error = [
        {"username":"","password":"","expected_results":"请输入手机号"},
        {"username":"123","password":"","expected_results":"请输入正确的手机号"}
    ]
    
    #登录成功用例
    data_success = [
        {"username": "15955100283", "password": "Cj900815", "expected_results": "我的帐户[小蜜蜂177872141]"}
    
    ]

    再次修改test_login()测试用例,并添加log信息(直接调用接口的logging_handler中的方法):

    """登录功能的测试用例"""
    
    import pytest
    from middware.pages.login import LoginPage
    from data.login_data import data_error,data_success
    from middware.handler import HandlerMiddle
    #获取excel中login数据
    # data = HandlerMiddle.excel.read_data("login")
    
    
    @pytest.mark.login
    class TestLogin:
        """登录功能的测试类"""
    
        @pytest.mark.error
        @pytest.mark.parametrize("test_info",data_error)
        def test_login_error(self,test_info,driver):
           """登陆失败用例"""
    
           #初始化 操作的页面 对象
           login_page  = LoginPage(driver)
    
           #测试步骤:输入用户名、密码、登录(调用po中的方法)
           actual_result =login_page.login_fail(username=test_info["username"],
                            password=test_info["password"]).get_error_info()
    
           # 断言
           expected_result = test_info["expected_results"]
           try:
            assert actual_result in expected_result
           except AssertionError as e:
               HandlerMiddle.logger.error("测试用例username为{},不通过!".format(test_info["username"]))
               raise e
    
    
        @pytest.mark.success
        @pytest.mark.parametrize("test_info",data_success)
        def test_login_success(self,test_info,driver):
            """登录成功测试用例"""
    
            #初始化页面对象
            login_page = LoginPage(driver)
    
            #执行测试,获取实际结果,
            actual_result = login_page.login_success(username=test_info["username"],
                            password=test_info["password"]).get_account_name()
    
            #断言
            try:
                assert actual_result in test_info["expected_results"]
            except AssertionError as e:
                HandlerMiddle.logger.error("测试用例username为{},不通过!".format(test_info["username"]))
                raise e

    运行通过!

    当然,你也可以用yaml文件去管理测试用例的数据,调用yaml数据即可,自行尝试。

    3.locator分层

    1)优化URL

    login.py中的URL还可以进一步优化

    # 访问登录页面
    url = "http://120.78.128.25:8765/Index/login.html"
    self.driver.get(url)

    将域名host放在yaml配置文件中管理,将URL作为类属性。(因为不同的项目的域名不一样,直接放在yaml中管理,维护方便,可复用性高)

    其次:将访问页面单独进行封装,如下login.py:

    """登录页面"""
    from middware.pages.index import IndexPage
    from middware.handler import HandlerMiddle
    
    
    class LoginPage:
        """登录"""

    URL =HandlerMiddle.yaml_data["host"] + "/Index/login.html" #初始化driver def __init__(self,driver): self.driver = driver def get(self): """访问页面""" self.driver.get(self.URL) return self def login_fail(self,username,password): # 元素定位+元素操作,输入用户名和密码,点击登录进行提交 self.enter_username(username) self.enter_password(password) self.driver.find_element_by_class_name("btn-special").click() return self def login_success(self,username,password): # 元素定位+元素操作,输入用户名和密码,点击登录进行提交 self.enter_username(username) self.enter_password(password) self.driver.find_element_by_class_name("btn-special").click() return IndexPage(self.driver) def enter_username(self,username): "输入用户名" self.driver.find_element_by_name("phone").send_keys(username) return self def enter_password(self,password): "输入密码" self.driver.find_element_by_name("password").send_keys(password) return self def get_error_info(self): "获取登录失败的错误信息" return self.driver.find_element_by_class_name("form-error-info").text

    在test_login.py用例中,调用login中的方法就要添加get(),完善链式调用:

    actual_result =login_page.get().login_fail(username=test_info["username"],
                            password=test_info["password"]).get_error_info()

    2)优化元素定位

    对于  self.driver.find_element_by_class_name("btn-special").click()中,前端修改了元素表达方式的话,维护起来会非常麻烦,所以要进行优化。

    那么,将元素定位locators,提取出来应该放在那里管理?

    ①locators是跟页面LoginPage绑定在一起的,是该页面的属性,所以可以提取出来作为类属性;-----提取:定位的方式,以及数值(因为定位的方式也可能会发生变化),元祖 、字典的形式表示

    ②find_element()方法,源码中的2个参数为:by,value,即定位的方式,和值。调用locator的元祖()表示方式)),用*args进行元祖解包;调用locator的字典{}表示方式,用**args进行字典解包

    举例1:元祖表示

    name:表示元素定位的方式(By)

    btn-special:表示name 的值,元素定位的属性(value)

    login_btn_locator = ("name","btn-special")

    调用解包:

        def login_fail(self,username,password):
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            self.driver.find_element(*self.login_btn_locator).click()
    
            return self

    举例2:字典表示

    login_btn_locator = {"by":"name","value":"btn-special"}

    调用:

    self.driver.find_element(*self.login_btn_locator).click()

    由上面的例子,将所有的元素定位进行优化,lgin.py:(注意类属性的元素定位,By为class name时,中间没有下划线)

    """登录页面"""
    from middware.pages.index import IndexPage
    from middware.handler import HandlerMiddle
    
    
    class LoginPage:
        """登录"""
        URL  =HandlerMiddle.yaml_data["host"] + "/Index/login.html"
        #登录按钮,元祖形式
        #login_btn_locator = ("name","btn-special")
        #登录按钮
        login_btn_locator = {"by":"class name","value":"btn-special"}
        #用户名
        username_locator = {"by":"name","value":"phone"}
        #密码
        password_locator = {"by":"name","value":"password"}
        #登陆失败的错误信息
        error_msg_locator = {"by":"class name","value":"form-error-info"}
    
    
        #初始化driver
        def __init__(self,driver):
            self.driver = driver
    
        def get(self):
            """访问页面"""
            self.driver.get(self.URL)
            return self
    
    
        def login_fail(self,username,password):
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            #self.driver.find_element(*self.login_btn_locator).click()
            self.driver.find_element(**self.login_btn_locator).click()#点击登录按钮
            return self
    
        def login_success(self,username,password):
    
            # 元素定位+元素操作,输入用户名和密码,点击登录进行提交
            self.enter_username(username)
            self.enter_password(password)
            self.driver.find_element(**self.login_btn_locator).click()#点击登录按钮
    
            return IndexPage(self.driver)
    
        def enter_username(self,username):
            "输入用户名"
            self.driver.find_element(**self.username_locator).send_keys(username)
            return self
    
        def enter_password(self,password):
            "输入密码"
            self.driver.find_element(**self.password_locator).send_keys(password)
            return self
    
        def get_error_info(self):
            "获取登录失败的错误信息"
            return self.driver.find_element(**self.error_msg_locator).text

     提示:在实际项目中,进行元素定位时,就可以先将PO模式中的类属性,比如 username_locator = {"by":"name","value":"phone"},先写好,后面再进行方法编写调用类属性。

  • 相关阅读:
    Android安全-数据安全3-通信安全
    Android安全-数据安全2-存储安全
    Android安全-数据安全1-代码中的字符串安全
    Android安全-代码安全5-调试器和模拟器的检测
    Android安全-代码安全4-逆向工具对抗
    Android安全-代码安全3-Dex文件校验
    Python----Anaconda + PyCharm + Python 开发环境搭建(使用pip,安装selenium,使用IDLE)
    Selenium----Selenium WebDriver /RC工作原理
    Selenium----Selenium简单介绍以及Selenium IDE环境搭建,脚本录制
    Python+Selenium----使用HTMLTestRunner.py生成自动化测试报告2(使用PyCharm )
  • 原文地址:https://www.cnblogs.com/ananmy/p/13571010.html
Copyright © 2011-2022 走看看