保姆入门级Web自动化学习资料
- 前言
- Web自动化环境搭建
- 通过常见属性定位
- 通过其它属性
- 标签
- 层级定位
- 索引
- 数据驱动
- 键盘操作
- 鼠标操作
- 获取元素属性
- 判断是否存在
- 弊端
- 隐式等待
- 弊端
- 无头模式
- 日志流程
- 控制台处理器
- 运行结果
- 文件处理器
- POM优势有哪些
- 为什么使用POM设计模式
- 思路解析
- login_page.py文件
- common.py
- TestCase.py
- unittest介绍
- unittest语法
- unittest四大组件
前言
- 本章内容需有一定Python基础,如何不懂的,请先学习Python。
什么??没有好的学习资料,给你准备好了!!
- 爆肝8万字的Python基础学习资料
- Python基础入门视频资料
- 以上资料都是本人亲自录制
Web自动化环境搭建
软件准备
- python64位安装包
- chrome64位浏览器&驱动
- 浏览器驱动下载
- 注意:chromedriver与chrome版本要对应。具体可查看该对应表
- 另外:本文主要以chromedirver为例
开始环境搭建
安装python:双击自定义安装 或者 在cmd中输入python-3.7.0-amd64.exe的路径,即在电脑中存放的位置,回车即可弹出安装页面,勾选Add Python 3.7 to PATH,即自动配置环境变量。
如图:
下图显示安装成功:
- 注:可以查看系统环境变量,发现D:\My pyhton3.7.0\Scripts;D:\My pyhton3.7.0;已经自动添加到了path中,这就是勾选Add Python 3.7 to PATH的效果.
python安装完成后可以在cmd界面输入python,会出现下图内容,说明python安装成功
- 安装selenium:
- 在cmd中运行pip install selenium 即可在线安装selenium,(ps:安装指定的版本可用pip install selenium==3.14.0)如图提示selenium安装成功。
- 使用pip show selenium 查看selenium版本信息
- 安装chrome浏览器
该处使用谷歌浏览器64位的版本号为70.0.3538.67 - 将chromedriver.exe放到python的安装目录下(或者目录下的scripts下)
以上5步就搭建好python+selenium环境了
浏览器和驱动下载
在我学习Ui自动化时,总会遇到浏览器驱动版本问题,小伙伴也是一头雾水也找不到下载的地方,今天给大家整理
chromedriver版本 | 支持的chrome版本 |
v2.46 | v72-74 |
v2.45 | v70-72 |
v2.44 | v69-71 |
v2.43 | v69-71 |
v2.42 | v68-70 |
v2.41 | v67-69 |
v2.40 | v66-68 |
v2.39 | v66-68 |
v2.38 | v65-67 |
v2.37 | v64-66 |
v2.36 | v63-65 |
v2.35 | v62-64 |
v2.34 | v61-63 |
v2.33 | v60-62 |
v2.32 | v59-61 |
v2.31 | v58-60 |
v2.30 | v58-60 |
==============================================================================
谷歌浏览器驱动版本对应以及下载:
点击下载chrome的webdriver:https://chromedriver.chromium.org/downloads
点击下载chrome的历史版本:https://www.chromedownloads.net/
点击进入谷歌官方版本对应页面:https://sites.google.com/a/chromium.org/chromedriver/downloads
edge浏览器驱动版本对应以及下载:
点击进入微软edge浏览器wendriver版本对应下载页面:https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/#downloads
ie浏览器驱动官方地址:
点击进入ie浏览器driver下载:http://selenium-release.storage.googleapis.com/index.html
点击进入ie浏览器官方github:https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver
safari浏览器官方地址:
点击进入safari浏览器官方地址:https://developer.apple.com/safari/download/
浏览器基本操作
前言
开始自动化测试之前,需了解浏览器的一些基本操作,以方便后续的自动测试。码上开始吧!
导入Selenium模块
from selenium import webdriver
浏览器基本操作
- 打开网站
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
url = "http://localhost:8080/Shopping/index.jsp"
# 也可以用其它浏览器:比如Firefox()等等
brower = webdriver.Chrome()
# 打开浏览器
brower.get(url)
- 设置休眠
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
import time
url = "http://localhost:8080/Shopping/index.jsp"
webdriver.Firefox
brower = webdriver.Chrome()
brower.get(url)
# 强制等待3秒
time.sleep(3)
- 页面刷新
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
url = "http://localhost:8080/Shopping/index.jsp"
webdriver.Firefox
brower = webdriver.Chrome()
brower.get(url)
# 刷新页面
brower.refresh()
- 前进和后退
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
import time
url = "http://localhost:8080/Shopping/index.jsp"
webdriver.Firefox
brower = webdriver.Chrome()
brower.get(url)
# 实际效果自己操作,当前就不做演式了
# 后退
brower.back()
# 前
brower.forward()
- 设置窗口大小
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
import time
url = "http://localhost:8080/Shopping/index.jsp"
webdriver.Firefox
brower = webdriver.Chrome()
brower.get(url)
# 设置窗口大小
brower.set_window_size(1280, 720)
# 设置全屏
# brower.maximize_window()
- 截屏
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
import time
url = "http://localhost:8080/Shopping/index.jsp"
webdriver.Firefox
brower = webdriver.Chrome()
brower.get(url)
brower.get_screenshot_as_file("./test.png")
# 退出浏览器进程
brower.quit()
- 退出
#! /usr/bin/python3
# @Author : 一凡
from selenium import webdriver
import time
url = "http://localhost:8080/Shopping/index.jsp"
webdriver.Firefox
brower = webdriver.Chrome()
brower.get(url)
time.sleep(3)
# 退出浏览器进程
brower.quit()
为什么要学习定位
- 让程序操作指定元素,就必须先找到此元素;
- 程序不像人类用眼睛直接定位到元素;
- webDriver提供了八种定位元素的方式。
- 定位总结
- id、name、class_name、tag_name:根据元素的标签或元素的属性来进行定位
- link_text、partial_link_text:根据超链接的文本来进行定位(a标签)
- xpath:为元素路径定位–重点
- css:为css选择器定位(样式定位)
常见定位方式
id
- 说明:HTML规定id属性在整个HTML文档中必须是唯一的,id定位就是通过元素的id属性来定位元素;
- 前提:元素有id属性
- id定位方法:find_element_by_id()
- 实现案例-1需求:打开百度界面(https://www.baidu.com/),通过id定位,输入信息,点击百度的钮
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
url = "https://www.baidu.com/"
driver = webdriver.Chrome()
driver.get(url)
driver.find_element_by_id("kw").send_keys("好好学习|天天向上")
driver.find_element_by_id("su").click()
time.sleep(3)
driver.quit()
name
- 说明:HTML规定name属性来指定元素名称,name定位就是根据name属性来定位
- 前提:元素有name属性
- name定位方法:find_element_by_name()
- 实现案例-2需求:打开百度(https://www.baidu.com/),通过name定位
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
url = "https://www.baidu.com/"
driver = webdriver.Chrome()
driver.get(url)
driver.find_element_by_name("wd").send_keys("好好学习|天天向上")
driver.find_element_by_id("su").click()
time.sleep(3)
driver.quit()
class_name
- 说明:HTML规定class来指定元素的类名,class定位就是根据class属性来定位,用法和name,id类似。
- 前提:元素有class属性
- class_name定位方法:find_element_by_class_name()
- 实现案例-3需求:打开百度界面(https://www.baidu.com/),通过class定位
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
url = "https://www.baidu.com/"
driver = webdriver.Chrome()
driver.get(url)
driver.find_element_by_class_name("s_ipt").send_keys("好好学习|天天向上")
driver.find_element_by_id("su").click()
time.sleep(3)
driver.quit()
tag_name
- tag_name是通过标签名称来定位的,如:a标签
- 注:由于HTML源码中,经常会出现很多相同的的标签名,所以一般不使用该定位方式
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
# 这里使用优设导航的百度搜索界面
# 获取浏览器对象
driver = webdriver.Chrome()
# 获取网络链接
url = "https://hao.uisdc.com/"
driver.get(url)
time.sleep(3)
# 获取搜索输入框,输入:优设导航的百度搜索
driver.find_element_by_tag_name("input").send_keys("优设导航的百度搜索")
# 暂停3秒
time.sleep(3)
# 退出浏览器驱动
driver.quit()
link_text
- 说明:link_text定位于前面4个定位有所不同,它专门用来定位超链接文本(文本值)
- 前提:定位的元素是链接标签(a标签)
- link_text定位方法:find_element_by_link_text()
- 实现案例-5需求:打开百度首页,通过link_text(链接文本)定位到【新闻】按钮,并进行点击操作
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
url = "https://www.baidu.com/"
driver = webdriver.Chrome()
driver.get(url)
driver.find_element_by_link_text("新闻").click()
time.sleep(3)
driver.quit()
partial_link_text
- 说明:partial_link_text定位是对link_text定位的补充,partial_link_text为模糊匹配;link_text为全部匹配。
- 前提:定位的元素是链接标签(a标签)
- partial_link_text定位方法:find_element_by_partial_link_text()
- 通过传入a标签局部文本或全部文本来定位元素,要求输入的文本能够唯一找到这个元素
- 实现案例-6需求:打开百度新闻(http://news.baidu.com/),通过partial_link_text定位任何一条新闻,并进行点击操作
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
url = "http://news.baidu.com/"
driver = webdriver.Chrome()
driver.get(url)
driver.find_element_by_partial_link_text("守护蓝色地球").click()
time.sleep(3)
# driver.quit()
元素组
- 元素组定位方式:find_elements_by_xxx
作用: - 查找返还定位所有符合条件的元素
- 返还的定位元素格式为列表格式
说明: - 列表数据格式的读取需要指定下标(下标从0开始)
- 案例要求:打开百度页面https://www.baidu.com/,通过元素组定位
- 定位:"//*[@id=‘s-top-left’]/a"
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium import webdriver
url = "https://www.baidu.com/"
driver = webdriver.Chrome()
driver.get(url)
path = "//*[@id='s-top-left']/a"
elements = driver.find_elements_by_xpath(path)
# 返回列表
print(len(elements))
# 通过列表方法获取相应的元素进行点击
elements[0].click()
xpath定位详解
通过常见属性定位
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get( "https://www.baidu.com" )
# xpath通过id定位
brower.find_element_by_xpath("//*[@id= 'kw']").click()
# xpath通过name定位
brower.find_element_by_xpath("//*[@name='wd']").click()
# xpath通过class_name定位
brower.find_element_by_xpath("//*[@class='s_ipt' ]").click()
通过其它属性
- 如果一个元素id、name、class属性都没有,这时候也可以通过其它属性定位到
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get( "https://www.baidu.com" )
#用xpath通过其它属性定位
brower.find_element_by_xpath("//*[@autocomplete='off']").send_keys("Python")
标签
- 有时候同一个属性,同名的比较多,这时候可以通过标签筛选下,定位更准一点
- 如果不想制定标签名称,可以用*号表示任意标签
- 如果想制定具体某个标签,就可以直接写标签名称
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get( "https://www.baidu.com" )
#用xpath通过其它属性定位
brower.find_element_by_xpath("//input[@id='kw']").send_keys( "Pthon")
层级定位
- 如果一个元素,它的属性不是很明显,无法直接定位到,这时候我们可以先找它老爸(图中数字1)
- 找到它老爸后,再找下个层级就能定位到了
- 如上图所示,要定位的是input这个标签,它的老爸的 class=“sec-input-box yuyin-cur”
- 要是它老爸的属性也不是很明显,就找它爷爷id=form(图中数字2)
- 于是就可以通过层级关系定位到
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get( "https://www.sogou.com" )
#通过老爸的span的class属性定位输入框
brower.find_element_by_xpath("//span[@class='sec-input-box']/input").send_keys("Python")
#通过爷爷的form的id属性来定位输入框
brower.find_element_by_xpath("//form[@id='sf']/span/input").send_keys("Python")
索引
- 百度主页–设置–搜索设置
- 如果一个元素它的兄弟元素跟它的标签一样,这时候无法通过层级定位到。因为都是一个父亲生的,多胞胎兄弟。
- 虽然双胞胎兄弟很难识别,但是出生是有先后的,于是可以通过它在家里的排行老几定位到。
- 如下图三胞胎兄弟
- 用xpath定位老大、老二和老三(这里索引是从1开始算起的,跟Python的索引不一样)
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get( "https://www.baidu.com" )
##定位设置
brower.find_element_by_link_text("设置").click()##定位搜索设置
# brower.find_element_by_xpath("html/body/div[1]/div[6]/a[1]").click()
#定义每页显示10条
brower.find_element_by_xpath("//select[@id='nr']/option[1]")
#定义每页显示20条
brower.find_element_by_xpath("//select[@id='nr']/option[2]")
# 定义每页显示50条
brower.find_element_by_xpath("//select[@id='nr']/option[3]")
逻辑
- xpath还有一个比较强的功能,是可以多个属性逻辑运算的,可以支持与(and)、或(or)、非(not)
- 一般用的比较多的是and运算,同时满足两个属性
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get( "https://www.baidu.com" )
# xpath逻辑运算
brower.find_element_by_xpath("//*[id='kw' and name='wd']")
定位总结
定位单个元素
1.id定位:find_element_by_id(self, id_)
2.name定位:find_element_by_name(self, name)
3.class定位:find_element_by_class_name(self, name)
4.tag定位:find_element_by_tag_name(self, name)
5.link定位:find_element_by_link_text(self, link_text)
6.partial_link定位find_element_by_partial_link_text(self, link_text)
下面两种要重点掌握其中一种,两者都会是更好的。现在我比较喜欢用xpath定位
7.xpath定位:find_element_by_xpath(self, xpath)
8.css定位:find_element_by_css_selector(self, css_selector)
定位一组元素
1.id复数定位find_elements_by_id(self, id_)
2.name复数定位find_elements_by_name(self, name)
3.class复数定位find_elements_by_class_name(self, name)
4.tag复数定位find_elements_by_tag_name(self, name)
5.link复数定位find_elements_by_link_text(self, text)
6.partial_link复数定位find_elements_by_partial_link_text(self, link_text)
7.xpath复数定位find_elements_by_xpath(self, xpath)
8.css复数定位find_elements_by_css_selector(self, css_selector)
如何定位多个元素呢?
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")#elements注意这个后面带有s,即复数的意思
elements = brower.find_elements_by_xpath("//*[@class='mnav']")#返回形式为列表形式
print(type(elements))
print(elements)
操作元素
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")#elements注意这个后面带有s,即复数的意思
elements = brower.find_elements_by_xpath("//*[@class='mnav']")#返回形式为列表形式
print(type(elements))
print(elements)
# 获取元素text信息(此步骤可省略)列表下标从o开始
print(elements[3].text)
#列表索引方法获取视频元素,并进行点击操作
elements[3].click()
brower.quit()
数据驱动
- 我个人的理解就是说,把自动化测试中需要用到的数据和代码分开。
- 当我们需要用到这个数据时,直接读取里边的数据就可以。
- 工作中常用到的文本格式有:txt、csv。假设我要搜索一组数据,进行一个简单的登录操作
txt文件
# -*- coding: utf-8 -*-
# @Author : 一凡
#打开文件
path = "test.txt"
file = open(path, 'r')#读取所有行的数据
r = file.readlines()
# split通过指定分隔符对字符串进行切片,此处分割符是逗号(,)
# #以列表形式返回,所以后面下标为别为:o和1
for i in r:
user = i.split(",")[0]
password = i.split(',')[1]
#然后行简单的登陆,传入参数并打印登录相关信息
def login(user, password):
if user == "laozhu":
print("管理员%s登录成功"%user)
print('----------')
else:
print("会员%s登录成功" % user)
login(user, password)
excel
- 小伙伴都知道,测试用例是写在Excel里的,如果是少量的用例很容易处理,如果用例成百上千条呢?
- 自动化测试的话,需要对用例数据进行读取,那必须循环读取才可以实现自动化。那么问题来了,怎么做呢?
问题解析:
1、用列表存放这些用例数据,所以要用到列表
2、每一行用例要存放在字典内,所以需要用到字典
3、循环写入到字典,然后存放到列表内
#!/usr/bin/python3
import xlrd
class excel_data:
"""读取excl表接口数据"""
# 替换自己的文件目录地址
data_path = "E:\\api_interface\\data\\interface.xlsx"
# 打开文件
excel = xlrd.open_workbook(data_path)
# 通过下标定位表格
sheet = excel.sheet_by_index(0)
# 行: 6 和列数: 5
rows, cols = sheet.nrows, sheet.ncols
def read_excl(self):
# 获取第一行数据key
first_row = self.sheet.row_values(0)
# print(first_row) # [编号,接口方式,host, params, result]
# 定义空列表,用于存放用例数据
self.result = []
# 从第一行用例开始循环(1, 6)循环5次
for i in range(1, self.rows):
# 定义空字典
info_dict = {}
# 每1次大循环要循环5次(字典里有5组数据)
for j in range(0, self.cols):
# j=0,1,2,3,4
# 添加到字典 (1)[0]---第2行第1例的值,依次循环
info_dict[first_row[j]] = self.sheet.row_values(i)[j]
# 将数据存放在列表中
self.result.append(info_dict)
print(self.result)
if __name__ == "__main__":
ex = excel_data()
ex.read_excl()
运行结果
yaml
- yaml文件后缀名为yaml,如文名件.yaml
- yaml为第3方模块,需另行安装pip install pyyaml
问题解析:
1.定义文件地址
2.打开yaml文件
3.读取文件后转成字典以方便读取
#!/usr/bin/python3
import yaml
import os
# 获取当前脚本所在文件夹路径
curPath = os.path.dirname(os.path.realpath(__file__))
# 获取yaml文件路径
yamlPath = os.path.join(curPath, "E:\\api_interface\\config\\yaml.yaml")
# open方法打开直接读出来
open_file = open(yamlPath, 'r', encoding='utf-8')
result = open_file.read()
file_dict = yaml.load(result, Loader=yaml.FullLoader) # 用load方法转字典
print(file_dict)
运行结果
键盘操作
什么是键盘事件
- 在web产品测试中我们经常还会用到其它键盘操作,如删除,空格,回车,Ctrl+C等
- 这些操作都包含在Keys类中,所以要模拟键盘操作是首先要导入keys包。
- 所有的键盘操作都是在最后的 send_keys()里面通过改变参数实现的。
# 在使用键盘按键方法前需要先导入 keys 类包:
from selenium.webdriver.common.keys import Keys
键盘常见操作
案例操作
# -*- coding:utf-8 -*-
# @Author : 一凡
# 鼠标操作
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
# 导入键盘模块
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get("https://www.baidu.com/") # 打开百度
driver.maximize_window()
driver.find_element_by_id("kw").send_keys("Python")
time.sleep(3)
# 全选
driver.find_element_by_id("kw").send_keys(Keys.CONTROL, "a")
time.sleep(3)
# 剪切
driver.find_element_by_id("kw").send_keys(Keys.CONTROL, "x")
driver.get("http://www.sogou.com")
time.sleep(3)
# 黏贴
driver.find_element_by_id("query").send_keys(Keys.CONTROL, "v")
鼠标操作
- 前面我们已经学习到可以用 click()来模拟鼠标的单击操作
- 在实际的 web 产品测试中发现,有关鼠标的操作,不仅只有单击,有时候还要用到右击,双击,拖动等操作
- 这些操作都包含在ActionChains 类中,所以要模拟鼠标操作是首先要导入ActionChains
# 在使用鼠标操作前需要先导入ActionChains类包:
from selenium.webdriver.common.action_chains import ActionChains
鼠标常见操作
左击操作
- 对ActionChains类进行实例化对象操作
- 调用click(元素)方法
- 执行操作perform方法
- 案例1: 对百度的搜索输入hello,左击百度一下按钮
# -*- coding:utf-8 -*-
# @Author : 一凡
# 鼠标操作
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
# 导入键盘模块
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get("https://www.baidu.com/") # 打开百度
driver.maximize_window()
# 定位新闻
element = driver.find_element_by_link_text("新闻")
time.sleep(5)
# 左击操作
ActionChains(driver).click(element).perform()
# ac = ActionChains(driver)
# ac.click(element).perform()
右击操作
操作思路:
- 对ActionChains类进行实例化对象操作
- 调用context_click(元素)方法
- 执行操作perform()方法
- 案例2: 对百度的搜索框进行右击操作
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
element = driver.find_element_by_id("kw")
time.sleep(3)
# 操作元素
ActionChains(driver).context_click(element).perform()
双击操作
操作思路:
- 对ActionChains类进行实例化对象操作
- 调用double_click(元素)方法
- 执行操作perform()方法
- 案例3: 对百度的搜索框输入:好好学习,双击鼠标选中文本
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
element = driver.find_element_by_id("kw")
element.send_keys("好好学习")
time.sleep(3)
# 双击操作
ActionChains(driver).double_click(element).perform()
拖动操作
操作思路:
- 对ActionChains类进行实例化对象操作
- 调用drag_and_drop(元素1,元素2)方法
- 执行操作perform()方法
- http://127.0.0.1:5000/signin,点击练习鼠标拖拽,再将drag me元素分别拖动到item1,item2等元素位置
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("http://127.0.0.1:5000/signin")
driver.find_element_by_link_text("练习鼠标拖拽").click()
element1 = driver.find_element_by_id("dragger")
# 复数定位返回结果是什么类型:列表
element2 = driver.find_elements_by_class_name("item")[3]
time.sleep(3)
# 拖动操作
ActionChains(driver).drag_and_drop(element1, element2).perform()
悬停操作
操作思路:
- 对ActionChains类进行实例化对象操作
- 调用move_to_element(元素)方法
- 执行操作perform()方法
-案例5: 进入百度,悬停到设置元素上
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.maximize_window()
mouse = driver.find_element_by_id("s-usersetting-top")
# 悬停操作
ActionChains(driver).move_to_element(mouse).perform()
time.sleep(5)
driver.find_element_by_xpath('//*[@id="s-user-setting-menu"]/div/a[2]').click()
按下左键操作
操作思路:
- 对ActionChains类进行实例化对象操作
- 调用click_and_hold(元素)方法
- 执行操作perform方法
- 案例6: 进入百度,在贴吧元素上按着不松
# -*- coding:utf-8 -*-
# @Author : 一凡
from selenium.webdriver.common.action_chains import ActionChains
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
element = driver.find_element_by_link_text("贴吧")
# 按下左键操作
ActionChains(driver).click_and_hold(element).perform()
time.sleep(5)
ActionChains(driver).release(element).perform()
鼠标其它操作
获取元素属性
- 我们要设计功能测试用例时,一般会有预期结果,有些预期结果测试人员无法通过肉眼进行判断的。
- 自动化测试运行过程是无人值守,一般情况下,脚本运行成功,没有异样信息就标识用户执行成功。
- 那怎么才能知道我打开这个网页,是不是我想要打开的这个网页呢?
-通常我们可以通过获得页面的 title 、URL 地址,页面上的标识性信息(如,登录成功的“欢迎,xxx”信息)来判断用例执行成功。
获取title
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.qq.com/")
time.sleep(3)
# 打印标题
print(brower.title)
获取URL
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.qq.com/")
time.sleep(3)
# 打印URL
print(brower.current_url)
获取元素标签
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")
time.sleep(3)
# 获取标签
tag = brower.find_element_by_id("su").tag_name
print(tag)
获取输入框文本值
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")
time.sleep(3)
brower.find_element_by_id("kw").send_keys("python")
value = brower.find_element_by_id("kw").get_attribute("value")
print(value)
# 打印属性
python
获取元素其它属性
- 获取其它属性方法:get_attribute(“属性”),这里的参数可以是class、name,value等任意属性
- 如获取百度一下按钮的value属性
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")
time.sleep(3)
value = brower.find_element_by_id("kw").get_attribute("value")
print(value)
# 打印属性
百度一下
文本超链接text
如下图这种显示在页面上的文本信息,可以直接获取到
2.查看元素属性:
<a id="setf" target="_blank" onmousedown="return ns_c({'fm':'behs','tab':'favorites','pos':0})
" href="//www.baidu.com/cache/sethelp/help.html">把百度设为主页</a>
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")
time.sleep(3)
text = brower.find_element_by_id("setf").text
print(text)
# 运行结果
把百度设为主页
获取浏览器名字
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.baidu.com/")
time.sleep(3)
# 获取浏览器名称
print brower.name
判断是否存在
title(title_is)
- 获取页面title的方法可以直接用driver.title获取到,然后也可以把获取到的结果用做断言。
- 首先导入expected_conditions模块:from selenium.webdriver.support import expected_conditions
- 由于这个模块名称比较长,所以为了后续的调用方便,重新命名为EC了(有点像数据库里面多表查询时候重命名)
- 打开博客首页后判断title,返回结果是True或False
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
brower = webdriver.Chrome()
brower.get("https://www.qq.com")
title = EC.title_is(u"腾讯首页")
print(title(brower))
判断title包含:title(title_contains)
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
brower = webdriver.Chrome()
brower.get("https://www.qq.com")
print(EC.title_contains("腾讯")(brower))
判断元素是否存在
- selenium是没有方法判断元素是否存的,所以需要自己写.元素不存在的话,操作元素会报错,或者元素有多个,不唯一的时候也会报错
find_elements方法判断
- find_elements方法是查找页面上所有相同属性的方法,这个方法其实非常好用
- 由于元素定位的方法很多,所以判断的时候定位方法不统一也比较麻烦,我选择xpath定位
- 写一个函数判断,找到就返回Ture,没找到就返回False(或者不止一个)
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
brower = webdriver.Chrome()
brower.get("https://www.baidu.com")
def is_element_exist(x_p):
s = brower.find_elements_by_xpath(xpath=x_p)
if len(s) == 0:
print("元素未找到:%s"%x_p)
return False
elif len(s) == 1:
return True
else:
print("找到%s个元素:%s"%( len(s),x_p))
return False
#判断页面有无id为kw的元素
if is_element_exist("//*[@id='kw']"):
brower.find_element_by_id("kw") .send_keys( "Python")
print(is_element_exist("//*[@id='kw']"))
#判断页面有无class_name为mnav的元素
if is_element_exist("//*[@class='mnav']"):
brower.find_elements_by_class_name("mnav")
#判断页面有无id为xx的元素
if is_element_exist("XX"):
brower.find_element_by_id("xx").send_keys( "Python")
判断元素文本
如果要判断按钮上的文本,就不能用上面那个方法
导入模块:from selenium.webdriver.support import expected_conditions as EC
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
brower = webdriver.Chrome()
brower.get("https://www.baidu.com")
#locator参数是定位的方法
locator = ("id", "su")
# #text参数是期望的值
ext = u"百度一下"
#判读正确返回True,否之则返回False
result = EC.text_to_be_present_in_element_value(locator,text)(brower)
print(result)
多窗口(句柄)
有些页面的链接打开后,会重新打开一个窗口,对于这种情况,想在新页面上操作,就得先切换窗口了。获取窗口的唯一标识用句柄表示,所以只需要切换句柄,我们就能在多个页面上灵活自如的操作了。
- 什么是多窗口
- 获取当前窗口句柄
- 元素有属性,浏览器的窗口其实也有属性的,只是你看不到,浏览器窗口的属性用句柄(handle)来识别。
- 人为操作的话,可以通过眼睛看,识别不同的窗口点击切换。但是脚本没长眼睛,它不知道你要操作哪个窗口,这时候只能句柄来判断了。
- 获取当前页面的句柄:driver.current_window_handle
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.qq.com/")
time.sleep(3)
#获取当前窗口句柄
handle = brower.current_window_handle
print(handle)
# 运行结果
CDwindow-3706F8F466314BE560AC3766144F6D00
- 获取所有句柄
- 定位赶集网招聘求职按钮,并点击
- 点击后,获取当前所以的句柄:window_handles
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.qq.com/")
time.sleep( 3)
handle = brower.current_window_handle#
# 打印首页句柄
print(handle)
brower.find_element_by_partial_link_text("娱乐").click()
all_handles = brower.window_handles
#打印所有句柄
print(all_handles)
# 运行结果
CDwindow-DE1541A4FD1DCE8660391A4BC7D16FC6
['CDwindow-DE1541A4FD1DCE8660391A4BC7D16FC6', 'CDwindow-217802088FCAB1ABD977BA061AF0822F']
- 切换句柄
# -*- coding: utf-8 -*-
# @Author : 一凡
from selenium import webdriver
import time
brower = webdriver.Chrome()
brower.get("https://www.qq.com/")
time.sleep(3)
brower.find_element_by_partial_link_text("娱乐").click()
#获取所有的句柄
all_handles = brower.window_handles
#切换句柄
brower.switch_to.window(all_handles[1])
#切换之后打印页面标题
print("切换后句柄标题是:", brower.title)
# 运行结果
切换后句柄标题是: 娱乐-腾讯网
selenium三种等待方式详解
为什么要使用等待?
- 在自动化测试脚本的运行过程中,webdriver操作浏览器的时候,对于元素的定位是有一定的超时时间,大致在1-3秒
- 如果这个时间内仍然定位不到元素,就会抛出异常,中止脚本执行
- 我们可以通过在脚本中设置等待的方式来避免由于网络延迟或浏览器卡顿导致的偶然失败
常用的三种等待方式
- 强制等待
- 隐式等待
- 显示等待
强制等待
- 利用time模块的sleep方法来实现,最简单粗暴的等待方法
- 强制等待,不管你浏览器是否加载完成,都得给我等待3秒,3秒一到,继续执行下面的代码,
# -*- coding: utf-8 -*-
# @Author : 码上开始
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 打开百度首页
driver.get(r'https://www.baidu.com/')
# 强制等待3秒
time.sleep(3)
driver.find_element_by_css_selector("#kw").send_keys("selenium")
# 退出
driver.quit()
弊端
- 不建议用这种等待方法,严重影响代码的执行速度
隐式等待
- implicitly_wait()方法用来等待页面加载完成(直观的就是浏览器tab页上的小圈圈转完)网页加载完成则执行下一步
- **隐式等待只需要声明一次,**一般在打开浏览器后进行声明
- 声明之后对整个drvier的生命周期都有效,后面不用重复声明
# -*- coding: utf-8 -*-
# @Author : 码上开始
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 打开百度首页
driver.get(r'https://www.baidu.com/')
# 隐性等待5秒
driver.implicitly_wait(5)
driver.find_element_by_css_selector("#kw").send_keys("selenium")
# 退出
driver.quit()
弊端
- 程序会一直等待整个页面加载完成,直到超时
- 有时候我需要的那个元素早就加载完成了,只是页面上有个别其他元素加载特别慢,我仍要等待页面全部加载完成才能执行下一步
显示等待
- WebDriverWait,配合该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了
- 它主要的意思就是:程序每隔xx秒看一眼,如果条件成立了,则执行下一步
- 否则继续等待,直到超过设置的最长时间,然后抛出TimeoutException
- 显示等待必须在每个需要等待的元素前面进行声明
# 导入模块
from selenium.webdriver.support.wait import WebDriverWait
四个参数
- driver:浏览器驱动
- timeout:等待时间
- poll_frequency:检测的间隔时间,默认0.5s
- ignored_exceptions:超时后的异常信息,默认抛出NoSuchElementException
expected_conditions
expected_conditions是selenium的一个模块
包含一系列可用于判断的条件
可以对网页上元素是否存在,可点击等等进行判断,一般用于断言或与WebDriverWait配合使用
from selenium.webdriver.support import expected_conditions as EC
# -*- coding: utf-8 -*-
# @Author : 码上开始
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
# 等待10s,等待过程中如果定位到元素,就直接执行后续的代码,反之等待10s后报错误信息
# 验证元素是否出现,传入的参数都是元组类型的locator,如(By.ID, ‘kw’)
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "kw"))).send_keys("好好学习")
模块用法汇总
#判断当前页面的title是否精确等于预期,返回布尔值
WebDriverWait(driver,10).until(EC.title_is("百度一下,你就知道"))
#判断当前页面的title是否包含预期字符串,返回布尔值
WebDriverWait(driver,10).until(EC.title_contains('new'))
#判断当前页面的url是否精确等于预期,返回布尔值
WebDriverWait(driver,10).until(EC.url_contains('https://www.baidu.com'))
#判断当前页面的url是否包含预期字符串,返回布尔值
WebDriverWait(driver,10).until(EC.url_contains('baidu'))
#判断当前页面的url是否满足字符串正则表达式匹配,返回布尔值
WebDriverWait(driver,10).until(EC.url_matches('.+baidu.+'))
#判断元素是否出现,只要有一个元素出现,返回元素对象
WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID,'kw')))
#判断元素是否可见,返回元素对象
WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(By.ID,'kw')))
#判断元素是否包含指定文本,返回布尔值
WebDriverWait(driver,10).until(EC.text_to_be_present_in_element((By.NAME,'tj_trnews'),'新闻'))
#判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去
WebDriverWait(driver,10,).until(EC.frame_to_be_available_and_switch_to_it(By.xpath,'//iframe'))
#判断某个元素是否可见并且是可点击的,如果是的就返回这个元素,否则返回False
WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.NAME,'tj_trnews')))
#判断某个元素是否被选中,一般用在下拉列表
WebDriverWait(driver,10).until(EC.element_to_be_selected(driver.find_element(By.xpath,'//input[@type="checkbox"]')))
#判断页面上是否存在alert,如果有就切换到alert并返回alert的内容
WebDriverWait(driver,10).until(EC.alert_is_present())
无头模式
- 在使用seleinum的时候经常会打开浏览器,如果电脑配置不在了的时候,会出现卡,有没有一种方式不弹出浏览器又把想要的操作执行完呢??
- 这时候firefox和chrome就有了无头模式,也就是没有界面的浏览器,在内存中执行。
Firefox
from selenium import webdriver
options = webdriver.FirefoxOptions()
options.add_argument('-headless')
browser = webdriver.Firefox(firefox_options=options)
browser.get('http://httpbin.org/get')
Chrome
Chrome_options = webdriver.ChromeOptions()
Chrome_options.add_argument('-headless')
drive = webdriver.Chrome(chrome_options=Chrome_options)
drive.get('http://httpbin.org/get')
logging模块
- 你可以控制消息的级别,过滤掉那些并不重要的消息。
- 你可决定输出到什么地方,以及怎么输出。有许多的重要性别级可供选择,debug、info、warning、error 以及 critical
- 通过赋予 logger 或者 handler 不同的级别,你就可以只输出错误消息到特定的记录文件中,或者在调试时只记录调试信息。
日志级别
- 级别从高到低
级别 | 使用范围 | 值 |
CRITICAL | 严重错误,程序本身可能无法继续运行 | 50 |
ERROR | 现更严重的问题,软件无法执行某些功能 | 40 |
WARNING | 意想不到的事情发生了,或预示着某个问题。但软件仍按预期运行。 | 30 |
INFO | 确认代码运行正常 | 20 |
DEBUG | 详细信息,用于诊断问题 | 10 |
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 设置日志输出的等级,这样的话就能输出debug及以上的日志
# logging.basicConfig(level="DEBUG")
logging.debug("info信息")
logging.info("info信息")
logging.warning("warning信息")
logging.error("error")
logging.critical("critical")
运行结果
- 默认打印WARNING及以上的日志信息
- level=“DEBUG” 设置级别名必须都是大写
- 如果我想打印日志的时间、在哪一行等格式?可以实现吗??
warning信息
error
critical
实现日志格式
- 使用basicConfig中的参数format
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 定制日志打印格式
fmt = "%(name)s-%(levelname)s %(asctime)s %(message)s"
logging.basicConfig(level="DEBUG", format=fmt)
logging.debug("debug信息")
logging.info("info信息")
logging.warning("warning信息")
logging.error("error")
logging.critical("critical")
运行结果
- 默认名字显示的root(可否显示一个自定义名字?继续往下看)
root-DEBUG 2021-01-23 17:14:57,553 debug信息
root-INFO 2021-01-23 17:14:57,553 info信息
root-WARNING 2021-01-23 17:14:57,553 warning信息
root-ERROR 2021-01-23 17:14:57,553 error
root-CRITICAL 2021-01-23 17:14:57,553 critical
日志流程
第1步:创建日志器
- 提供程序使用的接口,可以理解就创建一个logging实例化类
- 默认日志级别为:WARNING
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 创建日志器对像
log = logging.getLogger("码上开始")
第2步:创建处理器
- 由处理器来处理日成生的位置(控制台或文件或两者同时存在)
- 重点:日志器添加处理器
控制台处理器
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 定义日志器
log = logging.getLogger()
# 可设置等级,默认级别为WARNING
# 控制台处理器
console_handle = logging.StreamHandler()
# 处理器级别
# console_handle.setLevel(level="INFO")
# 日志器添加处理器
log.addHandler(console_handle)
log.debug("debug信息")
log.info("info信息")
log.warning("warning信息")
log.error("error")
log.critical("critical")
运行结果
- 如果日志器级别 >处理器级别则显示日志器级别,反之显示处理器级别
- 默认级别为WARING,所以只打印这三条日志
log.debug("debug信息")
log.info("info信息")
log.warning("warning信息")
文件处理器
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 定义日志器
log = logging.getLogger()
# 文件处理器
file_handle = logging.FileHandler("./log", mode="a", encoding="utf-8")
# 日志器添加处理器
log.addHandler(file_handle)
log.debug("debug信息")
log.info("info信息")
log.warning("warning信息")
log.error("error")
log.critical("critical")
第3步:格式器
- 决定日志生成的最终输出格式
- 重点:处理器添加格式器
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 定义日志器
log = logging.getLogger("码上开始")
# 定义处理器
console_handle = logging.StreamHandler()
# 定义日志打印格式
fmt = "%(name)s--->%(levelname)s--->%(asctime)s--->%(message)s"
# 创建格式器
get_fmt= logging.Formatter(fmt=fmt)
# 处理器添加格式器
console_handle.setFormatter(get_fmt)
# 日志器添加处理器
log.addHandler(console_handle)
log.debug("debug信息")
log.info("info信息")
log.warning("warning信息")
log.error("error")
log.critical("critica
运行结果:
码上开始--->WARNING--->2021-01-23 19:23:57,898--->warning信息
码上开始--->ERROR--->2021-01-23 19:23:57,899--->error
码上开始--->CRITICAL--->2021-01-23 19:23:57,899--->critical
日志同时生成在控制台和文本
- 重点就是创建1个控制台/文本处理器
- 小伙伴可能会发现,我们的代码没有进行封装,那最后一步,我们封装日志模块
# -*- coding: utf-8 -*-
# @Author : 码上开始
import logging
# 定义日志器
log = logging.getLogger("码上开始")
# 定义控制台和文本处理器
console_handle = logging.StreamHandler()
file_handle = logging.FileHandler("./log.txt", mode="a", encoding="utf-8")
# 定义日志打印格式
fmt = "%(name)s--->%(levelname)s--->%(asctime)s--->%(message)s"
# 创建格式器
get_fmt= logging.Formatter(fmt=fmt)
# 处理器添加格式器
console_handle.setFormatter(get_fmt)
file_handle.setFormatter(get_fmt)
# 日志器添加处理器
log.addHandler(console_handle)
log.addHandler(file_handle)
log.debug("debug信息")
log.info("info信息")
log.warning("warning信息")
log.error("error")
log.critical("critical")
封装日志模块
- 第1步:创建日志器
def __init__(self):
# 日志器
self.log = logging.getLogger("码上开始")
- 第2步:创建处理器
def ConsoleHadle(self, level="WARNING"):
"""控制台处理器"""
# 创建控制台处理器
self.console_handler = logging.StreamHandler()
# 设置处理器等级
self.console_handler.setLevel(level)
# 处理器添加格式器
self.console_handler.setFormatter(self.getFormater()[0])
# 返回控制台处理器
return self.console_handler
def FileHandle(self, level="DEBUG"):
"""文件处理器"""
# 创建文件处理器
self.file_handler = logging.FileHandler("./log.txt", mode="a", encoding="utf-8")
# 设置处理器等级
self.file_handler.setLevel(level)
# 处理器添加格式器
self.file_handler.setFormatter(self.getFormater()[1])
# 返回文件处理器
return self.file_handler
- 第3步:创建格式器
def getFormater(self):
"""格式器"""
# 定义输出格式
self.console_fmt = logging.Formatter(fmt="%(name)s--->%(levelname)s--->%(asctime)s--->%(message)s")
self.file_fmt = logging.Formatter(fmt="%(levelname)s--->%(asctime)s--->%(message)s")
# 返回格式器,第2个步骤中调用添加格式器
return self.console_fmt, self.file_fmt
- 第4步:日志器添加格式器
def get_log(self):
# 日志添加控制台处理器
self.log.addHandler(self.ConsoleHadle())
# 日志添加文件处理器
self.log.addHandler(self.FileHandle())
return self.log
完整代码
# -*- coding: utf-8 -*-
# @Author: 一凡
import logging
class Log():
def __init__(self, level="DEBUG"):
# 日志器
self.log = logging.getLogger("test")
self.log.setLevel(level)
def console_handle(self, level="DEBUG"):
"""控制台处理器"""
self.console_handler = logging.StreamHandler()
self.console_handler.setLevel(level)
# 处理器添加格式器
self.console_handler.setFormatter(self.get_formatter()[0])
return self.console_handler
def file_handle(self, level="DEBUG"):
"""文件处理器"""
self.file_handler = logging.FileHandler("./log.txt", mode="a", encoding="utf-8")
self.file_handler.setLevel(level)
# 处理器添加格式器
self.file_handler.setFormatter(self.get_formatter()[1])
return self.file_handler
def get_formatter(self):
"""格式器"""
# 定义输出格式
self.console_fmt = logging.Formatter(fmt="%(name)s--->%(levelname)s--->%(asctime)s--->%(message)s")
self.file_fmt = logging.Formatter(fmt="%(levelname)s--->%(asctime)s--->%(message)s")
return self.console_fmt, self.file_fmt
def get_log(self):
# 日志器添加控制台处理器
self.log.addHandler(self.console_handle())
# 日志器添加文件处理器
self.log.addHandler(self.file_handle())
return self.log
#实列化类
log = Log()
a = log.get_log()
a.debug("开始打印")
POM模型
Page Object Model (POM) 直译为“页面对象模型”,这种设计模式旨在为每个待测试的页面创建一个页面对象(class),将那些繁琐的定位操作封装到这个页面对象中,只对外提供必要的操作接口,是一种封装思想。
POM优势有哪些
- 让UI自动化更早介入项目中,可项目开发完再进行元素定位的适配与调试
- POM 将页面元素定位和业务操作流程分开,分离了测试对象和测试脚本
- 如果UI页面元素更改,测试脚本不需要更改,只需要更改页面对象中的某些代码就可以
- POM能让我们的测试代码变得可读性更好,高可维护性,高复用性
- 可多人共同维护开发脚本,利于团队协作
为什么使用POM设计模式
- 少数的自动化测试用例维护起来看起来是很容易的。但随着时间的迁移,测试套件将持续的增长。脚本也将变得越来越臃肿庞大。
- 如果变成我们需要维护10个页面,100个页面,甚至1000个呢?而且页面元素很多是公用的。那页面元素的任何改变都会让我们的脚本维护变得繁琐复杂,而且变得耗时易出错。
思路解析
- 需要一个文件用于管理页面元素,如login_page.py
- 封装一个公用的操作方法
- 最后需要一个文件用于编写测试用例
login_page.py文件
- 该文件用于管理登录页面所有的元素,操作这些元素的方法
#! /usr/bin/python3
#-*- coding:utf-8 -*-
'''管理登录页面所有的元素,操作这些元素的方法'''
from selenium.webdriver.common.by import By
class LoginPage:
username_input = (By.XPATH,'//*[@id="name"]') #登录页面的用户名输入框
password_input = (By.XPATH,'//*[@id="password"]') #登录页面的密码输入框
login_button = (By.XPATH,'//*[@id="submit"]') #登录按钮
common.py
- 该文件有用于封装一些共用的操作方法
#! /usr/bin/python3
#-*- coding:utf-8 -*-
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
'''封装一些常用公共方法'''
class InitBrowser():
'''浏览器常用操作封装'''
def __init__(self):
self.driver = webdriver.Firefox() # 启动谷歌浏览器
self.driver.get('https://sso.kuaidi100.com/sso/authorize.do') # 打开网站
def wait_element_visible(self, locate):
ele = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(locate)) #等待元素出现再去操作
print('等待元素出现')
return ele
def click_until_visible(self, locate):
self.wait_element_visible(locate).click()
def send_keys_until_visible(self, locate, value):
self.wait_element_visible(locate).send_keys(value)
TestCase.py
- 该文件用于管理测试用例
#! /usr/bin/python3
#-*- coding:utf-8 -*-
'''管理测试用例'''
import unittest
from common import InitBrowser
# 导入Pages文件下的login_page文件中的LoginPage类
from Pages.login_page import LoginPage
class TestCases(unittest.TestCase, InitBrowser, LoginPage):
def setUp(self) -> None:
'''前置操作初始化:打开浏览器,连接数据库,初始化数据'''
InitBrowser.__init__(self)
def testcase01(self):
'''测试用例'''
self.send_keys_until_visible(LoginPage.username_input, "账号")
self.send_keys_until_visible(LoginPage.password_input, "密码")
self.click_until_visible(LoginPage.login_button)
def tearDown(self) -> None:
'''后置操作:关闭浏览器,关闭数据库连接,清理测试数据'''
self.driver.quit()
if __name__=='__main__':
unittest.main()
unittest介绍
- unittest是Python自带的测试框架,可用于单元测试
- unittest测试框架可组织执行测试用例
- unittest提供丰富的断言方法
unittest语法
- 用import unittest导入unittest模块
- 继承unittest.TestCase类,如class xxx(unittest.TestCase)
- 每个测试用例执行前先执行setUp或setUpClass方法,执行完毕后执行tearDown或tearDownClass方法
- 用例必须名以test开头,否则unittest不能识别
- 调用unittest.main(),执行测试用例
unittest四大组件
test case
测试用例,方法命名基于test开头,测试用例自行排序执行顺序规则A-Z, a-z, 0-9
如用例test01/test02
add的用例不是用test开始就不会被识别,所以不会运行
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class TestHC(unittest.TestCase):
def test01(self):
print("用例1")
def test02(self):
print("用例2")
def add(self):
"""当前用例不会被执行"""
print("用例3")
if __name__ == '__main__':
unittest.main()
# 运行结果
..
OK
用例1
用例2
- 成功是
.
失败是F
,出错是E
test fixture
- 设置前置条件(setUp),后置条件(tearDown),每个测试用例方法执行前后都要执行前后置条件
- 用例前后都执行了setUp和tearDown,且运行了两次了?能否只打开一次浏览器和关闭浏览器一次
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class TestHC(unittest.TestCase):
def setUp(self) -> None:
print("打开浏览器")
def test01(self):
print("用例1")
def test02(self):
print("用例2")
def tearDown(self) -> None:
print("关闭浏览器")
if __name__ == '__main__':
unittest.main()
# 运行结果
打开浏览器
用例1
关闭浏览器
打开浏览器
用例2
关闭浏览器
- 设置前置条件(setUpClass),后置条件(tearDownClass),所有用例只会执行一次setUpClass和tearDownClass
- 方法前必须加装饰器: @classmethod
- 打开浏览器和关闭浏览器只运行了一次
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class TestHC(unittest.TestCase):
def setUpClass(cls) -> None:
print("打开浏览器")
def test01(self):
print("用例1")
def test02(self):
print("用例2")
def tearDownClass(cls) -> None:
print("关闭浏览器")
if __name__ == '__main__':
unittest.main()
# 运行结果
打开浏览器
用例1
用例2
关闭浏览器
test suite
- 先通过unittest.TestSuite() 创建测试套件实例对象,如:suite = unittest.TestSuite()
- 再通过addTest() 方法添加单个测试用例,或通过addTests([…]) 添加多个测试用例(列表中为用例方法名)
- 执行测试套件里的测试用例
方式一:加载测试用例
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class TestHC(unittest.TestCase):
def test01(self):
print("用例1")
def test02(self):
print("用例2")
if __name__ == '__main__':
"""方式1添加单条用例"""
# 创建suite实例
suite = unittest.TestSuite()
# 添加单条测试用例
suite.addTest(TestHC("test02")) # addTest()里参数格式为:测试类('测试方法')
suite.addTest(TestHC("test01"))
run = unittest.TextTestRunner()
run.run(suite)
"""方式2添加多条用例"""
# 创建suite实例
suite = unittest.TestSuite()
"""方式2添加多条用例"""
# addTests 复数
suite.addTests([TestHC('test02'), TestHC('test01')])
run = unittest.TextTestRunner()
run.run(suite)
方式二:加载测试用例类
- 先通过unittest.TestSuite() 创建测试套件实例对象。
- 再通过unittest.TestLoader()创建加载对象,加载测试用例类
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class Test(unittest.TestCase):
def test01(self):
print("用例1")
def test02(self):
print("用例2")
class Test1(unittest.TestCase):
def test03(self):
print("用例3")
def test04(self):
print("用例4")
if __name__ == '__main__':
"""添加单条用例"""
# 创建suite实例
suite = unittest.TestSuite()
loader = unittest.TestLoader()
# 加载用例
suite.addTest(loader.loadTestsFromTestCase(Test))
suite.addTest(loader.loadTestsFromTestCase(Test1))
run = unittest.TextTestRunner()
run.run(suite)
"""
# 加载用例
suite1 = loader.loadTestsFromTestCase(Test)
suite2 = loader.loadTestsFromTestCase(Test1)
# 用例添加到套件中
suites = unittest.TestSuite([suite1, suite2])
# 运行用例
run = unittest.TextTestRunner()
run.run(suites)
"""
方法三:加载指定路径里的测试用例
- 通过unittest.defaultTestLoader.discover()将指定路径的测试用例加载至测试用例集
- 注意:这里不需要创建unittest.TestSuite对象
- test_dir为指定路径, pattern=test_*.py 表示加载以test_开头的模块中的测试用例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wI6KlwyE-1634193811891)(C:\Users\zhichao\AppData\Roaming\Typora\typora-user-images\image-20210228152457445.png)]
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
# discover 可以一次调用多个脚本
# test_dir 用例文件的路径
# pattern 脚本名称匹配规则
test_dir = "D:\\HC\\testcase"
discover = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")
# 匹配test_case目录下所有以test开头的py文件,执行这些py文件下的所有测试用例
if __name__ == "__main__":
runner = unittest.TextTestRunner()
runner.run(discover)
test runner
- unittest框架执行测试用例之前,需先创建TextTestRunner实例,
- 再调用该实例的run()方法执行用例
# 创建TextTestRunner实例
runner = unittest.TextTestRunner()
# 使用run()方法运行测试套件(即运行测试套件中的所有用例)
runner.run(suite)
unittest生成测试报告
- unittest框架执行测试用例完成后会在控制台输出如上的结果
- 实际测试过程中,我们需要输出测试报告,这个时候我们需要使用第三方模块
下载HTMLTestRunner
- 下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html
文件修改
94行引入的名称要改,从 import StringIO修改成 import io
539行 self.outputBuffer = StringIO.StringIO()修改成self.outputBuffer=io.StringIO()
631行 print >>sys.stderr, ‘\nTime Elapsed: %s’ % (self.stopTime-self.startTime)修改成print (sys.stderr, - ‘\nTime Elapsed: %s’ %(self.stopTime-self.startTime))
642行,if not rmap.has_key(cls): 修改成 if not cls in rmap:
766行的uo = o.decode(‘latin-1’),修改成 uo=o
772行,把 ue = e.decode(‘latin-1’) 直接改成 ue = e
存放路径
- 将修改完成的模块存放在Python路径下Lib目录里即可
- D:\Program Files\python37\Lib
实例
# -*- coding: utf-8 -*-
# @Author : 一凡
import time
import unittest
import HTMLTestRunner
# 获取当前时间并指定时间格式,用于测试报告命名
now = time.strftime("%Y-%m-%d_%H_%M_%S")
# 测试报告存储路径
report_dir = 'D:\\HC\\report\\'
# 创建报告文件,并以写的形式打开文件,用于写入报告内容
fp = open(report_dir + now + "result.html", 'wb')
# 初始化一个HTMLTestRunner实例对象,用来生成报告
runner = HTMLTestRunner.HTMLTestRunner(stream=fp,
title="web自动化测试报告",
description="测试用例情况")
# 定义测试用例路径
test_dir='./testcase'
# 加载测试用例
dis = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
# 执行测试用例
runner.run(dis)
fp.close()
unittest断言
- 断言即测试用例执行结果和预期做对比,符合就即测试用例通过,反之不通过
- 只能在继承 unittest.TestCase 的类中使用这些方法
- 常见的几个断言方法(更多断言方法请自行查询)
序号 | 方法 | 用途 |
1 | assertEqual(a, b) | 核实 a == b |
2 | assertNotEqual(a, b) | 核实 a != b |
3 | assertTrue(x) | 核实 x 为 True |
4 | assertFalse(x) | 核实 x 为 False |
5 | assertIn( item , list ) | 核实 item 在 list 中 |
6 | assertNotIn( item , list ) | 核实 item 不在 list 中 |
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class Test(unittest.TestCase):
def test01(self):
"""核实 a == b"""
a = 1
self.assertEqual(a, 1)
def test02(self):
"""核实 a != b"""
a = 1
self.assertNotEqual(a, 2)
def test03(self):
"""核实 x 为 True"""
self.assertTrue(True)
def test04(self):
"""核实 x 为 False"""
self.assertTrue(type(0))
def test05(self):
"""核实item在list 中"""
self.assertIn(1, [1, 2, 3])
def test06(self):
"""核实item不在list 中"""
self.assertNotIn(0, [1, 2, 3])
if __name__ == '__main__':
unittest.main()
跳过用例的执行(skip)
- unittest提供了一些跳过指定用例的方法
- @unittest.skip(reason):强制跳转, reason是跳转原因
- @unittest.skipIf(condition, reason):condition为True的时候跳转
- @unittest.skipUnless(condition, reason):condition为False的时候跳转
- @unittest.expectedFailure:如果test失败了,这个test不计入失败的case数目
# -*- coding: utf-8 -*-
# @Author : 一凡
import unittest
class Test(unittest.TestCase):
.skip(reason="无条件跳过此用例")
def test_1(self):
print("测试1")
.skipIf(True, reason="为True的时候跳过")
def test_2(self):
print("测试2")
.skipUnless(False, reason="为False的时候跳过")
def test_3(self):
print("测试3")
.expectedFailure
def test_4(self):
print("测试4")
self.assertEqual(2, 4, msg="判断相等")
if __name__ == "__main__":
unittest.main()
总结
- 当我们再次使用登录时,只需要修改login_page.py里的定位元素方法和值就可以了
- 以上代码当然还有很多不足的地方,比如账号密码没有提出来,小伙伴可自行尝试
福利
如果想学习软件测试,就快加入:893694563,群内学软件测试,分享技术和学习资料,陪你一起成长和学习。
码字不易,小伙伴如果看到最后,烦请来个三连,谢谢拉~