一、web自动化测试体系与价值
做 UI 自动化之前,要想清楚想让 UI 自动化为你做什么,你想要做成 UI 自动化的的业务是不是稳定的,你要覆盖的场景是不是你每次回归测试必须要测试的,当脚本跑起来的时候,它的不稳定因素是什么?UI自动化也不仅仅是验证 UI 层相关的内容,也可以通过 UI 路径来验证接口的业务逻辑。UI 自动化测试是一把双刃剑,不要一味追求覆盖率,覆盖合适的场景才能形成最高的性价比。
1、 什么时候可以做UI自动化测试
- 业务流程不频繁改动
- UI 元素不频繁改动
- 需要频繁回归的场景
- 核心业务场景等
- 项目周期长,大版本功能稳定,有小功能不断迭代,有必要对核心功能进行不断回归
2、 Web自动化测试学习路线
二、selenium环境准备
1、 Selenium的简介
- 用于web浏览器测试的工具
- 支持的浏览器包括IE,Firefox,Safari,Chrome,Edge等
- 使用简单,可使用Java,Python等多种语言编写用例脚本
- 主要由三个工具构成:WebDriver、IDE、Grid
架构图:
2、selenium环境配置
(1)安装selenium
pip install selenium
(2) Driver的下载与配置
A、windows
- 1、下载和浏览器对应版本的dirver
- 2、 解压下载的驱动,获取到xxxdriver.exe(chromedriver.exe或者msedgedriver.exe等)
- 3、两种方式任选一种:
- a、将xxxdriver.exe放到某个路径,之后添加环境变量
- b、直接将xxxdriver.exe复制到python.exe所在的目录,也就是Python的安装目录
B、mac/linux
- 1、下载和浏览器对应版本的dirver
- 2、 解压下载的驱动,获取到xxxdriver
- 3、将xxxdriver复制到/user/local/bin目录即可
3、selenium 4.x的一些介绍
4、Selenium Manager
Selenium Manager 为 Selenium 提供自动化的驱动程序和浏览器管理。
(1) 自动浏览器管理
Selenium Manager自动检查浏览器驱动是否安装,如果安装正确,则使用此驱动,如果不是,Selenium Manager将管理CfT,无论哪种情况,浏览器最终也会受到管理。
- 前提:干掉之前手动安装的driver.exe;
- 安装:
pip install webdriver-manager
总结:无需手动更新driver。
- Chrome:基于Chrome for Testing(CfT)从Selenium 4.11.0开始。
- Firefox:基于公开的Firefox版本,从Selenium 4.12.0开始。
- Edge:计划用于Selenium4.13.0
from selenium import webdriver
# 导包的时候注意导入对应浏览器的service和manager
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
service = Service(executable_path=ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
三、selenium api使用
1、浏览器控制
操作 | api | 说明 |
---|---|---|
打开浏览器 | driver.get("url") |
通过webdriver驱动浏览器打开一个网址 |
刷新driver驱动的网址窗口 | driver.refresh() |
注意:是get打开的网址所在的窗口,不一定是肉眼看到的页面窗口 |
回退到上一个driver驱动的网址 | driver.back() |
注意:是get打开的网址所在的窗口回退到上一个地址,不一定是肉眼看到的页面窗口 |
浏览器窗口最大化 | driver.maximize_window() |
|
浏览器窗口最小化 | driver.minimize_window() |
|
关闭driver驱动的网址页面 | driver.close() |
注意:是关闭get打开的网址页面,不一定是肉眼看到的页面窗口 |
关闭整个浏览器进程 | driver.quit() |
如果不调用quit方法杀掉浏览器进程,电脑CPU会炸 |
打开新窗口/页面 | driver.execute_script(js) |
selenium没有打开新窗口/页面的方法,需要执行js脚本打开新窗口/页面 |
注意: 注意区分页面和窗口,也要注意refresh、back、close操作的具体是哪个窗口/页面
from time import sleep
from selenium import webdriver
# 创建驱动对象
driver = webdriver.Edge()
# 打开网页--百度
driver.get("https://www.baidu.com")
# 脚本停止执行3s
sleep(3)
# 窗口最大化
driver.maximize_window()
# 脚本停止执行3s
sleep(3)
# 打开一个新页面--搜狗
# js = 'window.open("https://www.sogou.com", "_blank", "resizable,scrollbars,status");' # 这个是打开新窗口
js = 'window.open("https://www.sogou.com");'# 这个是打开新页面
driver.execute_script(js)
# 脚本停止执行3s
sleep(3)
# 在百度窗口访问淘宝
driver.get("https://www.taobao.com/")
# 脚本停止执行3s
sleep(3)
# 刷新淘宝网页
driver.refresh()
# 脚本停止执行3s
sleep(3)
# 回退到百度网页
driver.back()
# 脚本停止执行3s
sleep(3)
# 关闭当前窗口--注意:这里关闭的是百度,而不是搜狗
driver.close()
# 脚本停止执行3s
sleep(3)
# 窗口最小化
driver.minimize_window()
# 脚本停止执行3s
sleep(3)
# 关闭整个浏览器
driver.quit()
补充: 使用js打开新窗口或者新页面
-
window.open(strUrl, strWindowName, [strWindowFeatures]);
-
strUrl: 新窗口需要载入的url地址。
-
strWindowName:新窗口的名字,通过这个名字我们可以获得新窗口的引用,容易重复,如果我们希望每次打开新窗口都是一个全新的窗口,可以设置成关键字 “_blank”。
- 注意:_blank 是打开一个新窗口,不给这个参数才是打开新页面,注意区别,使用上方代码可以肉眼观察效果;
-
strWindowFeatures:新窗口的一些设置,比如是否显示菜单栏,是否可以滚动和缩放窗口大小等。
-
2、元素定位方法
注意:
-
在最新的selenium版本中已经舍弃了
driver.find_element_by_定位方式(定位元素)
系列的定位方法,只支持driver.find_element(By.定位方式, 定位元素)
定位方法; -
在使用响应定位方法的时候,最好在网页源代码中搜索一下有没有重复,必须要用唯一的定位属性,不然会遇到让你炸裂的问题;
定位方式 | api | 说明 |
---|---|---|
id属性值 | driver.find_element(By.ID,“id”) | 标签id 属性对应的值 |
name属性值 | driver.find_element(By.NAME,“name”) | 标签name属性对应的值 |
css选择器 | driver.find_element(By.CSS_SELECTOR,“css_selector”) | 标签的css选择器 |
xpath选择器 | driver.find_element(By.XPATH,“xpath”) | 标签的xpath选择器 |
class属性值 | driver.find_element(By.CLASS_NAME,“class_name”) | 标签的class属性对应的值 |
超链接文本 | driver.find_element(By.LINK_TEXT,“link_text”) | 基本都是用于a标签的文本定位,注意;文本需要完全匹配 |
超链接文本 | driver.find_element(By.PARTIAL_LINK_TEXT,“partial_link_text”) | 和上一个方法一样,只是如果多个元素匹配,则只会选择第一个元素。 |
标签名 | driver.find_element(By.TAG_NAME,“tag_name”) | 标签名称 |
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Edge()
driver.get("https://www.baidu.com/")
driver.maximize_window()
# 睡一秒
sleep(1)
# # id定位百度一下按钮
# button = driver.find_element(By.ID,"kw")
# # name定位搜索输入框
# query = driver.find_element(By.NAME,"wd")
#
# # 点击过去
# query.send_keys("刘涛")
# button.click()
# sleep(1)
# # css选择器定位登录按钮
# login = driver.find_element(By.CSS_SELECTOR,"#s-top-loginbtn")
# # 点击登录按钮
# login.click()
# sleep(2)
#
# # xpath选择器定位登录弹框X
# close = driver.find_element(By.XPATH,"//*[@id='TANGRAM__PSP_4__closeBtn']")
# # 关闭登录弹框继续首页定位
# close.click()
# sleep(1)
# # 使用标签class属性值定位--百度搜索输入框
# query = driver.find_element(By.CLASS_NAME,"s_ipt")
# query.send_keys("张雨绮")
# sleep(2)
# # 使用a标签的文本定位--百度新闻
# news = driver.find_element(By.LINK_TEXT,"新闻")
# news.click()
# sleep(2)
# driver.find_element(By.PARTIAL_LINK_TEXT,"partial_link_text")
# driver.find_element(By.TAG_NAME,"tag_name")
driver.quit()
定位策略
选择定位器通用原则
-
与研发约定的属性优先(比如约定css,则用css 属性选择器:
[name='locate']
) -
身份属性 id,name(web 定位)
-
复杂场景使用组合定位:
- xpath,css
- 属性动态变化(id,text)
- 重复元素属性(id,text,class)
- 父子定位(子定位父)
-
js定位
补充:css定位,更多css定位请参考文章
-
绝对定位:从很远的标签开始一级一级的往下下找,从浏览器copy过来的就是绝对定位方式;
-
相对定位:通过几个css选择器的相对 关系 进行定位,比如 相邻兄弟(+)、兄弟(~)、父子(>)、后代(空格) 相对关系;
-
相对定位的好处:
- 可维护性更强
- 语法更加简洁
- 解决各种复杂的定位场景
-
案例:分别使用绝对定位和相对定位来定位同一个元素的区别
# 绝对定位
$("#ember63 > td.main-link.clearfix.topic-list-data > span > span > a")
# 相对定位
$("#ember63 [title='新话题']")
-
顺序定位,使用相对关系及伪类选择器(
nth-child(n)
及nth-of-type(n)
)详情这篇文章找- 父子关系+顺序:
'#form>input:nth-child(n)'
–找的是input的爹form的第n个孩子,也就是冒号前面元素的爹的第n个孩子,只有顺序和冒号前面的选择器都匹配的才会被找到; - 父子关系+标签类型+顺序:
'#form>input:nth-of-type(n)'
–匹配input的爹form的儿子中,叫input的第n个;
- 父子关系+顺序:
-
检验css选择器是否正确以及该css选择器能匹配到多少元素的方法:
- 1、直接打开浏览器开发者选项,在元素页面
ctrl+f
把ccss选择器输入,按回车,能搜索到元素就说明选择器编写正确,能在源码中搜索到多少个就说明能匹配到多少个; - 2、在开发者选项的控制台,使用jQuery的语法编写选择器直接打印找到的元素,点开查看length属性,length是几就找到几个,格式:
$("css选择器")
;
- 1、直接打开浏览器开发者选项,在元素页面
-
注意:css类选择器,如果在源码中有多个类名通过空格连接,在写css类选择器的时候,复制类名之后要把中间空格干掉改成
.
即可;
补充:xpath定位
3、强制等待、隐式等待、显示等待
4、控件交互
(1)普通控件交互
场景 | api | 说明 |
---|---|---|
点击 | WebElement.click() | |
输入 | WebElement.send_keys(str) | |
清空 | WebElement.clear() | |
获取标签属性 | WebElement.get_attribute(“属性名称”) | |
获取标签文本信息 | WebElement.text | |
获取标签名 | WebElement.tag_name |
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Edge()
driver.get("https://www.baidu.com")
driver.maximize_window()
news = WebDriverWait(driver,10,0.5).until(expected_conditions.element_to_be_clickable((By.LINK_TEXT,"新闻")))
print(news)
# 标签文本
print(news.text) # 新闻
# 标签名
print(news.tag_name) # a
# 标签属性
print(news.get_attribute("href")) # http://news.baidu.com/
driver.quit()
(2)高级控件交互ActionChains类
5、网页 frame 与多窗口处理
(1)多窗口处理
- 点击某些链接,会重新打开⼀个窗口,对于这种情况,想在新页⾯上操作,就
得先切换窗口。 - 获取窗口的唯⼀标识句柄,只需要切换句柄,就可以在多个页⾯灵活操作。
A、具体流程
- 1、确认想要操作的窗口句柄:
driver.current_window_handle
–用于后续切换的时候知道切换到哪个句柄; - 2、获取所有的窗口句柄:
driver.window_handles
–返回一个所有窗口句柄的列表; - 3、切换到想要操作的窗口句柄:
driver.switch_to.window(window_name)
–通过之前获取的窗口句柄名称,然后从窗口句柄列表中找;
B、来个案例
- 需求:
- 1、打开百度首页,点击登录,点击登录弹窗中立即注册,进入注册页面;
- 2、在注册页面输入用户名、手机号之后,通过窗口句柄切换到百度首页;
- 3、在百度首页输入用户名、密码,勾选登录协议,点击登录;
代码实现:
"""
多窗口切换
* 需求:
* 1、打开百度首页,点击登录,点击登录弹窗中立即注册,进入注册页面;
* 2、在注册页面输入用户名、手机号之后,通过窗口句柄切换到百度首页;
* 3、在百度首页输入用户名、密码,勾选登录协议,点击登录;
"""
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Edge()
driver.maximize_window()
# 1、打开百度首页,点击登录,点击登录弹窗中立即注册,进入注册页面;
driver.get("https://www.baidu.com")
driver.implicitly_wait(5)
print(f"打开百度首页之后的窗口句柄:{driver.current_window_handle}") # 80529454E26756CE0F5DD5EE3363D2BA
print(f"打开百度首页之后的所有窗口句柄:{driver.window_handles}") # ['80529454E26756CE0F5DD5EE3363D2BA']
# 点击登录
driver.find_element(By.LINK_TEXT,"登录").click()
# 点击立即注册
driver.find_element(By.LINK_TEXT,"立即注册").click()
print(f"进入注册页面之后的窗口句柄:{driver.current_window_handle}") # 80529454E26756CE0F5DD5EE3363D2BA
print(f"进入注册页面之后的所有窗口句柄:{driver.window_handles}") # ['80529454E26756CE0F5DD5EE3363D2BA', 'EE695F66B3754567AEDE1F5ECDF83D44']
# 获取所有的窗口句柄
handles = driver.window_handles
# 2、在注册页面输入用户名、手机号之后,通过窗口句柄切换到百度首页;
# 需要先切换到注册页面的窗口句柄,不然无法定位到注册页面的元素
driver.switch_to.window(handles[-1])
print(f"切换到注册页面之后的当前句柄:{driver.current_window_handle}") # EE695F66B3754567AEDE1F5ECDF83D44
driver.find_element(By.ID,"TANGRAM__PSP_4__userName").send_keys("register_username")
driver.find_element(By.ID,"TANGRAM__PSP_4__phone").send_keys("register_phone")
driver.find_element(By.ID,"TANGRAM__PSP_4__password").send_keys("register_password")
time.sleep(3)
# 通过窗口句柄切换到百度首页
driver.switch_to.window(handles[0])
print(f"再次切换到首页之后的当前句柄:{driver.current_window_handle}") # 80529454E26756CE0F5DD5EE3363D2BA
time.sleep(3)
# 3、3、在百度首页输入用户名、密码,勾选登录协议,点击登录;
driver.find_element(By.ID,"TANGRAM__PSP_11__userName").send_keys("login_phone")
driver.find_element(By.ID,"TANGRAM__PSP_11__password").send_keys("login_password")
driver.find_element(By.ID,"TANGRAM__PSP_11__isAgree").click()
driver.find_element(By.ID,"TANGRAM__PSP_11__submit").click()
time.sleep(3)
driver.quit()
(2)网页frame处理
A、frame介绍
-
在web⾃动化中,如果⼀个元素定位不到,那么很⼤可能是在iframe中。
-
什么是frame?
- frame是html中的框架,在html中,所谓的框架就是可以在同⼀个浏览器中显⽰不⽌⼀个页⾯。
- 基于html的框架,又分为垂直框架和⽔平框架(cols,rows)。
-
Frame 分类
- frame标签包含frameset、frame、iframe三种,
- frameset:和普通的标签⼀样,不会影响正常的定位,可以使⽤css、xpath、id、name、class等任意种⽅式定位
- frame与iframe:对selenium定位⽽⾔是⼀样的,需要切换进去之后才可以对立面的元素进行定位;
-
frame存在两种:⼀种是嵌套的,⼀种是未嵌套的。
B、切换frame的方法:
-
drive.switch_to.frame():切换到某个指定frame;
- frame接收三种参数:
- 字符串类型的frame标签的name属性值:driver.switch_to.frame(‘frame_name’)
- 整数类型的frame的下标:driver.switch_to.frame(1)
- 或者是定位到的frame元素:driver.switch_to.frame(driver.find_elements(By.TAG_NAME, “iframe”)[0])
- frame接收三种参数:
-
drive.switch_to.default_content():切换到默认frame
-
drive.switch_to.parent_frame():切换到父级frame,如果当前的frame已经是最顶层frame,那保持不变;
C、frame处理案例
- 地址:
https://mail.163.com/
- 需求:在163邮箱首页输入账号和密码点击登录;
- 不切换frame是定位不到元素的;
代码实现:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
drive = webdriver.Edge()
drive.maximize_window()
drive.get("https://mail.163.com/")
drive.implicitly_wait(5)
# # 不切换frame的时候直接定位输入框---定位不到
# email = drive.find_element(By.XPATH,'//*[@name="email"]').send_keys("email") # NoSuchElementException
# 切换frame--方式1:传入定位到的frame元素
frame = drive.find_element(By.XPATH,'//*[@id="loginDiv"]/iframe')
drive.switch_to.frame(frame)
drive.find_element(By.XPATH,'//*[@name="email"]').send_keys("email")
drive.find_element(By.XPATH,'//*[@name="password"]').send_keys("password")
time.sleep(2)
drive.find_element(By.ID,"dologin").click()
time.sleep(3)
drive.quit()
6、文件上传
- input标签的文件上传:可以直接使⽤element.send_keys(⽂件地址)上传⽂件;
-
非input型上传:
-
- autoIT,借助外力,我们去调用其生成的au3或exe文件。
-
- Python pywin32库,识别对话框句柄,进而操作。
-
- SendKeys库–不稳定,不推荐。
-
- keybd_event,跟3类似,不过是模拟按键,ctrl+a,ctrl+c, ctrl+v…。
-
7、弹框处理
(1)弹框类型
-
div模态框:使用xpath、css等定位方法正常定位;
-
系统弹框:
- Alert弹窗:只有信息及确认按钮
- Confirm弹窗:在Alert弹窗基础上增加了取消按钮
- Prompt类型弹框:在Confirm的基础上增加了可输入文本内容的功能
- Alert弹窗:只有信息及确认按钮
-
通过js代码动态生成的div弹框:这种弹框基本都是点击js代码监听到点击时间弹出的提示信息,1-2s之后会自动消失,类似于Android中的toast,这种正常定位即可;
(2)系统弹框处理方法
- driver.switch_to.alert:切换到弹框并且获取到弹框对象
- alert.text:获取弹框中的文案;
- alert.accept():接受提示框,相当于点击确定;
- alert.dismiss():不接收提示框,相当于点击取消;
- alert.send_keys():给提示框输入内容;
(3)系统弹框处理案例
"""
系统弹框处理
* Alert弹窗:只有信息及确认案例
* Confirm弹窗:在Alert弹窗基础上增加了取消按钮
* Prompt类型弹框:在Confirm的基础上增加了可输入文本内容的功能
"""
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Edge()
driver.maximize_window()
driver.get("https://vip.ceshiren.com/#/ui_study/date_picker")
driver.implicitly_wait(15)
# d点击按钮出现alert弹框
driver.find_element(By.ID,"warning_btn").click()
# 获取alert弹框
alert = driver.switch_to.alert
print(f"alert对象是啥:{alert}")# <selenium.webdriver.common.alert.Alert object at 0x0000028CFD44E590>
# 获取警告框中的文案
alerttext = alert.text
print(f"弹框中的文案:{alerttext}")
time.sleep(3)
# 接收弹框--相当于点击弹框中的确定
alert.accept()
time.sleep(3)
driver.quit()
(4)js动态div弹框案例
"""
div弹框处理
"""
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
driver = webdriver.Edge()
driver.maximize_window()
driver.get("https://vip.ceshiren.com/#/ui_study/date_picker")
driver.implicitly_wait(15)
# 点击按钮出现toast--使用action模拟鼠标点击
btn = driver.find_element(By.ID,"success_btn")
action = ActionChains(driver)
time.sleep(1)
# 鼠标移上去
action.move_to_element(btn)
time.sleep(1)
# 点击--无语,点一次居然不出现toast,那就双击
action.double_click().perform()
time.sleep(1)
# 获取div弹框中的文案
toast = driver.find_element(By.XPATH,'//*[@role="alert"]')
print(f"toast中的文案:{toast.text}")
time.sleep(3)
driver.quit()
8、浏览器复用–复用已有浏览器
(1)为什么需要使用浏览器复用
-
自动化测试过程中,存在人为介入场景;
- 人为处理登录:无法通过输入用户名密码的方式解决登录问题,比如扫描登录,后面所有的脚本都无法执行;
-
提高调试 UI 自动化测试脚本效率;
- 调试时跳过步骤: 比如有多条测试用例,在第30步的时候卡住了,修改之后重新运行,需要从第一步开始运行,通过浏览器复用,可以只执行这一步;—使用debug模式启动浏览器,并在页面人工手动点击进入需要调试的页面,多余的步骤在脚本中注释掉,只运行需要调试的哪一步,脚本会复用这个浏览器直接就找到了需要调试的页面;
-
说明:复用已有浏览器的具体脚本运行步骤如下:
- 1、先把脚本写好放着不动,不着急执行;
- 2、使用如下步骤将将浏览器的debug模式配置好,并以debug模式成功启动浏览器;
- 3、在启动的浏览器中输入网址,人工登录或者是人工点击进入到脚本需要操作的界面;
- 4、此时才去运行脚本,脚本不会启动新的浏览器窗口或者进程,而是复用已经启动了的debug模式下的浏览器,之所以叫复用已有浏览器的缘由也在于此;
- 5、只要debug模式启动的浏览器窗口一直不关,脚本可以一直复用这个浏览器,这对脚本调试也带来了很大的便捷;
(2)浏览器复用步骤
A、google浏览器复用步骤
-
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")
B、Edge浏览器复用步骤
-
1、配置环境变量,保证命令行输入Edge命令可以正常启动浏览器;
-
2、 关闭所有的Edge浏览器窗口及进程–查看任务管理器里的浏览器进程;
-
3、重启命令行窗口,在命令行输入命令启动浏览器:
# 1、正常情况下使用这个命名就可以
msedge.exe --remote-debugging-port=9225
# 2、有个时候需要加user-data-dir参数,否则会失败
msedge.exe --remote-debugging-port=9225 --user-data-dir="xxx"
- 4、验证浏览器是否启动成功
- 在打开的浏览器中输入
localhost:9225
,只要不显示无法访问此网站就表示配置成功;
- 在打开的浏览器中输入
- 5、代码中增加EdgeOptions:
from selenium import webdriver
# 注意:导包的时候不要导错,chrome.options和edge.options要分清楚
from selenium.webdriver.edge.options import Options
from selenium.webdriver.common.by import By
# 获取浏览器配置的实例对象
option = Options()
# 设置浏览器的debug模式和地址属性---值就是通过命令行启动浏览器时的localhost:端口
option.debugger_address = "localhost:9225"
# 创建浏览器驱动的时候需要把option对象传入
driver = webdriver.Edge(options=option)
driver.maximize_window()
# 访问企业微信
driver.get("https://work.weixin.qq.com/wework_admin/frame")
(3)使用案例
-
需求:进入企业微信,点击跳转到客户联系;
- 地址:
https://work.weixin.qq.com/wework_admin/frame
- 说明:企业微信需要扫描登录,所以这里只能使用复用已有浏览器,先人工扫描登录进入到需要操作的页面之后,再去启动脚本才能实现需求;
- 地址:
A、复用google浏览器实现
import time
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")
# 点击跳转到客户联系
driver.find_element(By.XPATH,'//*[(text()="客户联系")]').click()
time.sleep(5)
driver.quit()
B、复用Edge浏览器实现
import time
from selenium import webdriver
# 注意:导包的时候不要导错,chrome.options和edge.options要分清楚
from selenium.webdriver.edge.options import Options
from selenium.webdriver.common.by import By
# 获取浏览器配置的实例对象
option = Options()
# 设置浏览器的debug模式和地址属性---值就是通过命令行启动浏览器时的localhost:端口
option.debugger_address = "localhost:9225"
# 创建浏览器驱动的时候需要把option对象传入
driver = webdriver.Edge(options=option)
driver.maximize_window()
# 访问企业微信
driver.get("https://work.weixin.qq.com/wework_admin/frame")
# 点击跳转到客户联系
driver.find_element(By.XPATH,'//*[(text()="客户联系")]').click()
time.sleep(5)
driver.quit()
9、cookie复用
(1)什么是cookie
-
Cookie 是一些身份认证数据信息,当登录成功之后,服务端会返回带session标识的cookie信息存储在电脑的浏览器上,后续发送请求浏览器会带上这个cookie,服务器就知道是谁发过来的请求,也就是认证通过,这样就可以正常访问登录后的接口信息;
-
当session过期或者是用户退出登录之后,之前的cookie就失效了,需要用户重新登录,这就是session-cookie的认证机制;
(2) 为什么要使用Cookie自动化登录
- 复用浏览器仍然在每次用例开始都需要人为介入启动浏览器;
- 若用例需要经常执行,复用浏览器则不是一个好的选择;
- 大部分cookie的时效性都很长,扫一次登录获取cookie后可以使用多次;
(3)cookie复用流程
-
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之前需要先访问登录登录页面/刷新浏览器;
-
(4)案例
-
需求:对企业微信通讯录页面进行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)
10、自动化关键数据记录
内容 | 作用 |
---|---|
日志 | 1. 记录代码的执行记录,方便复现场景;2. 可以作为bug依据 |
截图 | 1. 断言失败或成功截图;2.异常截图达到丰富报告的作用;3. 可以作为bug依据 |
page source | 1. 协助排查报错时元素当时是否存在页面上 |
(1)行为日志记录
- 日志封装
- 脚本日志级别
- debug记录步骤信息
- info记录关键信息,比如断言等
日志封装.py
# 日志配置
import logging
# 创建logger实例
logger = logging.getLogger('simple_example')
# 设置日志级别
logger.setLevel(logging.DEBUG)
# 流处理器
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 日志打印格式
formatter = logging.Formatter\
('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 添加格式配置
ch.setFormatter(formatter)
# 添加日志配置
logger.addHandler(ch)
脚本:
# 日志与脚本结合
class TestDataRecord:
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(3)
def teardown_class(self):
self.driver.quit()
def test_log_data_record(self):
# 实例化self.driver
search_content = "霍格沃兹测试开发"
# 打开搜狗首页
self.driver.get("https://www.sogou.com/")
logger.debug("打开搜狗首页")
# 输入霍格沃兹测试学院
self.driver.find_element(By.CSS_SELECTOR, "#query").\
send_keys(search_content)
logger.debug(f"搜索的内容为{search_content}")
# 点击搜索
self.driver.find_element(By.CSS_SELECTOR, "#stb").click()
# 搜索结果
search_res = self.driver.find_element(By.CSS_SELECTOR, "em")
logger.info(f"搜索结果为{search_res.text}")
assert search_res.text == search_content
(2)异常截图记录
-
driver.save_screenshot(截图路径+名称)
- 这个方法底层调用了
get_screenshot_as_file(filename)
方法;
- 这个方法底层调用了
- 记录关键页面
- 断言页面
- 重要的业务场景页面
- 容易出错的页面
案例:
"""
异常截图
"""
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
driver = webdriver.Edge()
driver.maximize_window()
driver.get("https://vip.ceshiren.com/#/ui_study/date_picker")
driver.implicitly_wait(5)
# 双击按钮
action = ActionChains(driver)
btn = driver.find_element(By.ID,"primary_btn")
action.double_click(btn).perform()
# 截图保留双击之后的效果
driver.save_screenshot("screenshot/double.png")
driver.quit()
(3)page_source页面源码记录
- 使用driver.page_source属性获取页面源码,返回的是字符串;
- 在调试过程中,如果有找不到元素的错误时可以保存当时页面的page_source调试代码;
案例:
"""
获取页面源码
"""
import time
from selenium import webdriver
driver = webdriver.Edge()
driver.maximize_window()
driver.get("https://vip.ceshiren.com/#/ui_study/date_picker")
driver.implicitly_wait(5)
time.sleep(5)
# 获取页面源码
source = driver.page_source
print(f"页面源码:{source}")
# 把源码写入html文件
with open("pagesource/double.html","w",encoding="utf-8") as f:
f.write(source)
driver.quit()
11、异常自动截图
初步方案:使用异常捕获,操作元素的时候产生了异常,捕获异常之后保存截图和页面源码;
import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestBaidu:
def test_search(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get("https://www.baidu.com")
try:
# 如果操作元素出现了异常,则捕获异常
self.driver.find_element(By.ID, "kw1").send_keys("selenium")
except Exception:
# 捕获异常之后保留截图和页面源码
# 拼接文件路径
temp_time = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime())
shotpath = "screenshot/search/" + temp_time + "百度首页截图.png"
# 保存截图
self.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(self.driver.page_source)
# 将源码文件上传到allure报告
allure.attach.file(
source=sourcepath,
name="页面源码",
attachment_type=allure.attachment_type.TEXT,
extension="html"
)
# 为了不然异常处理影响用例的真实运行结果,需要再次把异常跑出去
raise Exception
time.sleep(3)
self.driver.quit()
问题:如果每个元素操作都使用异常捕获,那会增加很大代码量;
优化方案:定义一个装饰器实现异常处理,给用例加上装饰器即可;
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 TestBaidu:
@ui_exception_recode
def test_search(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get("https://www.baidu.com")
# 百度输入框的id=kw,这里为了让异常产生用了kw1,也就是页面找不到id=kw1这个元素,抛出异常
self.driver.find_element(By.ID, "kw1").send_keys("selenium")
time.sleep(3)
self.driver.quit()
12、多浏览处理
(1) 多浏览器测试概述
- 是跨不同浏览器组合验证网站或 web 应用程序功能的过程
- 是兼容性测试的一个分支,用于保持功能和质量的一致性
- 适用于面向客户的网站和组织内部使用的站点
(2)多浏览器的实现方案
(3)使用pytest的hook函数新增选择浏览器的命令行参数
-
pytest_addoption
添加命令行参数组/命令行参数 -
pytest_configure
解析命令行选项,每个插件都会用到这个hook函数
13、执行js脚本(js代码)
(1)常用js脚本-更多js用法查看这里
A、 通过 css 查找操作元素
- 点击元素(对应click)
- 修改属性值–input标签对应的值(对应send_keys)
- 元素的类属性
- 元素的文本属性
// 百度首页:https://www.baidu.com/
// 修改属性值--input标签对应的值(对应send_keys)
document.querySelector("#kw").value = "霍格沃兹测试学院"
// 点击操作
document.querySelector("#su").click()
// 修改属性值后再点击
document.querySelector("#kw").value="霍格沃兹";document.querySelector("#su").click();
// 淘宝首页: https://www.taobao.com/
// 修改元素的类属性
document.querySelector("#J_SiteNavMytaobao").className\
="site-nav-menu site-nav-mytaobao site-nav-multi-menu J_MultiMenu site-nav-menu-hover"
// 测试人首页:https://ceshiren.com/
// 获取元素内的文本信息
document.querySelector("#ember63").innerText
案例1:修改时间控件的值
// 修改时间控件的值--https://www.12306.cn/index/
document.querySelector("#train_date").value="2020-02-02"
案例2:通过修改元素类属性的方式实现鼠标悬停下拉框处理
- 淘宝首页-我的淘宝控件:
-
用selenium的api处理:先定位到元素,然后模拟鼠标移动到该元素并悬停,才能出现下拉框,之后才可以对下拉框里面的元素进行操作;
-
使用js代码处理:打开开发者选项,找到控件的源代码,然后把鼠标放上去在移开,反复操作,然后去观察html代码的变化,发现class类属性的值在变化,那就大胆猜测,这个下拉框是受class类属性的值控制的,那就通过js代码直接修改这个控件的class属性值即可然下拉框一直显示;
-
// 修改元素的类属性
document.querySelector("#J_SiteNavMytaobao").className="site-nav-menu site-nav-mytaobao site-nav-multi-menu J_MultiMenu site-nav-menu-hover"
B、 JS脚本滚动操作
- 页面滚动到底部:
document.documentElement.scrollTop=10000;
- 页面滚动到指定元素位置:
document.querySelector('css表达式').scrollIntoView();
- 先使用css选择器定位到元素,然后再滚动到指定元素;
案例:在测试人社区首页滚动到某个页面不可见元素
// 页面滚动到指定元素位置
document.querySelector('[data-topic-id="25012"] td span a').scrollIntoView()
(2)js脚本使用步骤
- 先在浏览器开发者工具的consol中调试js代码,调通达到预期结果之后再用selenium的api去执行js代码;
- 调用selenium的api去执行js代码;
- 执行普通js代码:
driver.execute_script("js 代码")
- 执行带返回值的js代码:
driver.execute_script("return js脚本")
- 执行普通js代码:
"""
执行js代码
需求:打开淘宝首页
1、定位到我的淘宝-我的足迹,获取控件的文本信息
2、滚动到页面不可见某个控件,获取文案
"""
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://www.taobao.com/")
driver.implicitly_wait(5)
# 通过js修改”我的淘宝“控件的class属性值,让hover下拉框一直出现,然后使用selenium的定位去定位“我的足迹”,获取控件的文本信息
driver.execute_script('document.querySelector("#J_SiteNavMytaobao").className="site-nav-menu site-nav-mytaobao site-nav-multi-menu J_MultiMenu site-nav-menu-hover";')
time.sleep(3)
# 使用selenium的定位去定位“我的足迹”
my = driver.find_element(By.XPATH,'//*[@class="site-nav-menu-bd-panel menu-bd-panel"]//*[text()="我的足迹"]')
print(f"控件文本信息为:{my.text}")
time.sleep(5)
# 滚动到页面不可见某个控件,获取文案
js1 = '''return document.querySelector("[class='mod mod-last '] div h4 span");'''
js2 = '''document.querySelector("[class='mod mod-last '] div h4 span").scrollIntoView();'''
el = driver.execute_script(js1)
print(el)
driver.execute_script(js2)
features = driver.find_element(By.CSS_SELECTOR,'[class="mod mod-last "] div h4 span')
print(f"不可见控件文案:{features.text}")
14、浏览器配置 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
option = webdriver.ChromeOptions()
# 设置窗口最大化
# 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/")
(1)创建Options实例的方法
A、直接通过父类Optoins进行实例化
- 注意:通过这种方式的时候,导包的时候需要导入对应浏览器的options包
# 注意:导包的时候不要导错,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()
B、通过webdirver的方法去创建options对象–在新版本中这种方式已经不推荐
from selenium import webdriver
option = webdriver.ChromeOptions()
option = webdriver.EdgeOptions()
option = webdriver.FirefoxOptions()
(2)常用配置
- 无头模式:
--headless
- 窗体最大化
start-maximized
- 指定浏览器分辨率
window-size=1920x3000
from selenium import webdriver
from selenium.webdriver.common.by import By
option = webdriver.ChromeOptions()
# 设置窗口最大化
# 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/")
# 随便定位一个元素获取文本
t = driver.find_element(By.XPATH,'//*[text()="所有类别"]').text
print(t)
driver.quit()
(3)注意:在selenium 4.x以上之后,options和capability 有千丝万缕的关系,需要花时间去看看源码好好梳理一下两者的区别与联系和具体用法–TODO
15、 capability 配置参数解析与分布式运行
(1)capability
- Capabilities是WebDriver支持的标准命令之外的扩展命令(配置信息)
- 配置web驱动的属性,如浏览器名称、浏览器平台等。
- 结合Selenium Grid完成分布式、兼容性等测试
- 注意:在selenium 4.x以上之后,options和capability 有千丝万缕的关系,需要花时间去看看源码好好梳理一下两者的区别与联系和具体用法–TODO
(2)Selenium Grid
- Selenium Grid 允许我们在多台机器上并行运行测试,并集中管理不同的浏览器版本和浏览器配置(而不是在每个单独的测试中)。
-
Selenium Grid 4.x
(3)老版本使用案例
- 保证本地可以正常调通
- 实例化Remote()类并添加相应的配置
- 远程地址
- 设备配置
from selenium import webdriver
from selenium.webdriver.common.by import By
def test_ceshiren_ca():
# mac
# capability = {"platformName": "mac"}
capability = {"platformName": "windows"}
driver = webdriver.Chrome(desired_capabilities=capability)
driver.get("https://ceshiren.com/")
def test_ceshiren_grid():
# 1. 保证本地执行代码调通
# 2. 切换为webdriver.remote
# driver = webdriver.Chrome()
executor_url = "https://selenium-node.hogwarts.ceshiren.com/wd/hub" # 这是远端的selenium grid地址--这个地址目前已经不能用了,这里仅展示代码
capabilities = {"browserName":"chrome","browserVersion":"99.0"}
# capabilities = {"browserName":"firefox"}
driver = webdriver.Remote(
# Either a string representing URL of the remote server or a custom
# remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
command_executor=executor_url,
# A dictionary of capabilities to request when starting the browser session. Required parameter.
desired_capabilities=capabilities
)
driver.implicitly_wait(3)
driver.get("https://ceshiren.com/")
login_text = driver.find_element(By.CSS_SELECTOR, ".login-button").text
print(login_text)
16、验证码问题
- 1、自动化时关闭验证码
- 2、测试时使用万能验证码
- 3、使用成功登录后的cookies绕过登录
- 4、使用图像识别技术处理简单图片验证码-- ddddocr模块
- 5、使用selenium模拟鼠标事件处理拖拽等验证码
四、PageObject设计模式
1、POM建模原则
-
字段意义
- 不要暴露页面内部的元素给外部
- 不需要建模 UI 内的所有元素
-
方法意义
- 用公共方法代表 UI 所提供的功能
- 方法应该返回其他的 PageObject 或者返回用于断言的数据
- 同样的行为不同的结果可以建模为不同的方法
- 不要在方法内加断言
案例:搜索场景-POM 脚本
from selenium import webdriver
from selenium.webdriver.common.by import By
class SearchPage:
__INPUT_SEARCH = (By.NAME, "q")
__BUTTON_SEARCH = (By.CSS_SELECTOR, "i.search")
__SPAN_STOCK = (By.XPATH, "//table//strong")
def __init__(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(3)
self.driver.get("https://xueqiu.com/")
def search_stock(self, stock_name: str):
self.driver.find_element(*self.__INPUT_SEARCH).send_keys(stock_name)
self.driver.find_element(*self.__BUTTON_SEARCH).click()
name = self.driver.find_element(By.XPATH, "//table//strong").text
return name
2、 恢复用例初始状态
-
问题:单条用例执行完成之后如果不恢复下一条用例的开始状态(回复用例初始页面),则会影响下一条用例的执行。
-
解决方案:
- 每条用例执行完成都
quit()
(影响执行效率) - 封装一个方法,用例执行完成之后回到首页
- 每条用例执行完成都
3、 数据清理
-
清理策略
- 在前置处理中执行
- 在后置处理中执行
-
清理方式
- 调用业务接口
- 通过UI自动化方式操作
- 连接数据库执行SQL(不推荐)