登录:未授权
场景
输入未授权的用户名及密码,点击登录后会弹出提示:此账号没有经过授权,请联系管理员!
这个提示会一闪而过,所以一般的打开F12-elements直接定位是定位不到的。那么该如何定位呢?
提示框的定位
在这里我们可以打开F12,切换到Sources,这里有个调试功能,我们在输入用户名、密码,点击登录按钮后,可以按下暂停按钮,这样就能定位到一闪而过的提示框了:
定位好元素后,剩下的部分就跟之前登录的测试一样了!
初步代码
login_data.py
#登录未授权数据
"""封装登录操作所需要用到的数据"""
login_invaild = [
{"username":"15222222222", "password": "123456","expected":"此账号没有经过授权,请联系管理员!"}
]
login_page.py
from config import config
from middleware.page.index_page import Index_page
from selenium.webdriver.common.by import By
class Login_page():
def __init__(self,driver):
self.driver = driver
#url地址
host = config.HOST
url = host + "/Index/login.html"
#元素定位(定位方法+定位表达式)
user_locator = {"by":By.NAME,"value":"phone"}
pwd_locator = {"by":By.NAME,"value":"password"}
login_btn_locator = {"by":By.CLASS_NAME,"value":"btn-special"}
invaild_text_locator = {"by":By.XPATH,"value":'//div[@class="layui-layer-content"]'}
#登录未授权用户
def login_invaild(self,username,password):
#元素定位、元素操作(定位用户名、密码并输入,点击登录)
self.enter_username(username)
self.enter_password(password)
self.click_login_button()
return self
#访问登录页面
def get_url(self):
self.driver.get(self.url)
return self
#输入用户名
def enter_username(self,username):
self.driver.find_element(**self.user_locator).send_keys(username)
#输入密码
def enter_password(self,password):
self.driver.find_element(**self.pwd_locator).send_keys(password)
#点击登录按钮
def click_login_button(self):
self.driver.find_element(**self.login_btn_locator).click()
#用户未授权时获取错误信息
def get_invaild_message(self):
invaild_text = self.driver.find_element(**self.invaild_text_locator).text
return invaild_text
test_login.py
from middleware.handler import Handler
import pytest
from data.login_data import login_invaild
from middleware.page.login_page import Login_page
class Test_login():
#用户未授权
@pytest.mark.parametrize("test_data",login_invaild)
@pytest.mark.login_error_02
def test_login_invaild(self,driver,test_data):
# 初始化页面
login_page = Login_page(driver)
# 调用页面行为完成页面操作,从而获取实际结果
actual_result = login_page.get_url().login_invaild(
username=test_data["username"],
password=test_data["password"]
).get_invaild_message()
# 实际结果与预期结果的断言
try:
assert actual_result in test_data["expected"]
except AssertionError as e:
Handler.logger.error("测试用例不通过!")
raise e
难点:元素的加载需要等待
问题分析
刚刚我们执行用例的时候是能执行成功的。但是这里有一个问题,就是提示框的加载问题。我们在点击登录按钮之后,立刻就去定位提示框并获取它的文本内容。要是点击登录后,它没那么快弹出来呢?所以这里我们得加等待时间。
等待时间有3种:强制等待、隐性等待和显性等待。
选择强制等待自然是可以的,但是时间不好控制。时间设得太久就会延长执行时间;时间设得太短又难以应对网络状况差等突发情况。
选择隐性等待呢?我们知道隐性等待是专门用来等待元素定位的,而我们这里又刚好需要定位这个提示框。那么用隐性等待就行了吗?并不是的,我们定位提示框之后,还要获取它的文本内容。这里需要说到网页加载的知识了。如下图所示,网页在加载时,首先会先加载标签,然后再去加载一些耗时的东西,比如说标签里的内容、图片等等。这里我们就算用隐性等待,等待这个元素被定位到了,被定位到那一刻它的任务就完成了,就没后续了。此时后面的代码立刻去获取定位到的这个元素的文本内容,不一定能获取得到,因为它的内容可能还没被加载出来就去获取是获取不到的。当然虽然说元素标签被加载到元素本身里面的内容被完全加载出来的过渡时间很短,但在一些网络状况差等突发情况时就会变得比较长了。所以隐性等待也是不可行的。
解决方法
那么现在只有一个显性等待可以选了。这里我们不仅要等待元素被定位,还要等待元素可见,从而才能获取它的文本信息:
#用户未授权时获取错误信息
def get_invaild_message(self):
invaild_locator = WebDriverWait(self.driver,20,poll_frequency=0.5).until(
expected_conditions.visibility_of_element_located(
self.invaild_text_locator.values()
)
)
return invaild_locator.text
这里要注意的是,visibility_of_element_located方法中传入的locator参数得是一个元祖。但python并不是一个强类型的编程语言,所以这里我们可以直接通过字典的value方法来把里面的值传进去。
这样一来执行的逻辑就变成:
设定20秒的deadline。每隔0.5秒去看下locator的这个元素有没有被定位到并且可见(可见就是说在页面中出现),如果元素可见了,就结束任务;如果过了20秒还没有可见,那么就抛异常。
显性等待的封装
我们可以把显性等待单独封装,因为可能后面有些元素需要显性等待,这样直接调用方法就行了:
#用户未授权时获取错误信息
def get_invaild_message(self):
element = self.invaild_text_locator.values()
invaild_locator = self.wait_element_visiable(20,0.5,element)
return invaild_locator.text
#等待元素可见
def wait_element_visiable(self,timeout,poll_frequency,element):
ele = WebDriverWait(self.driver,timeout,poll_frequency=poll_frequency).until(
expected_conditions.visibility_of_element_located(element)
)
return ele
投资:输入非法金额
场景
投资的场景如下。
首先先登录,进入首页后,选择一个项目,点击抢投标按钮:
接着进入投资页,在输入框中输入数字,点击投标
当输入的数字不是10的整数倍时,按钮就会置灰不可点击,且按钮上出现文本:请输入10的整数倍
前置条件分析
在进行正式投资前,我们需要进行如下前置操作:
- 登录。只有登录进入首页后,才能选择项目进行投资;
- 充值。只有保证自己账户余额足够,才能进行投资。
前置条件我们可以写到conftest模块当中,这是pytest专门的测试夹具模块。
登录好解决,只要调用前面封装好的登录PO中的login_success方法即可。
接着考虑充值。
接着考虑充值。要想实现充值,我们有如下几个方法: - 再写个充值页面,封装充值的操作,然后调用方法;
- 调用充值接口;
- 手工进行充值;
- 数据库里面修改数据
这里我们可以采用第三种方法,直接去数据库把账号余额修改成非常大的数字。
在这里要说明一点,web自动化中的前置条件并非一定要通过web自动化的方式,可以通过多种方法来实现前置条件。
代码
conftest.py
import pytest
from selenium import webdriver
from middleware.page.login_page import Login_page
from data.login_data import login_success
# 定义测试夹具
@pytest.fixture()
def driver():
"""前置条件"""
# 打开浏览器
driver = webdriver.Chrome()
driver.implicitly_wait(20)
# 返回driver对象
yield driver
"""后置条件"""
driver.close()
@pytest.fixture()
#登录
def login(driver):
"""前置条件"""
login_data = login_success[0]
Login_page(driver).get_url().login_success(
username=login_data["username"],
password=login_data["password"]
)
yield driver
index_page.py
from config import config
from selenium.webdriver.common.by import By
from middleware.page.invest_page import Invest_page
class Index_page():
def __init__(self,driver):
self.driver = driver
#首页url
url = config.HOST + "/index.html"
#元素定位(定位方法+定位表达式)
login_success_mess_locator = {"by":By.XPATH,"value":'//a[@href="/Member/index.html"]'}
invest_btn_locator = {"by":By.CLASS_NAME,"value":"btn-special"}
#进入首页页面
def get_url(self):
self.driver.get(self.url)
return self
#获取登录成功后的登录信息
def get_login_message(self):
login_success_text = self.driver.find_element(**self.login_success_mess_locator)
return login_success_text
#点击投标按钮
def click_invest_btn(self):
self.driver.find_element(**self.invest_btn_locator).click()
return Invest_page(self.driver)
invest_page.py
from selenium.webdriver.common.by import By
class Invest_page():
def __init__(self,driver):
self.driver = driver
#元素定位(定位方法+定位表达式)
amount_locator = {"by":By.CLASS_NAME,"value":"form-control"}
error_btn_locator = {"by":By.CLASS_NAME,"value":"btn-special"}
#输入金额
def input_amount(self,amount):
self.driver.find_element(**self.amount_locator).send_keys(amount)
return self
#获取输入非10倍金额时的投资按钮
def error_btn(self):
error_text = self.driver.find_element(**self.error_btn_locator).text
return error_text
test_invest.py
这里要说明两点:
- 这里调用的PO是首页的PO而不是投资页的PO。为什么呢?因为你不登录怎么进去投资页?所以进入投资页的前置条件就是登录,所以这里得调用首页PO
- conftest中前置条件的login方法可以返回也可以不返回。其实调用login_success方法后,就会返回首页的PO对象:Index_page,所以可以不返回。要返回的话,可以返回driver,这样到了test_invest中调用首页的PO:Index_page时可以传driver进去。这里以返回driver处理。
import pytest
from middleware.page.index_page import Index_page
from middleware.handler import Handler
@pytest.mark.invest_error_01
def test_invest_not_10times(login):
"""
测试步骤:
1.前置条件:(1)登录;(2)充值
2.首页:点击投标按钮
3.投资页:输入投资金额
4.投资页:获取实际结果
"""
#初始化页面
driver = login
#调用页面行为完成页面操作,从而获取实际结果
actual_result = Index_page(driver).get_url().click_invest_btn().input_amount(2).error_btn()
#断言
try:
assert actual_result == "请输入10的整数倍"
except AssertionError as e:
Handler.logger.error("测试用例不通过!")
raise e
难点:上下两个页面存在相同的元素定位
问题分析
在执行代码时报了个错:无法定位元素 {“method”:“xpath”,“selector”:"//a[@class=“btn-special”]"}
定位不到元素的原因无非就以下这几个:
-
元素表达式错误
-
进入了当前元素所在区域(可能是整个页面,也可能是某个iframe),但元素没有加载出来,所以也就定位不了
-
不在当前元素所在区域(可能是整个页面,也可能是某个iframe),所以找不到这个元素
接下来一个个进行排除。 -
元素表达式错误
首先,这个元素是在投资页中的。我们进入投资页,在浏览器的F12上,把元素表达式复制过来,是能找到元素的,说明元素表达式没错。
-
进入了当前元素所在区域(可能是整个页面,也可能是某个iframe),但元素没有加载出来
在执行过程中,页面一直停留在投资的上一个页面,也就是首页,并没有进入投资页,所以也就定位不到元素。所以这个错误的原因就是第三点:并没有进入到当前元素所在页面。
接下来我们进行分析。页面一直在首页,并没有跳转至投资页。我们在首页跳转至投资页,是要点击“抢投标”按钮才会跳转。正是因为点击抢投标按钮这个操作出了问题,从而导致跳转不到投资页,也就定位不到输入金额的输入框。
那么我们看回点击抢投标按钮的操作:
这里首先先定位抢投标的按钮,然后点击。这里有两个步骤:定位元素、点击。
先分析元素有没有定位到。很明显是有的,因为如果定位不到这个元素,就会先报这个元素的错,而不是报跳转后页面的那个元素的错。
那元素能不能点击呢?很明显也是可以的,因为这个按钮并没有置灰,或其他不可点击的状态。
那为什么元素也定位到了,也能点击,就是无法跳转至下一个页面呢?
接下来我们从大一点的方向分析。要想跳转至投资详情页要经历如下页面:
登录页——首页——投资详情页
在执行过程中,我们发现了一个很关键的点:网页的加载圈是逆时针转动的!浏览器专门对网页加载做了非常细节的处理:如果是逆时针转动,则说明在请求url或解析IP地址的阶段;如果顺时针转动,则才是真正地在加载当前网页。而这里虽然已经跳转到了首页,但它的网页加载方向是逆时针的,说明其实还在登录页,它在请求首页。所以可以判断出,当前页面还是在登录页面。
那么执行过程的逻辑就出来了。在登录页面点击登录按钮后,仍然停留在登录页面在请求下一个页面,也就是首页,但却一直没有返回回来。接着要在登录页面中点击抢投标的按钮,由于抢投标按钮与登录页面的登录按钮一样,所以还是点的是登录按钮,也就是说在登录页面点了两次登录按钮。点了两遍登录按钮后,要去定位投资详情页的金额输入框并输入金额,当然定位不到,所以抛出投资详情页的金额输入框定位不到的错误。
解决方法
为了避免上下页面存在两个元素定位方法及表达式一样,从而导致下一个页面无法定位到元素的情况,可以采取以下几个方法。
- 上下两个页面的元素采取不同的定位方式。但这个方法有一定的局限性。
比如说现在这个元素,原先采取的是className的定位方式,现在换一种。这个元素还有另一个属性是href,但这个属性后面有一串/Id/27941,说明这个url是会随时变化的。那么我们采取父级元素定位到子级元素这样的xpath方式呢?
比如说这样:
但是替换上去后,还是报了一样的错。说明这种方法有一定的局限性,除非这个元素有多个属性可以进行定位,否则用不了这种方法。 - 等待。等待下一个页面加载完成。这里有三种等待方式可以采取:
- time.sleep(2) 。这种方式前面说了,它非常地不灵活,时间不好把控;
- get_url()。既然无法通过登录自然地跳转至首页,那么我们可以强制地加上跳转至首页的方法。在首页的PO加上:
然后在test_invest中调用首页的PO后加上get_url的方法,强制跳转至首页。
这种方法就像刚刚那个不可控的元素一样,url也有可能会发生变化。所以这种方法在url会随时变化的情况下就不适用了。 - 显性等待:WebDriverWait().until(expected_conditions.title_contains(title_name))。这是一个比较通用的方法。它会等待符合预期的网页的标题加载出来后才执行后面的代码。
代码如下:
class Index_page():
#首页标题
title = "前程贷官网 - 业内领先的社群互联网金融平台"
def __init__(self,driver):
self.driver = driver
try:
WebDriverWait(self.driver, 20, poll_frequency=0.5).until(
expected_conditions.title_contains(self.title))
except:
print("你的操作不在当前{}页面中,无法定位元素".format(self.title))
#省略后续代码
执行后可顺利通过:
投资:投资成功
接着我们按照之前的文章:【web自动化实战之PageObject】中提到的web自动化测试用例实现的流程来把一个测试用例的编写过程走一遍:
场景分析
登录
不管什么业务场景,都需要进行登录操作。所以投资前得进行登录的前置操作。
抢投标
在首页中选择项目,点击抢投标,进入投资详情页。
投资
进入投资详情页后,输入正确的金额,点击投标按钮。
我们可以顺便对投标按钮进行xpath方式的定位://button[text()=“投标”]
点击投标按钮后,就弹出这么一个提示框,证明我们投资成功。接下来就进入了断言环节。
断言
那么怎么判断投资是否成功了呢?投资之后,页面上弹出了一个提示框,提示投资成功,这个提示的文字当然可以当做断言的一个依据。但是单单只是断言页面上出现投资成功的字就可以判断投资真的成功了吗?并非如此,我们还需要判断数据是否发生了变化,也就是用户余额是否有减少。所以,不管是接口自动化还是web自动化,判断一个业务是否成功,断言都需要进行两方面的断言:
- 页面展示(提示文字、出现某个元素等)、接口返回结果的message;
- 数据是否发生变化(页面数据变化、数据库的数据变化)
页面展示断言
首先先进行页面展示的断言。上面弹出的投资成功几个字自然是可以当做断言的依据的,那么我们可以定位这几个字。但这里定位就出现了个问题:
可以看到通过xpath方式查找元素时,找到了两个相同的元素。这里根据页面的区块我们可以看到,我们要找的是第二个:
接着可以看到,我们要找的这个div它是有层级关系的,我们可以根据它的父级div:capital_ts然后再去找到它:
但是我们根据父级找子级的定位方法://div[@class=“capital_ts”]/div[contains(text(),‘投标成功!’)]仍然定位到了两个元素:
父级不行,那么我们可以找它的爷爷——layui-layer-content:
一波三折之后,我们总算定位到了唯一的这个元素:
//div[@class=“layui-layer-content”]//div[contains(text(),‘投标成功!’)]
数据变化断言
单单有页面的结果还不行,我们还需要对数据变化进行断言。那么投资这个场景中,怎么判断数据是否发生变化呢?首先,投资之后,用户的余额肯定是会减少的,而减少的这个数字正好就是投资时输入的数字,所以我们可以根据这么一个公式进行断言:
投资前的余额 + 投资金额 = 投资后的余额
所以我们需要定位两个地方:投资前的余额、投资后的余额。
在我们点进“查看并激活”进入个人信息页后,可看到可用余额,这个就是投资后的余额:
那么投资前的余额在哪里看呢?其实在金额输入框中就已经显示有可用余额了,这里就是投资前余额:
在定位完投资详情页的输入金额框、查看并激活按钮,个人信息页的可用余额后,场景分析也就结束了。
操作步骤
下面总结一下投资成功场景的操作步骤:
- 首页:点击抢投标按钮;
- 投资详情页:金额输入框中输入正确的金额,点击投标按钮(xpath://button[text()=“投标”)
- 投资详情页:页面展示断言,断言弹出的提示框中的“投资成功!”文本(xpath://div[@class=“layui-layer-content”]//div[contains(text(),‘投标成功!’)])
- 投资详情页&个人信息页:数据变化断言,投资详情页的金额输入框(class name:form-control)作为投资前余额;点击查看并激活按钮(xpath://div[@class=‘layui-layer-content’]//button),进入个人详情页,获取可用余额(class name:color_sub),然后根据公式:投资前的余额 + 投资金额 = 投资后的余额进行数据变化断言。
接着我们可以把测试步骤写到测试用例方法中:
@pytest.mark.invest_success
def test_invest_success():
"""
1. 首页:点击抢投标按钮;
2. 投资详情页:金额输入框中输入正确的金额,点击投标按钮
3. 投资详情页:页面展示断言,断言弹出的提示框中的“投资成功!”文本
4. 投资详情页&个人信息页:数据变化断言,投资详情页的金额输入框作为投资前余额;点击查看并激活按钮,进入个人详情页,获取可用余额,然后根据公式:投资前的余额 + 投资金额 = 投资后的余额进行数据变化断言。
"""
pass
准备前置后置
投资成功场景的前置就一个,就是登录,所以可以复用之前投资:输入非法金额的前置后置:
@pytest.mark.invest_success
def test_invest_success(login):
"""
1. 首页:点击抢投标按钮;
2. 投资详情页:金额输入框中输入正确的金额,点击投标按钮
3. 投资详情页:页面展示断言,断言弹出的提示框中的“投资成功!”文本
4. 投资详情页&个人信息页:数据变化断言,投资详情页的金额输入框作为投资前余额;点击查看并激活按钮,进入个人详情页,获取可用余额,然后根据公式:投资前的余额 + 投资金额 = 投资后的余额进行数据变化断言。
"""
#初始化页面
driver = login
根据测试步骤封装页面行为
- invest_page.py
from selenium.webdriver.common.by import By
from middleware.page.user_page import User_page
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
class Invest_page():
title = "前程贷官网 - 项目详情"
def __init__(self,driver):
self.driver = driver
def __init__(self, driver):
self.driver = driver
# 等待页面标题出现
try:
WebDriverWait(self.driver, 20, poll_frequency=0.5).until(
expected_conditions.title_contains(self.title))
except:
print("你的操作不在当前{}页面中,无法定位元素".format(self.title))
#元素定位(定位方法+定位表达式)
amount_locator = {"by":By.CLASS_NAME,"value":"form-control"}
invest_btn_locator = {"by":By.CLASS_NAME,"value":'btn-special'}
invest_success_text_locator = {"by":By.XPATH,"value":'//div[@class="layui-layer-content"]//div[contains(text(),"投标成功!")]'}
check_active_btn_locator = {"by":By.XPATH, "value":"//div[@class='layui-layer-content']//button"}
#等待元素可见
def wait_element_visiable(self,timeout,poll_frequency,element):
element = WebDriverWait(self.driver,timeout,poll_frequency=poll_frequency).until(
expected_conditions.visibility_of_element_located(element)
)
return element
#等待元素可被点击
def wait_element_clickable(self,timeout,poll_frequency,element):
element = WebDriverWait(self.driver,timeout,poll_frequency=poll_frequency).until(
expected_conditions.element_to_be_clickable(element)
)
return element
#输入金额
def input_amount(self,amount):
self.driver.find_element(**self.amount_locator).send_keys(amount)
return self
#获取输入非10倍金额时的投资按钮
def error_btn(self):
error_text = self.driver.find_element(**self.invest_btn_locator).text
return error_text
#获取投资前余额
def before_amount(self):
before_amount = self.driver.find_element(**self.amount_locator)
return before_amount.get_attribute("data-amount")
#点击投标按钮
def click_invest_btn(self):
self.driver.find_element(**self.invest_btn_locator).click()
return self
#获取投标成功文本
def get_success_text(self):
element = self.invest_success_text_locator.values()
success_msg = self.wait_element_visiable(20,0.5,element)
return success_msg.text
#点击查看并激活按钮
def click_check_active_btn(self):
# self.driver.find_element(**self.check_active_btn_locator).click()
# element = self.check_active_btn_locator.values()
# self.wait_element_visiable(20,0.5,element).click()
element = self.check_active_btn_locator.values()
wait_element = self.wait_element_clickable(20,0.5,element).click()
print("wait_element的值:{}".format(wait_element))
return User_page(self.driver)
调用页面行为,获取实际结果
- test_invest.py
@pytest.mark.invest_success
def test_invest_success(login):
"""
1. 首页:点击抢投标按钮;
2. 投资详情页:金额输入框中输入正确的金额,点击投标按钮
3. 投资详情页:页面展示断言,断言弹出的提示框中的“投资成功!”文本
4. 投资详情页&个人信息页:数据变化断言,投资详情页的金额输入框作为投资前余额;
点击查看并激活按钮,进入个人详情页,获取可用余额,然后根据公式:投资前的余额 + 投资金额 = 投资后的余额进行数据变化断言。
"""
#初始化页面
driver = login
invest_page = Index_page(driver).click_invest_btn()
#获取投资前余额
before_amount = invest_page.before_amount()
#获取投标成功文本
success_text = invest_page.input_amount(100).click_invest_btn().get_success_text()
print(success_text)
assert "投标成功" in success_text
#点击查看并激活,进入个人信息,获取投资后余额
after_amount = invest_page.click_check_active_btn().after_amount()
断言
#断言
from decimal import Decimal
assert Decimal(before_amount) - Decimal(str(100)) == Decimal(after_amount)