目录(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