霍格沃兹总结心得
https://ceshiren.com/t/topic/20967
1.selenium options类
- 是一个配置浏览器启动的选项类,用于自定义和配置Driver会话。
- 在创建 WebDriver实例时,可以配置它的启动参数以进行一些初始设置,这些设置将会在 WebDriver 的整个生命周期内生效对于不同类型的浏览器,WebDriver 传入的参数并不相同
- 常见使用场景:
- 设置无头模式:不会显示调用浏览器,避免人为干扰的问题。
options.add_argument('--headless')
- 设置调试模式:调试自动化测试代码(浏览器复用)。
option.debugger_address = "localhost:9222"
- 设置无头模式:不会显示调用浏览器,避免人为干扰的问题。
-
特别注意: 当使用了options选项之后,在创建driver的时候需要传入参数
options=
;
from selenium import webdriver
from selenium.webdriver.common.by import By
# 注意:导包的时候不要导错,chrome.options和edge.options要分清楚
from selenium.webdriver.chrome.options import Options
# Edge浏览器选项
# from selenium.webdriver.edge.options import Options
# 火狐浏览器选项
# from selenium.webdriver.firefox.options import Options
# 获取浏览器配置的实例对象
option = Options()
# 设置窗口最大化
# option.add_argument("start-maximized")
# 指定浏览器分辨率
option.add_argument('window-size=777×777')
# 无头模式
# option.add_argument('--headless')
driver = webdriver.Chrome(options=option)
# 打开测试人页面
driver.get("https://ceshiren.com/")
2.capablility
- Capabilities是WebDriver支持的标准命令之外的扩展命令(配置信息)
- 配置web驱动的属性,如浏览器名称、浏览器平台等。
- 结合Selenium Grid完成分布式、兼容性等测试
一、特殊场景
1.浏览器复用
1.1应用场景:
- 存在人为介入场景:需要扫描登录
- 提高调试UI自动化脚本效率
1.2复用步骤:
以goole为例子
- 1、配置环境变量,保证命令行输入chrome命令可以正常启动浏览器;
- 将Chrome浏览器的安装目录添加到环境变量;
- 2、 关闭所有的chrome浏览器窗口及进程;
- windows:查看任务管理器里的浏览器进程;
- linux/mac:使⽤ps aux|grep chrome|grep -v 'grep’查看是否有chrome进程存在。确保没有chrome进程被启动过。
- 3、重启命令行窗口,在命令行输入命令启动浏览器:
- windows:
chrome --remote-debugging-port=9222
- mac:
Google\ Chrome --remote-debugging-port=9222
- windows:
- 4、验证浏览器是否启动成功
- 在打开的浏览器中输入
localhost:9222
,只要不显示无法访问此网站就表示配置成功;
- 在打开的浏览器中输入
- 5、 代码中增加debug参数;
from selenium import webdriver
# 注意:导包的时候不要导错,chrome.options和edge.options要分清楚
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
# Edge浏览器选项
# from selenium.webdriver.edge.options import Options
# 火狐浏览器选项
# from selenium.webdriver.firefox.options import Options
# 获取浏览器配置的实例对象
option = Options()
# 设置浏览器的debug模式和地址属性---值就是通过命令行启动浏览器时的localhost:端口
option.debugger_address = "localhost:9222"
# 创建浏览器驱动的时候需要把option对象传入
driver = webdriver.Chrome(options=option)
driver.maximize_window()
# 访问企业微信
driver.get("https://work.weixin.qq.com/wework_admin/frame")
2.Cookie复用
2.1.为什么要使用Cookie自动化登录
- 复用浏览器仍然在每次用例开始都需要人为介入启动浏览器;
- 若用例需要经常执行,复用浏览器则不是一个好的选择;
- 大部分cookie的时效性都很长,扫一次登录获取cookie后可以使用多次;
2.2cookie复用流程
- 1、编写脚本访问登录页面,设置强制等待时间,这个时间用于我们手动去登录;
- 2、打开浏览器,扫码登录;
- 3、确保登录之后,通过Python代码获取cookies,并确认是否获取成功;
driver.get_cookies()
- 4、将获取到的cookie信息做持久化存储;
- 5、Python代码中设置cookies,之后访问页面,后续的调试或者脚本运行就可以携带上这个cookie就行操作了;
driver.add_cookie(cookie)
- 常见问题:
-
- 企业微信cookie有互踢机制。在获取cookie成功之后。不要再进行扫码操作!!!!
-
- 获取cookie的时候,即执行代码获取cookie时,一定要确保已经登录。
-
- 植入cookie的时候是给当前driver打开的地址设置cookie,所以在植入cookie之前需要先访问登录登录页面/刷新浏览器;
-
2.3案例
- 需求:对企业微信通讯录页面进行UI自动化测试,使用cookie处理登录;
- 地址:
https://work.weixin.qq.com/wework_admin/frame#contacts
- 地址:
- 特别说明:植入cookie的时候是给当前driver打开的地址设置cookie,所以在植入cookie之前需要先访问登录登录页面;
import time
import pytest
import yaml
from selenium import webdriver
class TestWeWork:
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
def teardown_class(self):
self.driver.quit()
# 给这条用例做个标记,这是专门用于获取cookies的用例,当cookies没有过期的时候就复用之前获取的cookie,那就通过标记跳过这条用例
@pytest.mark.cookie
def test_get_cookies(self):
# 1、访问企业微信首页
self.driver.get("https://work.weixin.qq.com/wework_admin/frame")
# 2、设置等待时间,这个时间用于去页面上手动扫描成功登录
time.sleep(20)
# 3、在页面上确认成功登录之后,在这里获取登录后的cookie信息
cookies = self.driver.get_cookies()
# 4、打印cookies信息确保成功获取到cookies
print(cookies)
# 5、将获取的cookie做持久化存储
with open("wework_cookies.yaml","w") as f:
yaml.safe_dump(cookies,f)
def test_add_cookie(self):
# 6、植入cookie的时候是给当前driver打开的地址设置cookie,所以在植入cookie之前需要先访问登录登录页面--或者刷新浏览器
# self.driver.refresh()
self.driver.get("https://work.weixin.qq.com/wework_admin/frame")
# 7、从文件中获取保持的cookies信息
with open("wework_cookies.yaml",'r') as f:
cookies = yaml.safe_load(f)
print(cookies)
# 8、设置cookie信息---从源码中发现add_cookie()就收的是字典,而cookies中是字典列表,所以需要遍历cookies将每个字典都添加进去
for cookie in cookies:
self.driver.add_cookie(cookie)
# 9、访问目标页面,因为此时已经设置了cookie,已经不再需要扫描登录
self.driver.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
time.sleep(5)
3.自动化关键数据记录
内容 | 作用 |
---|---|
日志 | 1. 记录代码的执行记录,方便复现场景;2. 可以作为bug依据 |
截图 | 1. 断言失败或成功截图;2.异常截图达到丰富报告的作用;3. 可以作为bug依据 |
page source | 1. 协助排查报错时元素当时是否存在页面上 |
3.1行为日志
脚本日志级别:
- debug记录步骤信息
- info记录关键信息,比如断言等
3.2异常截图
-
driver.save_screenshot(截图路径+名称)
- 这个方法底层调用了
get_screenshot_as_file(filename)
方法;
- 这个方法底层调用了
- 记录关键页面
- 断言页面
- 重要的业务场景页面
- 容易出错的页面
部分元素截图
方式1:
优化方案:定义一个装饰器实现异常处理,在basepage的封装的查询UI元素的方法上使用该装饰器(也可以装饰测试用例)
import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By
def ui_exception_recode(func):
# 通用装饰器形式(*args,**kwargs),任何方法都可以被装饰
def inner(*args,**kwargs):
# 执行被装饰函数,如果函数有异常则捕获
try:
# 因为被装饰函数需要拿到元素之后进行操作,所以这里需要有返回值
return func(*args,**kwargs)
except Exception:
# 因为在异常处理过程中需要用到driver,所以从传过来的参数中获取driver
driver = args[0].driver
# 捕获异常之后保留截图和页面源码
# print("异常了。。。")
# 拼接文件路径
temp_time = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime())
shotpath = "screenshot/search/" + temp_time + "百度首页截图.png"
# 保存截图
driver.save_screenshot(shotpath)
# 将截图文件上传到allure报告
allure.attach.file(
# 文件路径
source=shotpath,
# allure上文件名称
name="页面截图",
# 文件类型
attachment_type=allure.attachment_type.PNG,
# 文件扩展名
extension="png"
)
# 拼接文件路径
sourcepath = "pagesource/search/" + temp_time + "百度首页源码.html"
# 保存页面源码
with open(sourcepath, 'w', encoding='utf-8') as f:
f.write(driver.page_source)
# 将源码文件上传到allure报告
allure.attach.file(
source=sourcepath,
name="页面源码",
attachment_type=allure.attachment_type.TEXT,
extension="html"
)
# 为了不然异常处理影响用例的真实运行结果,需要再次把异常跑出去
raise Exception
return inner
class BasePage:
# 定位单个元素
@expected_ui_error
def do_find(self,by,locator=None):
if locator:
return self.driver.find_element(by,locator)
else: # 如果传的是元组
return self.driver.find_element(*by)
方式2:
参考链接youyou 13537355.html
说明:pytest 有个很好的钩子函数 pytest_runtest_makereport 可以获取到用例执行的结果,所以我们在这个钩子函数里面判断用例失败后截图就可以了。
import pytest
from selenium import webdriver
import os
import allure
_driver = None
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
'''
获取每个用例状态的钩子函数
:param item:
:param call:
:return:
'''
# 获取钩子方法的调用结果
outcome = yield
rep = outcome.get_result()
# 仅仅获取用例call 执行结果是失败的情况, 不包含 setup/teardown
if rep.when == "call" and rep.failed:
mode = "a" if os.path.exists("failures") else "w"
with open("failures", mode) as f:
# let's also access a fixture for the fun of it
if "tmpdir" in item.fixturenames:
extra = " (%s)" % item.funcargs["tmpdir"]
else:
extra = ""
f.write(rep.nodeid + extra + "\n")
# 添加allure报告截图
if hasattr(_driver, "get_screenshot_as_png"):
with allure.step('添加失败截图...'):
allure.attach(_driver.get_screenshot_as_png(), "失败截图", allure.attachment_type.PNG)
@pytest.fixture(scope='session')
def browser():
global _driver
if _driver is None:
_driver =webdriver.Chrome()
yield _driver
print("1111111111")
_driver.quit()
用例方法中参数写入browser引用
import pytest
from selenium import webdriver
import allure
def test_login(browser):
with allure.step("step1:打开登录首页"):
browser.get("http://ip:6009/admin/login/?next=/admin/")
with allure.step("step2:输入账号:admin"):
browser.find_element_by_name("username").send_keys("admin")
with allure.step("step2:输入密码:123456"):
browser.find_element_by_name("password").send_keys("123456")
# 故意断言失败,看是否会截图
assert browser.title == "悠悠"
3.3page_source页面源码
- 使用driver.page_source属性获取页面源码,返回的是字符串;
- 在调试过程中,如果有找不到元素的错误时可以保存当时页面的page_source调试代码;
# 获取页面源码
source = driver.page_source
print(f"页面源码:{source}")
# 把源码写入html文件
with open("pagesource/double.html","w",encoding="utf-8") as f:
f.write(source)
4.多浏览器处理
5. 验证码问题
- 1、自动化时关闭验证码
- 2、测试时使用万能验证码
- 3、使用成功登录后的cookies绕过登录
- 4、使用图像识别技术处理简单图片验证码-- ddddocr模块
- 5、使用selenium模拟鼠标事件处理拖拽等验证码
二、UI自动化框架总结小技巧
1.搜索功能步骤写法
可以把所有搜索条件都列入一个方法中,然后判断参数是否正常传递再决定是否去输入字符到该搜索框
例子:
页面实现方法
class SendOrderSearchPage(BasePage):
__APPMAINFRAME = (By.ID,'appMainFrame')
__SEARCHFCSENDPOLICY = (By.ID,'id_netstmrpacpadsearchFCSendPolicy')
__DELIVERYID = (By.CSS_SELECTOR, '#deliveryId') # 送单编号
__CUSTNO = (By.CSS_SELECTOR, 'input#netsCustId.input_default') # 客户编号
__APPLICANTPHONENO = (By.CSS_SELECTOR, 'input#phoneNo.input_default') # 投保人手机号
__SEARCHBUTTON = (By.CSS_SELECTOR,"input[value='查 询']") # 查询按钮
def searchByApplicantPhoneNo(self,deliveryId=None,custNo=None,phoneNo=None):
self.jump_out_of_all_frames()
self.switch_new_frame(self.__APPMAINFRAME)
self.switch_new_frame(self.__SEARCHFCSENDPOLICY)
# 三个查询条件
if deliveryId: # 先判断是否正常传递了这个参数再去觉得是否输入这个搜索条件
self.do_send_key(deliveryId,self.__DELIVERYID)
if custNo:
self.do_send_key(custNo,self.__CUSTNO)
if phoneNo:
self.do_send_key(phoneNo, self.__APPLICANTPHONENO)
# 点击查询按钮
self.do_find(self.__SEARCHBUTTON).click()
logging.info('查询执行完毕')
用例
class TestSendOrder:
@pytest.mark.parametrize('deliveryId,custNo,phoneNo',
[(None,None,'xx'),
('100002',None,None)])
def test_searchOrder(self,deliveryId,custNo,phoneNo,login_tmr):
login_tmr.quickly_search('10112035349145')\
.entryCustinfo_byID()\
.intoServiceResultPage()\
.dealServiceRessult()
login_tmr.fc_orderSearch().searchByApplicantPhoneNo(deliveryId,custNo,phoneNo)
2.window切回原始窗口
在窗口1里跳转出来一个新窗口2,此时要在窗口2操作需要切换到窗口2才能操作。
当窗口2关闭,再需要操作窗口1,那么需要切换回去。(如果不切换找不到元素定位)
# 自己封装的,但是这个方法切不回原来的?
def switch_window(self):
current_window =self.driver.current_window_handle
print(current_window)
all_windows = self.driver.window_handles
print(all_windows[-1])
self.driver.switch_to.window(all_windows[-1])
def dealServiceRessult(self):
# 切换窗口,自己封装的
self.switch_window()
self.jump_out_of_all_frames()
# 点击咨询险种,等待1秒是为了等待子险种加载出来
self.do_find(self.__FAMILYWEALTHINSURANCE).click()
time.sleep(1)
# 保存按钮
self.do_find(self.__SAVEBUTTON).click()
# 切换回原来的窗口
windws_list = self.driver.window_handles
self.driver.switch_to.window(windws_list[0]) # 不能用
3.conftest.py的login_tmr在用例中的调用
# conftest.py
test_user_data = [{'user': 'xx', 'pwd': 'xx'}]
@pytest.fixture(autouse=True,scope='session',params=test_user_data)
def login_tmr(request):
user = request.param['user']
pwd = request.param['pwd']
yield LoginPage().login(user, pwd)
用例中调用
class TestFcKeepFresh:
def test_productKeepFresh(self,login_tmr ):
# 1.首先是进入客户信息页面
cust_page = login_tmr.quickly_search(cust_id='1000452573063').entryCustinfo_byID()
# 2.客户信息页面找到保鲜按钮,对客户电话进行保鲜
tel = cust_page.switch_to_jfKeepFresh().productlistCustPhone_update()
logging.info(f'更新的电话是{tel}')
# 3.最后返回到客户信息汇总页面查看客户电话是否被成功更新
landPhone = login_tmr.quickly_search(cust_id='1000452573063').entryCustinfo_byID().get_custInfo()['landPhone']
logging.info(f'固定电话是{landPhone}')
说明:
以上代码中第3步之前我只是保鲜操作后回到原信息汇总页面去查看客户电话,但是此时页面没有刷新,不会获取到更新后的电话。
所以解决办法是-》此时我再次使用fixture函数login_tmr去进行查询客户并重新进入到客户信息页。注意我在一个用例中使用login_tmr了两次,但是并没登录两次
原因:
- 因为login_tmr是session级别
- 且属于setup方法,是在用例之前运行的
- 另外login_tmr在用例中就是相当于一个类的实例,不会因为多使用几次就重新进行初始化操作