目录(82、27min)

1.By

2.登录未授权问题

3.等待

 

 

正文

1.By

背景:上篇文章提到,在定义类属性:元素定位的时候,By方式是class name的时候,没有下划线_,下面来进行深入分析,为什么没有下划线?

首先看下find_element()的源码:有2个参数,by给了默认值By.ID 

    def find_element(self, by=By.ID, value=None):
        """
        Find an element given a By strategy and locator. Prefer the find_element_by_* methods when
        possible.

        :Usage:
            element = element.find_element(By.ID, 'foo')

        :rtype: WebElement
        """
        if self._w3c:
            if by == By.ID:
                by = By.CSS_SELECTOR
                value = '[id="%s"]' % value
            elif by == By.TAG_NAME:
                by = By.CSS_SELECTOR
            elif by == By.CLASS_NAME:
                by = By.CSS_SELECTOR
                value = ".%s" % value
            elif by == By.NAME:
                by = By.CSS_SELECTOR
                value = '[name="%s"]' % value

        return self._execute(Command.FIND_CHILD_ELEMENT,
                             {"using": by, "value": value})['value']

再点击进入查看By的源码:By的类属性名对应的值都是没有下划线的。

class By(object):
    """
    Set of supported locator strategies.
    """

    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

那么,username_locator = {"by":"name","value":"phone"} 就等价于username_locator = {"by":By.NAME,"value":"phone"} ,

用By的类属性的方式的好处:①编写有提示,不会出错;②编写代码,尽量用变量名表示,不写死。

login.py优化,如下:

"""登录页面"""
from selenium.webdriver.common.by import By
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":By.CLASS_NAME,"value":"btn-special"}
    #用户名
    username_locator = {"by":By.NAME,"value":"phone"}
    #密码
    password_locator = {"by":By.NAME,"value":"password"}
    #登陆失败的错误信息
    error_msg_locator = {"by":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

By总结:

①代码的可读性也进一步增强 (看到CLASS_NAME,立马知道是find_element_by_class_name的方式进行元素定位)

②编写过程,避免出现编写错误(调用,编译器有提示,减少错误)

 

2.登录未授权-------JS暂停进行定位

背景:在账号/密码输入错误的时候,有个弹框,弹出来几秒就消失了,无法进行元素定位,该怎么办?

 

 

解决措施:弹框弹出的时候,暂停JS操作,让弹框暂停不动。< ---------(弹框在web中涉及到JS加载,是JS命令调出来的)

暂停代码运行的方式:断点

所以可以采取断点调试的方式,让弹框卡住,不让其消失。

 

操作:F12进入页面调试,login.html中有JS代码,右边是调试进行的基本操作(暂停、单步执行。。。。。)

 

 

 下面操作暂停弹框,进行元素定位的步骤:

①F12进入source模式,输入用户名、密码,点击登录-

②出现弹框,点击右边【暂停】按钮,暂停JS执行

③对【账号或者密码错误】进入定位(查看是否定位的元素的唯一性:比如通过class属性定位的,用CSS选择器查看是否只有一个)

 

这样的话,就完成了弹框的元素定位。--->通过手动定位的元素,可以记录在LoginPage中的类属性。

#登录失败,没有授权的元素定位方式
invalid_msg_locator = {"by":By.CLASS_NAME,"value":"layui-layer-content"}

元素定位成功,可以进行定位方法的封装了。

但是,直接进行定位的方法封装,会定位不到元素,因为页面加载中,弹框是后加载出来的,需要加等待时间。

其次,点击登录以后,用class进行定位并返回text文本,有可能返回值是None,(文本还没加载出来)----这个时候隐式等待就可能找不到文本

注意:通过隐式等待可以等待元素被加载,但是,元素被加载并不表示里面的动态文本内容能够被获取

添加显式等待:①visible 等待元素可见

       ②text文本定位

强制等待:把握好时间

 

完善登录未授权的代码:

①login_data.py中添加测试用例数据

"""登录测试用例数据"""

#登录失败数据
data_error = [
    {"username":"","password":"","expected_results":"请输入手机号"},
    {"username":"123","password":"","expected_results":"请输入正确的手机号"}
]

#登录成功用例
data_success = [
    {"username": "15955100283", "password": "Cj900815", "expected_results": "我的帐户[小蜜蜂177872141]"}

]

#登录未授权用例
data_invalid = [
    {"username": "15955100283", "password": "15", "expected_results": "账号或密码错误!"}

]

②login.py模块的LoginPage类中,添加未授权的定位方法,来获取text文本,用作最后的断言

"""登录页面"""
from selenium.webdriver.common.by import By
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":By.CLASS_NAME,"value":"btn-special"}
    #用户名
    username_locator = {"by":By.NAME,"value":"phone"}
    #密码
    password_locator = {"by":By.NAME,"value":"password"}
    #登陆失败的错误信息
    error_msg_locator = {"by":By.CLASS_NAME,"value":"form-error-info"}
    #登录失败,没有授权的元素定位方式
    invalid_msg_locator = {"by":By.CLASS_NAME,"value":"layui-layer-content"}


    #初始化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


    def get_invalid_info(self):
        """登录未授权 获取未授权弹框信息"""
        return self.driver.find_element(**self.invalid_msg_locator).text

将准备好的data,添加到parametrize,实现数据驱动

③test_login.py测试用例中,添加登录未授权的测试方法

"""登录功能的测试用例"""

import pytest
# from middware.handler import HandlerMiddle
from middware.pages.login import LoginPage
from data.login_data import data_error,data_success,data_invalid
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.get().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.get().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

    @pytest.mark.invalid
    @pytest.mark.parametrize("test_info",data_invalid)
    def test_login_invalid(self,test_info,driver):
        """登录未授权的测试用例"""

        #初始化页面
        login_page = LoginPage(driver)
        #执行测试用例 获取测试结果
        actual_result = login_page.get().login_fail(username=test_info["username"],
                        password=test_info["password"]).get_invalid_info()

        #断言
        try:
            assert actual_result in test_info["expected_results"]
        except AssertionError as e:
            HandlerMiddle.logger.error("测试用例username为{},不通过!".format(test_info["username"]))
            raise e

用例筛选时,记得去pytest.ini中注册标签invalid

总结:web自动化测试用例实现的流程(比如test_login.py测试用例的编写)

①准备前置、后置条件 conftest 中的测试夹具fixture

②编写测试步骤:测试用例的函数注解 docstring

   例如:

登录失败的docstring

      1)登录页面输入用户名

   2)登录页面输入密码

      3)登录页面点击登录按钮

      4)登录页面获取登录失败的错误信息

登录成功的docstring

      1)登录页面输入用户名

   2)登录页面输入密码

      3)登录页面点击登录按钮

      4)首页页面获取登录用户账号信息(进行了页面跳转)

 

在封装的方法中,添加docstring的好处,按照步骤进行封装的方法的编写,减少页面跳转的错误。(前提:docstring要写对啊)

----手工进行测试步骤的验证,确保测试步骤能够实现哪些功能(手工测试的时候,可以直接将需要进行元素定位的类属性完成,写好)

③根据测试步骤,封装页面行为

④调用页面行为,获取实际结果

⑤断言

 

83节

根据上面的步骤,进行get_invalid_info() 进行显示等待的元素定位:

    def get_invalid_info(self):
        """登录未授权 获取未授权弹框信息"""
        # return self.driver.find_element(**self.invalid_msg_locator).text
        elem = WebDriverWait(self.driver,timeout=20,poll_frequency=0.5).until(
            expected_conditions.visibility_of_element_located(self.invalid_msg_locator.values()))
        return elem.text

上面的visibility_of_element_located()的参数是一个元祖,在类属性定义的时候,需要改为元祖。

不修改为元祖的话,也可以将字典.values()转化为列表,也是OK的。(Python是动态语言,对格式的要求不会那么严格,了解即可)

 

在项目中,显示等待可能会经常使用,那么将显式等待封装起来~~~~~~~~~~~~~~~~~~

 封装:等待元素可见的方法

    def get_invalid_info(self):
        """登录未授权 获取未授权弹框信息"""
   
        elem = self.wait_element_visible(self.invalid_msg_locator.values())
        return elem.text

    def wait_element_visible(self,locator,timeout = 20,poll = 0.5):
        """显式等待元素可见
        :return elem
        """
        ele = WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(
            expected_conditions.visibility_of_element_located(locator)
        )
        return ele

 

在定义LoginPage中,元素定位方式的类属性时,用的是字典,在实际项目中,用元祖的方式更加简单,不需要类型转化,

其次是,在进行显性等待的时候,

ele = WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(
            expected_conditions.visibility_of_element_located(locator) 

中传入的locator是个元祖,直接定义类属性为元祖,直接调用即可。

下面将login.py中类属性的字典表示方式改为元组形式。
"""登录页面"""
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

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,"btn-special")
    #用户名
    username_locator = (By.NAME,"phone")
    #密码
    password_locator = (By.NAME,"password")
    #登陆失败的错误信息
    error_msg_locator = (By.CLASS_NAME,"form-error-info")
    #登录失败,没有授权的元素定位方式
    invalid_msg_locator = (By.CLASS_NAME,"layui-layer-content")

    #初始化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


    def get_invalid_info(self):
        """登录未授权 获取未授权弹框信息"""
        
        elem = self.wait_element_visible(self.invalid_msg_locator)
        return elem.text

    def wait_element_visible(self,locator,timeout = 20,poll = 0.5):
        """显式等待元素可见
        :return elem
        """
        ele = WebDriverWait(self.driver,timeout=timeout,poll_frequency=poll).until(
            expected_conditions.visibility_of_element_located(locator)
        )
        return ele

 

下面将index.py文件进行locator分层封装,优化:

"""登录成功页面"""
from selenium.webdriver.common.by import By
from middware.handler import HandlerMiddle


class IndexPage:
    """登录成功"""

    #首页的URL
    URL  =HandlerMiddle.yaml_data["host"] + "/Index.html"

    #用户账号locator
    user_accout_locator = (By.XPATH,'//a[@href="/Member/index.html"]')

    #初始化driver
    def __init__(self,driver):
        self.driver = driver

    #打开首页
    def get(self):
        self.driver.get(self.URL)
        return self

    #获取登录成功的用户名
    def get_account_name(self):
        web_element=self.driver.find_element(*self.user_accout_locator)
        return web_element.text