序号 | 章节 | 知识点 |
---|---|---|
1 | 第一章 Web自动化入门 | 1.认识自动化及自动化测试 2.自动化测试工具选择 3.环境搭建 |
2 | 第二章 Selenium-API | 1.元素定位方式 2.元素和浏览器的操作方法 3.鼠标和键盘操作 4.元素等待 5.HTML特殊元素处理 6.窗口截图 7.验证码处理 |
3 | 第三章 Pytest框架 | 1.Pytest基本使用 |
4 | 第四章 PO模式 | 1.方法封装 2.PO模式介绍 3.PO模式实战 |
5 | 第五章 数据驱动 | 1.JSON读写 2.数据驱动介绍 3.数据驱动实战 |
6 | 第六章 日志收集 | 1.日志相关概念 2.日志的基本方法 3.日志的高级方法 |
由机器设备代替人工自动完成指定目标的过程
减少人工劳动力
提高工作效率
产品规格统一标准
规模化(批量生产)
安全
用程序代替人工去执行测试的过程
软件测试: 对程序进行操作,以发现程序错误,并验证其是否满足需求的过程
解决-回归测试
回归测试:项目在发新版本之后对项目之前的功能进行验证
解决-压力测试
压力测试:可以理解多用户同时去操作软件,统计软件服务器处理多用户请求的能力
解决-兼容性测试
兼容性测试:不同浏览器(IE、Firefox、Chrome)等等,执行的用例都是同一条,只是浏览器不同
解决-冒烟测试
冒烟测试:针对最基本的功能或者流程进行的测试
提高测试效率,保证产品质量
优点
误区
概念
用程序代替人工去执行Web测试的过程
什么是接口自动化测试?
什么是移动自动化测试?
什么样的项目适合做Web自动化测试
Web自动化测试什么时候开始
手工测试结束后 (为什么是手工测试结束?)
web自动化测试的场景多为回归测试
Web自动化测试所属分类
属于功能测试 (黑盒)
我们编写了自动化的脚本进行测试为什么不属于白盒或者灰盒?
我们编写的自动化脚本本身不属于程序内部的代码
主流工具
QTP
一个商业化的功能测试工具, 收费, 支持web/ 桌面自动化测试
Selenium(重点)
一个开源的web自动化测试工具, 主要做功能测试(开源)
Robot framework
一个基于python可扩展的关键字驱动的自动化测试框架(运用较少)
什么是关键字驱动?
什么是驱动?
Selenium特点
基于python搭建环境:
python 开发环境 (python v3.x)
安装 selenium 包
安装浏览器
安装浏览器驱动
保证能够用程序驱动浏览器, 实现自动化测试
为什么我们是基于python搭建的,能基于其他语言进行环境搭建吗?
安装
pip install selenium
验证
pip list
# 或者
pip show selenium
卸载
pip uninstall selenium
离线安装
- 把下载好的selenium包放到 D:\python\Lib\site-packages\ 下
- 命令行, 进入到~\python\Lib\site-packages\selenium-3.141.0
- 执行 python setup.py install
驱动下载
Chrome
下载地址: http://chromedriver.storage.googleapis.com/index.html
镜像地址: http://npm.taobao.org/mirrors/chromedriver/
浏览器下载地址: https://www.chromedownloads.net/
Firefox
下载地址: https://github.com/mozilla/geckodriver/releases
Edge(ie浏览器的改进版)
下载地址: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
安装步骤
下载浏览器驱动
各个驱动下载地址: http://www.seleniumhq.org/download/
浏览器的版本和驱动版本要一致
把驱动文件所在目录添加到Path环境变量中
或者直接放到Python安装目录,因为Python已添加到Path中
通过程序启动浏览器,并打开百度首页,暂停3秒,关闭浏览器
1.导包
2.创建浏览器驱动对象
3.打开百度首页
4.暂停3秒
5.关闭驱动对象
# 1.导包
import time
from selenium import webdriver
# 2.创建浏览器驱动对象
driver = webdriver.Chrome()
# 3.打开百度首页
driver.get("http://www.baidu.com")
# 4.暂停3秒
time.sleep(3)
# 5.关闭驱动对象
driver.quit()
让程序操作指定元素,就必须先找到此元素
HTML网页实际上就是由许多的HTML元素构成的文本文件,并且任何网页浏览器都可以直接运行HTML文件。所以可以这样说,HTML元素就是构成HTML文件的基本对象,HTML元素可以说是一个统称而已。HTML元素就是通过使用HTML标签进行定义的。
标签就是、、
等被尖括号“<”和“>”包起来的对象,绝大部分的标签都是成对出现的,如标签就是用来标记HTML元素的。位于起始标签和结束标签之间的文本就是HTML元素的内容。
为HTML元素提供各种附加信息的就是HTML属性,它总是以"属性名=属性值"这种名值对的形式出现,而且属性总是在HTML元素的开始标签中进行定义。
元素定位就是通过元素的信息或元素层级结构来定位元素的
html页面由标签构成,标签的基本格式如下:
<标签名 属性名1="属性值1" 属性名2="属性值2">文本标签名>
示例:
我的购物车
思考:如何快速的查看一个元素的相关信息?
概念
浏览器开发者工具就是给专业的web应用和网站开发人员使用的工具。包含了对HTML查看和编辑、Javascript控制台、网络状况监视等功能,是开发JavaScript、CSS、HTML和Ajax的得力助手
作用
定位元素, 查看元素信息
使用
需求:
使用浏览器开发者工具, 查看百度输入框的相关信息
Selenium提供了八种定位元素方式
1. id
2. name
3. class_name
4. tag_name
5. link_text
6. partial_link_text
7. XPath
8. CSS
id、name、class_name:为元素属性定位
tag_name:为元素标签名称
link_text、partial_link_text:为超链接定位(a标签)
XPath:为元素路径定位
CSS:为CSS选择器定位
说明:id定位就是通过元素的id属性来定位元素,HTML规定id属性在整个HTML文档中必须是唯一的;
前提:元素有id属性
但是id唯一不是硬性要求,一般情况id都是唯一的
方法
element = driver.find_element_by_id(id)
案例
案例演示环境说明:
受限于网络速度的影响,我们案例采用本地的html页面来演示。这样可以提高学习效率和脚本执行速率
需求:打开注册A.html页面,完成以下操作
1.使用id定位,输入用户名:admin
2.使用id定位,输入密码:123456
3.3秒后关闭浏览器窗口
实现步骤分析:
1. 导入selenium包 --> from selenium import webdriver
2. 导入time包 --> import time
3. 实例化浏览器驱动对象 --> driver = webdriver.Firefox()
4. 打开注册A.html --> driver.get(url)
5. 调用id定位方法 --> element = driver.find_element_by_id("")
6. 使用send_keys()方法输入内容 --> element.send_keys("admin")
7. 暂停3秒 --> time.sleep(3)
8. 关闭浏览器驱动对象 --> driver.quit()
说明:为了更好的学习体验,我们先暂时使用下send_keys()方法来输入内容
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") driver.find_element_by_id("userA").send_keys("admin") driver.find_element_by_id("passwordA").send_keys("123456") # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
说明:name定位就是根据元素name属性来定位。HTML文档中name的属性值是可以重复的。
前提:元素有name属性
方法
element = driver.find_element_by_name(name)
案例
需求:打开注册A.html页面,完成以下操作
1.使用name定位,输入用户名:admin
2.使用name定位,输入密码:123456
3.3秒后关闭浏览器窗口
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") driver.find_element_by_name("userA").send_keys("admin") driver.find_element_by_name("passwordA").send_keys("123456") # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
说明:class_name定位就是根据元素class属性值来定位元素。HTML通过使用class来定义元素的样式
前提:元素有class属性
注意:如果class有多个属性值,只能使用其中的一个
方法
element = driver.find_element_by_class_name(class_name)
案例
需求:打开注册A.html页面,完成以下操作
1.通过class_name定位电话号码A,并输入:13122223333
2.通过class_name定位电子邮箱A,并输入:[email protected]
3.3秒后关闭浏览器窗口
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") driver.find_element_by_class_name("telA").send_keys("13122223333") driver.find_element_by_class_name("emailA").send_keys("[email protected]") # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
说明:tag_name定位就是通过标签名来定位;
HTML本质就是由不同的tag组成,每一种标签一般在页面中会存在多个,所以不方便进行精确定位,一般很少使用
方法
element = driver.find_element_by_tag_name(tag_name)
# 如果存在多个相同标签,则返回符合条件的第一个标签
# 如何获取第二个元素?后面会讲到
案例
需求:打开注册A.html页面,完成以下操作
1.使用tag_name定位用户名输入框,并输入:admin
2.3秒后关闭浏览器窗口
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") driver.find_element_by_tag_name("input").send_keys("admin") # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
说明:link_text定位是专门用来定位超链接元素(标签),并且是通过超链接的文本内容来定位元素
方法
element = driver.find_element_by_link_text(link_text)
# link_text:为超链接的全部文本内容
案例
需求:打开注册A.html页面,完成以下操作
1.使用link_text定位(访问 新浪 网站)超链接,并点击
2.3秒后关闭浏览器窗口
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") driver.find_element_by_link_text("访问 新浪 网站").click() # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
说明:partial_link_text定位是对link_text定位的补充,link_text使用全部文本内容匹配元素,而partial_link_text可以使用局部来匹配元素,也可以使用全部文本内容匹配元素
方法
element = driver.find_element_by_partial_link_text(partial_link_text)
# partial_link_text:可以传入a标签局部文本-能表达唯一性
案例
需求:打开注册A.html页面,完成以下操作
1.使用partial_link_text定位(访问 新浪 网站)超链接,并点击
2.3秒后关闭浏览器窗口
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") driver.find_element_by_partial_link_text("访问").click() # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
使用上述方法的时候, 发现有一些类似的方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRn7ywk1-1609329063534)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201222143040129.png)]
方法
elements = driver.find_elements_by_xxx("xxxxxx")
作用:
1. 查找定位所有符合条件的元素
2. 返回值是一个列表
说明:列表数据格式的读取需要指定下标(下标从0开始)
思考:如果定位一组列表没定位到,会报错吗?
案例
需求:打开注册A.html页面,完成以下操作
1.使用tag_name定位密码输入框(第二个input标签),并输入:123456
2.3秒后关闭浏览器窗口
代码:
# 1.导包 import time from selenium import webdriver # 2.创建浏览器驱动对象 driver = webdriver.Chrome() # 3.业务操作 driver.get("C:\\Users\\Administrator.USER-20190219YL\\Desktop\\pagetest\\注册A.html") #elements = driver.find_elements_by_tag_name("input") #elements[1].send_keys("123456") driver.find_elements_by_tag_name("input")[1].send_keys("123456") # 4.暂停3秒 time.sleep(3) # 5.关闭驱动对象 driver.quit()
为什么要学习XPath/CSS定位
1. 如果要定位的元素没有id、name、class属性,该如何进行定位?
2. 如果通过name、class、tag_name无法定位到唯一的元素,该如何进行定位?
示例:
跟之前学接口测试jmeter中的xpath提取器有什么区别?
1. XPath即为XML Path的简称,它是一门在 XML 文档中查找元素信息的语言
2. HTML可以看做是XML的一种实现,所以Selenium用户可以使用这种强大的语言在Web应用中定位元素
XML:一种标记语言,用于数据的存储和传递。 后缀.xml结尾
3
3
3
等边三角形
4
4
5
等腰三角形
XPath之所以强大, 是因为它有非常灵活的定位方式
四种定位方式
1. 路径-定位
2. 利用元素属性-定位
3. 属性与逻辑结合-定位
4. 层级与属性结合-定位
方法
element = driver.find_element_by_xpath(xpath)
绝对路径:从最外层元素到指定元素之间所有经过元素层级的路径
- 绝对路径以/html根节点开始,使用/来分隔元素层级
如:/html/body/div/fieldset/p[1]/input- 绝对路径对页面结构要求比较严格,不建议使用
相对路径:匹配任意层级的元素,不限制元素的位置
相对路径以//开始
格式://input 或者 //*
如://input[@id=‘passwordA’] #找input标签,并且查找id是passwordA的元素
如果是//*input[@id=‘passwordA’] #找所有的标签,并且查找id是passwordA的元素
练习
需求:打开注册A.html页面,完成以下操作
1.使用绝对路径定位用户名输入框,并输入:admin
2.暂停2秒
3.使用相对路径定位密码输入框,并输入:123
核心代码:
driver.find_element_by_xpath("/html/body/div/fieldset/form/p[1]/input").send_keys("admin") time.sleep(2) driver.find_element_by_xpath("//*[@id='passwordA']").send_keys("123")
说明:通过使用元素的属性信息来定位元素
格式://input[@id='userA'] 或者 //*[@id='userA']
//input[@id='userA'] 是你知道他的层级是input标签,而且你也知道他的id是userA
//*[@id='userA'] 是你不知道他是哪个层级的标签,*代表全部,但是你知道他的id是userA
练习
需求:打开注册A.html页面,完成以下操作
利用元素的属性信息精确定位用户名输入框,并输入:admin
说明:解决元素之间个相同属性重名问题
格式://*[@name='tel' and @class='tel']
练习
需求:打开注册A.html页面,完成以下操作
使用属性与逻辑结合定位策略,在密码对应的输入框里输入:123
核心代码:
driver.find_element_by_xpath("//*[@type='password' and @id='passwordA']").send_keys("123")
说明:如果通过元素自身的信息不方便直接定位到该元素,则可以先定位到其父级元素,然后再找到该元素
格式://*[@id='p1']/input
练习
需求:打开注册A.html页面,完成以下操作
使用层级与属性结合定位策略,在test1对应的输入框里输入:test1
核心代码:
driver.find_element_by_xpath("//*[@id='p1']/input").send_keys("test1")
//*[text()="xxx"] 文本内容是xxx的元素
//*[contains(@attribute,'xxx')] 属性中含有xxx的元素 #attribute是属性名
//*[starts-with(@attribute,'xxx')] 属性以xxx开头的元素
举例:
driver.find_element_by_xpath("//*[text()='访问 新浪 网站']").click() driver.find_element_by_xpath("//*[contains(@placeholder,'用户名')]").send_keys('admin') driver.find_element_by_xpath("//*[starts-with(@placeholder,'请输入密')]").send_keys('123')
前端三件宝:html,js,css
html管理布局,js做效果,css做样式
如:html告诉你一个人应该有什么器官,js告诉你一个人能做哪些行为动作,css表示一个人长什么样
如:你想去定义一只手长啥样,你需要先定位到哪只手,才能去定义他的样式
css选择器,是css提供的一种定位方式。
1. CSS(Cascading Style Sheets)是一种语言,它用来描述HTML元素的显示样式
2. 在CSS中,选择器是一种模式,用于选择需要添加样式的元素
3. 在Selenium中也可以使用这种选择器来定位元素
提示:
1. 在selenium中推荐使用CSS定位,因为它比XPath定位速度要快
2. css选择器语法非常强大,在这里我们只学习在测试中常用的几个
常用定位方式
1. id选择器
2. class选择器
3. 元素选择器
4. 属性选择器
5. 层级选择器
方法
element = driver.find_element_by_css_selector(css_selector)
说明:根据元素id属性来选择
格式:#id
例如:#userA <选择id属性值为userA的元素>
练习
需求:打开注册A.html页面,完成以下操作
使用CSS定位方式中id选择器定位用户名输入框,并输入:admin
核心代码:
driver.find_element_by_css_selector("#userA").send_keys("admin")
说明:根据元素class属性来选择
格式:.class
例如:.telA <选择class属性值为telA的所有元素>
练习
需求:打开注册A.html页面,完成以下操作
使用CSS定位方式中class选择器定位电话号码输入框,并输入:13100000000
核心代码:
driver.find_element_by_css_selector(".telA").send_keys("123456")
说明:根据元素的标签名选择
格式:element
例如:input <选择所有input元素>
练习
需求:打开注册A.html页面,完成以下操作
使用CSS定位方式中元素选择器定位注册按钮,并点击
核心代码:
driver.find_element_by_css_selector("button").click()
说明:根据元素的属性名和值来选择
格式:[attribute=value] element[attribute=value]
例如:[type="password"] <选择type属性值为password的元素>
练习
需求:打开注册A.html页面,完成以下操作
使用CSS定位方式中属性选择器定位密码输入框,并输入:123456
核心代码:
driver.find_element_by_css_selector("[type='password']").send_keys("123456")
说明:根据元素的父子关系来选择
格式1:element1>element2 通过element1来定位element2,并且element2必须为element1的直接子元素
例如1:p[id='p1']>input <定位指定p元素下的直接子元素input>
格式2:element1 element2 通过element1来定位element2,并且element2为element1的后代元素
例如2:p[id='p1'] input <定位指定p元素下的后代元素input>
练习
需求:打开注册A.html页面,完成以下操作
使用CSS定位方式中的层级选择器定位用户名输入框,并输入:admin
核心代码:
#driver.find_element_by_css_selector("p[id='pa']>input").send_keys("admin") driver.find_element_by_css_selector("div[class='zc'] input").send_keys("admin")
input[type^='p'] type属性以p字母开头的元素
input[type$='d'] type属性以d字母结束的元素
input[type*='w'] type属性包含w字母的元素
举例:
# 输入用户名 # driver.find_element_by_css_selector("input[type^='t']").send_keys("admin") driver.find_element_by_css_selector("input[id^='u']").send_keys("admin") driver.find_element_by_css_selector("input[type$='t']").send_keys("admin") driver.find_element_by_css_selector("input[type*='ex']").send_keys("admin")
定位方式 | XPath | CSS |
---|---|---|
元素名 | //input | input |
id | //*[@id=‘userA’] | #userA |
class | //*[@class=‘telA’] | .telA |
属性 | 1. //[starts-with(@attribute,‘x’)] 2. //[text()=“x”] 3. //*[contains(@attribute,‘x’)] |
1. input[type^=‘x’] 2. input[type$=‘x’] 3. input[type*=‘x’] |
1. id、name、class_name:为元素属性定位
2. tag_name:为元素标签名称
3. link_text、partial_link_text:为超链接定位(a标签)
4. XPath:为元素路径定位
5. CSS:为CSS选择器定位
方法
方法:find_element(by=By.ID, value=None)
备注:需要两个参数,第一个参数为定位的类型由By提供,第二个参数为定位的具体方式
导包:from selenium.webdriver.common.by import By
示例
1. driver.find_element(By.CSS_SELECTOR, '#emailA').send_keys("[email protected]")
2. driver.find_element(By.XPATH, '//*[@id="emailA"]').send_keys('[email protected]')
3. driver.find_element(By.ID, "userA").send_keys("admin")
4. driver.find_element(By.NAME, "passwordA").send_keys("123456")
5. driver.find_element(By.CLASS_NAME, "telA").send_keys("13111111111")
6. driver.find_element(By.TAG_NAME, 'input').send_keys("123")
7. driver.find_element(By.LINK_TEXT, '访问 新浪 网站').click()
8. driver.find_element(By.PARTIAL_LINK_TEXT, '访问').click()
def find_element_by_id(self, id_):
"""Finds an element by id.
:Args:
- id\_ - The id of the element to be found.
:Returns:
- WebElement - the element if it was found
:Raises:
- NoSuchElementException - if the element wasn't found
:Usage:
element = driver.find_element_by_id('foo')
"""
return self.find_element(by=By.ID, value=id_)
1. 需要让脚本模拟用户给指定元素输入值
2. 需要让脚本模拟人为删除元素的内容
3. 需要让脚本模拟点击操作
1. click() 单击元素
2. send_keys(value) 模拟输入
3. clear() 清除文本
需求
需求:打开注册A页面,完成以下操作
1.通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:[email protected]
2.间隔3秒,修改电话号码为:18600000000
3.间隔3秒,点击‘注册’按钮
4.间隔3秒,关闭浏览器
ps: 元素定位方法不限
操作难点分析
1. 修改电话号码,先清除再输入新的号码; 清除 --> clear()
2. 点击按钮 --> click()
脚本启动浏览器窗口大小默认不是全屏?
如何刷新页面?
怎样解决这些问题?
1. maximize_window() 最大化浏览器窗口 --> 模拟浏览器最大化按钮
2. set_window_size(width, height) 设置浏览器窗口大小 --> 设置浏览器宽、高(像素点)
3. set_window_position(x, y) 设置浏览器窗口位置 --> 设置浏览器位置
4. back() 后退 --> 模拟浏览器后退按钮
5. forward() 前进 --> 模拟浏览器前进按钮
6. refresh() 刷新 --> 模拟浏览器F5刷新
7. close() 关闭当前窗口 --> 模拟点击浏览器关闭按钮
8. quit() 关闭浏览器驱动对象 --> 关闭所有程序启动的窗口
9. title 获取页面title
10. current_url 获取当前页面URL
close关闭与quit关闭有什么区别?
close关闭的是当前页面,但是浏览器驱动还在,会造成内存泄露,最终导致内存溢出
quit关闭的是当前浏览器驱动
# 最大化浏览器
driver.maximize_window()
# 刷新
driver.refresh()
# 后退
driver.back()
# 前进
driver.forward()
# 设置浏览器大小
driver.set_window_size(300,300)
# 设置浏览器位置
driver.set_window_position(300,200)
# 关闭浏览器单个窗口
driver.close()
# 关闭浏览器所有窗口
driver.quit()
# 获取title
title = driver.title
# 获取当前页面url
url = driver.current_url
1. 如何获取元素的文本?
2. 如何获取元素属性值?
3. 如何让程序判断元素是否为可见状态?
1. size 返回元素大小
2. text 获取元素的文本
3. get_attribute("xxx") 获取属性值,传递的参数为元素的属性名
4. is_displayed() 判断元素是否可见
5. is_enabled() 判断元素是否可用
6. is_selected() 判断元素是否选中,用来检查复选框或单选按钮是否被选中
提示:
size、text:为属性,调用时无括号;如:xxx.size
is_displayed() 和is_enabled() 扩展
面试题:如果你定位不到一个元素,请问怎么去排查?
需求:使用‘注册A.html’页面,完成以下操作:
1.获取用户名输入框的大小
2.获取页面上第一个超链接的文本内容
3.获取页面上第一个超链接的地址
4.判断页面中的span标签是否可见
5.判断页面中取消按钮是否可用
6.判断页面中'旅游'对应的复选框是否为选中的状态
代码:
# 1.获取用户名输入框的大小 print(driver.find_element_by_tag_name("a").size) # 2.获取页面上第一个超链接的文本内容 print(driver.find_element_by_tag_name("a").text) # 3.获取页面上第一个超链接的地址 print(driver.find_element_by_tag_name("a").get_attribute("href")) # 4.判断页面中的span标签是否可见 print(driver.find_element_by_tag_name("span").is_displayed()) # 5.判断页面中取消按钮是否可用 print(driver.find_element_by_id("cancelA").is_enabled()) # 6.判断页面中'旅游'对应的复选框是否为选中的状态 print(driver.find_element_by_id("lyA").is_selected())
点击、右击、双击、悬停、拖拽等
现在Web产品中存在丰富的鼠标交互方式,作为一个Web自动化测试框架,需要应对这些鼠标操作的应用场景
说明:在Selenium中将操作鼠标的方法封装在ActionChains类中
实例化对象:
action = ActionChains(driver)
方法:
1. context_click(element) 右击 --> 模拟鼠标右键点击效果
2. double_click(element) 双击 --> 模拟鼠标双击效果
3. drag_and_drop(source, target) 拖动 --> 模拟鼠标拖动效果
4. move_to_element(element) 悬停 --> 模拟鼠标悬停效果
5. perform() 执行 --> 此方法用来执行以上所有鼠标操作
为了更好的学习其他方法,我们先学习perform()执行方法,因为所有的方法都需要执行才能生效
说明:在ActionChains类中所有提供的鼠标事件方法,在调用的时候所有的行为都存储在ActionChains对象中, 而perform()方法就是真正去执行所有的鼠标事件
强调:必须调用perform()方法才能执行鼠标事件
说明:
对于点击鼠标右键,如果弹出的是浏览器默认的菜单,Selenium没有提供操作菜单选项的方法;
如果是自定义的右键菜单,则可以通过元素定位来操作菜单中的选项
练习
需求:打开注册页面A,在用户名文本框上点击鼠标右键
关键点分析
1. 导包:from selenium.webdriver.common.action_chains import ActionChains
2. 实例化ActionChains对象:action = ActionChains(driver)
3. 调用右键方法:action.context_click(element)
4. 执行:action.perform()
核心代码:
element = driver.find_element_by_id("userA") action = ActionChains(driver).context_click(element) action.perform()
说明:模拟双击鼠标左键操作
练习
需求:打开注册页面A,输入用户名admin,暂停3秒钟后,双击鼠标左键,选中admin
核心代码:
element = driver.find_element_by_id("userA") element.send_keys("admin") time.sleep(3) ActionChains(driver).double_click(element).perform()
说明:模拟鼠标拖动动作,选定拖动源元素释放到目标元素
关键点分析
1. 源元素 source = driver.find_element_by_id(xxx)
2. 目标元素 target = driver.find_element_by_id(xxx)
3. 调用方法 action.drag_and_drop(source, target).perform()
练习
需求:打开‘drag.html’页面,把红色方框拖拽到蓝色方框上
核心代码:
source = driver.find_element_by_id("div1") target = driver.find_element_by_id("div2") ActionChains(driver).drag_and_drop(source, target).perform()
说明: 模拟鼠标悬停在指定的的元素上
练习
需求:打开注册页面A,模拟鼠标悬停在‘注册’按钮上
核心代码:
element = driver.find_element_by_xpath("/html/body/div/fieldset/form/p[5]/button") ActionChains(driver).move_to_element(element).perform()
扩展:
鼠标操作ActionChains可进行链式调用,单击后可继续调用双击继续调用右键,可执行一连串操作
思考:
如何实现复制/ 粘贴的操作?
说明:
1. 模拟键盘上一些按键或者组合键的输入 如:Ctrl+C 、Ctrl+V;
2. Selenium中把键盘的按键都封装在Keys类中
导包:from selenium.webdriver.common.keys import Keys
1. send_keys(Keys.BACK_SPACE) 删除键(BackSpace)
2. send_keys(Keys.SPACE) 空格键(Space)
3. send_keys(Keys.TAB) 制表键(Tab)
4. send_keys(Keys.ESCAPE) 回退键(Esc)
5. send_keys(Keys.ENTER) 回车键(Enter)
6. send_keys(Keys.CONTROL,'a') 全选(Ctrl+A)
7. send_keys(Keys.CONTROL,'c') 复制(Ctrl+C)
提示:以上方法就不一个一个讲解了,因为调用方法都一样
需求:打开注册A页面,完成以下操作
1. 输入用户名:admin1,暂停2秒,删除1
2. 全选用户名:admin,暂停2秒
3. 复制用户名:admin,暂停2秒
4. 粘贴到密码框
# 定位用户名
element = driver.find_element_by_id("userA")
# 输入用户名
element.send_keys("admin1")
# 删除1
element.send_keys(Keys.BACK_SPACE)
# 全选
element.send_keys(Keys.CONTROL, 'a')
# 复制
element.send_keys(Keys.CONTROL, 'c')
# 粘贴
driver.find_element_by_id('passwordA').send_keys(Keys.CONTROL, 'v')
定位页面元素时如果未找到,在指定时间内一直等待的过程
元素等待一共分为两种类型
- 隐式等待
- 显式等待
由于一些原因,我们想找的元素并没有立刻出来,此时如果直接定位会报错,场景如下:
是否定位每个元素都应该需要元素等待?
方法
# 隐式等待为全局设置(只需要设置一次,就会作用于所有元素)
# 参数:
# timeout:超时的时长,单位:秒
driver.implicitly_wait(timeout)
案例
需求:打开注册A页面,完成以下操作
使用隐式等待定位用户名输入框,如果元素存在,就输入admin
注意点
单个元素定位超时会报 NoSuchElementException异常
在Selenium中把显式等待的相关方法封装在 WebDriverWait 类中
方法
# 显式等待,为定位不同元素的超时时间设置不同的值
1. 导包 from selenium.webdriver.support.wait import WebDriverWait
2. WebDriverWait(driver, timeout, poll_frequency=0.5)
1). driver:浏览器驱动对象
2). timeout:超时的时长,单位:秒
3). poll_frequency:检测间隔时间,默认为0.5秒
3. 调用方法 until(method):直到...时
1). method:函数名称,该函数用来实现对元素的定位
2). 一般使用匿名函数来实现:lambda x: x.find_element_by_id("userA")
如:ele = WebDriverWait(driver, 10, 1).until(lambda x: x.find_element_by_id("userA"))
一般函数的定义:
def test():
print("test")
#调用方式
test()
匿名函数:
test = lambda x , y : x+y
#调用
test(1,2)
#结果为3
print(test(1,2))
案例
需求:打开注册A页面,完成以下操作
使用显示等待定位用户名输入框,如果元素存在,就输入admin
注意点
单个元素定位超时会报 TimeoutException异常
1. 作用域:隐式为全局元素,显式等待为单个元素有效
2. 使用方法:隐式等待直接通过驱动对象调用,而显式等待方法封装在WebDriverWait类中
3. 达到最大超时时长后抛出的异常不同:隐式为NoSuchElementException,显式等待为TimeoutException
案例
需求:使用‘注册A.html’页面,完成对城市的下拉框的操作
1.选择‘广州’
2.暂停2秒,选择‘上海’
3.暂停2秒,选择‘北京’
核心代码
driver.find_element_by_xpath("//*[@id='selectA']/option[3]").click()
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[2]").click()
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[1]").click()
说明: 下拉框就是 HTML 中的 元素
思路: 先定位到操作的 option 元素, 然后执行点击操作
问题: 操作起来比较繁琐
Select类
说明:Select类是Selenium为操作select标签特殊封装的
实例化对象:
select = Select(element)
element:
Select类实现步骤分析
1. 导包 Select类 --> from selenium.webdriver.support.select import Select
2. 实例化Select类 select = Select(driver.find_element_by_id("selectA"))
3. 调用方法:select.select_by_index(index)
案例核心代码
select = Select(driver.find_element_by_xpath("//*[@id='selectA']"))
select.select_by_index(2)
time.sleep(2)
select.select_by_index(1)
time.sleep(2)
select.select_by_index(0)
网页中常用的弹出框有三种
1. alert 警告框
2. confirm 确认框
3. prompt 提示框
需求:打开注册A.html页面,完成以下操作:
1.点击 alert 按钮
2.输入用户名:admin
错误代码:
driver.find_element_by_xpath("//*[@id='alerta']").click() driver.find_element_by_id("userA").send_keys("admin")
思考
按钮被点击后弹出警告框,而接下来输入用户名的语句没有生效
1. 什么问题导致的?
2. 如何处理警告框?
说明:Selenium中对处理弹出框的操作,有专用的处理方法;并且处理的方法都一样
1. 获取弹出框对象
alert = driver.switch_to.alert
2. 调用
alert.text --> 返回alert/confirm/prompt中的文字信息
alert.accept() --> 接受对话框选项
alert.dismiss() --> 取消对话框选项
核心代码
driver.find_element_by_xpath("//*[@id='alerta']").click()
time.sleep(2)
alert = driver.switch_to.alert
time.sleep(2)
print(alert.text)
time.sleep(2)
alert.accept()
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")
1. 在HTML页面中,由于前端技术框架的原因,页面元素为动态显示,元素根据滚动条的下拉而被加载
2. 页面注册同意条款,需要滚动条到最底层,才能点击同意
说明:
selenium中没有提供操作滚动条的方法,但是它提供了可执行JavaScript脚本的方法,所以我们可以通过JavaScript脚本来达到操作滚动条的目的
1. 设置JavaScript脚本控制滚动条
js = "window.scrollTo(0,1000)"
(0:左边距;1000:上边距;单位像素)
2. selenium调用执行JavaScript脚本的方法
driver.execute_script(js)
JavaScript是一种被广泛用于Web前端开发的脚本语言 ,常用来为网页添加动态功能(弹窗,点击事件…),JavaScript提供了页面对象获取和操作功能。
实现方式一:
编写js脚本 定位用户名输入框,并且输入admin
js = "document.getElementById('userA').value='admin'"
#执行js脚本
driver.execute_script(js)
#实现方式二:
#编写js定位元素并且返回定位到的元素
js = "return document.getElementById('userA')"
#将定位到的元素赋予el这个变量
el = driver.execute_script(js)
#输入admin
el.send_keys('admin')
如何去开发者工具调试js语句?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzWlNqtB-1609329063538)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201230150516102.png)]
需求:打开注册页面A,暂停2秒后,滚动条拉到最底层
示例代码
js1 = "window.scrollTo(0,10000)" # 最底部
js2 = "window.scrollTo(0,0)" # 最顶部
time.sleep(2)
driver.execute_script(js1)
time.sleep(2)
driver.execute_script(js2)
frame:HTML页面中的一种框架,主要作用是在当前页面中指定区域显示另一页面元素;
形式一:[了解]
形式二:
需求:打开‘注册实例.html’页面,完成以下操作
1. 填写主页面的注册信息
2. 填写注册页面A中的注册信息
3. 填写注册页面B中的注册信息
问题:
当前页面内无法定位注册页面A和注册页面B
说明:在Selenium中封装了如何切换frame框架的方法
步骤:
1. driver.switch_to.frame(frame_reference) --> 切换到指定frame的方法
frame_reference:可以为frame框架的name、id或者定位到的frame元素
2. driver.switch_to.default_content() --> 恢复默认页面方法
在frame中操作其他页面,必须先回到默认页面,才能进一步操作
解决方案:
- 完成主页面注册信息;
- 调用frame切换方法(switch_to.frame(“myframe1”))切换到注册用户A框架中
- 调用恢复默认页面方法(switch_to.default_content())
- 调用frame切换方法(switch_to.frame(“myframe2”))切换到注册用户B框架中
driver.find_element_by_id("userA").send_keys("admin")
# driver.switch_to.frame(driver.find_element_by_id("idframe1"))
# driver.switch_to.frame("idframe1")
driver.switch_to.frame("myframe1")
driver.find_element_by_id("userA").send_keys("adminA")
driver.switch_to.default_content()
driver.switch_to.frame("myframe2")
driver.find_element_by_id("userA").send_keys("adminB")
什么是窗口?
为什么要切换窗口?
在HTML页面中,当点击超链接或者按钮时,有的会在新的窗口打开页面
需求:打开注册页面A
1. 新窗口打开新浪
2. 在新浪搜索框输入'新浪搜索'
3. 在注册页输入用户名admin
说明:在Selenium中封装了获取当前窗口句柄、获取所有窗口句柄和切换到指定句柄窗口的方法
句柄:英文handle,窗口的唯一识别码
方法:
1. driver.current_window_handle --> 获取当前窗口句柄
2. driver.window_handles --> 获取所有窗口句柄
3. driver.switch_to.window(handle) --> 切换指定句柄窗口
解决方案:
- 打开注册A页面
- 获取当前窗口句柄
- 在注册A页面点击 “访问 新浪 网站”
- 获取所有窗口句柄
- 根据句柄, 切换到新浪窗口
- 对输入框进行输入
- 切换回原窗口, 即注册A窗口
- 输入用户名 admin
注意: 新浪页面加载慢, 可能需要用到元素等待
driver.implicitly_wait(10)
print("切换前窗口句柄:", driver.current_window_handle)
driver.find_element_by_id("fw").click()
handles = driver.window_handles
print("所有窗口句柄:", handles)
driver.switch_to.window(handles[1])
print("切换后窗口句柄:", driver.current_window_handle)
driver.find_element_by_class_name("inp-txt").clear()
driver.find_element_by_class_name("inp-txt").send_keys("1111111111111")
time.sleep(3)
driver.switch_to.window(handles[0])
print("切换回原窗口句柄:", driver.current_window_handle)
driver.find_element_by_id("userA").send_keys("admin")
什么是窗口截图?
把当前操作的页面,截图保存到指定位置
为什么要窗口截图?
自动化脚本是由程序去执行的,因此有时候打印的错误信息并不是十分明确
如果在执行出错的时候对当前窗口截图保存,那么通过图片就可以非常直观地看到出错的原因
说明:在Selenium中,提供了截图方法,我们只需要调用即可
方法:
driver.get_screenshot_as_file(imgpath)
imgpath:图片保存路径
需求:打开‘注册A.html’页面,完成以下操作
1. 填写注册信息
2. 截图保存
核心代码:
driver.find_element_by_id("userA").send_keys("admin") # 需提前创建 png 目录 driver.get_screenshot_as_file("./png/123.png") ps:filepath = "./img/test_{}.png".format(time.strftime("%Y%m%d%H%M%S"))
什么是验证码?
一种随机生成的信息(数字、字母、汉字、图片、算术题)等为了防止恶意的请求行为,增加应用的安全性
为什么要学习验证码?
在Web应用中,大部分系统在用户登录注册的时候都要求输入验证码,而我们在设计自动化测试脚本的时候,就需要面临处理验证码的问题
说明:Selenium中并没有对验证码处理的方法,在这里我们介绍一下针对验证码的几种常用处理方式
方式:
1. 去掉验证码
(测试环境下-采用)
2. 设置万能验证码
(生产环境和测试环境下-采用)
3. 验证码识别技术
(通过Python-tesseract来识别图片类型验证码;识别率很难达到100%)
4. 记录cookie
(通过记录cookie进行跳过登录)
PS
1. 去掉验证码、设置万能验证码:都是开发来完成,我们在这里不做讲解
2. 验证码识别技术:成功率不高,验证码种类繁多,不太适合
3. 记录cookie:比较实用,我们对它进行下讲解
1. Cookie是由Web服务器生成的,并且保存在用户浏览器上的小文本文件,它可以包含用户相关的信息
2. Cookie数据格式:键值对组成(python中的字典)
3. Cookie产生:客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个Cookie数据
4. Cookie使用:当浏览器再次请求该网站时,浏览器把请求的数据和Cookie数据一同提交给服务器,服务器检查该 Cookie,以此来辨认用户状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-liiZ3JXN-1609329063540)(.\Web自动化测试.assets\image-20200104103932686.png)]
1. 实现会话跟踪,记录用户登录状态
2. 实现记住密码和自动登录的功能
3. 用户未登录的状态下,记录购物车中的商品
说明:Selenium中对cookie操作提供相应的方法
方法:
1. get_cookie(name) --> 获取指定cookie
name:为cookie的名称
2. get_cookies() --> 获取本网站所有本地cookies
3. add_cookie(cookie_dict) --> 添加cookie
cookie_dict:一个字典对象,必选的键包括:"name" and "value"
需求:使用cookie实现跳过登录
1. 手动登录百度,获取cookie
2. 使用获取到的cookie,达到登录目的,然后就可以执行登录之后的操作
BDUSS是登录百度后的唯一身份凭证(*.baidu.com),拿到BDUSS就等于拿到帐号的控制权,通行贴吧、知道、百科、文库、空间、百度云等百度主要产品。
1. 登录baidu,登录成功后抓取 (BDUSS)
2. 使用add_cookie()方法,添加 (BDUSS)键和值
3. 调用刷新方法 driver.refresh()
from selenium import webdriver
import time
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
driver.add_cookie({'name': 'BDUSS', 'value': '根据实际填写'})
time.sleep(3)
driver.refresh()
time.sleep(3)
driver.quit()
说明:
1. 框架英文单词framework
2. 为解决一类事情的功能集合
Pytest框架 是一个第三方单元测试框架,用它来做单元测试
UnitTest 是Python自带的一个单元测试框架
1. 能够组织多个用例去执行
2. 提供丰富的断言方法
3. 能够生成测试报告
安装
pip install pytest
# pip install pytest==3.10
推荐安装 3.10 的版本, 兼容性较好
校验
方式1. 命令行模式【建议】
方式2. 主函数模式
test_login.py
class TestLogin:
def test_a(self): # test开头的测试函数
print("----->test_a")
def test_b(self):
print("----->test-b")
命令行中执行:
pytest -s test_login.py
-s 表示支持控制台打印,如果不加,print 不会出现任何内容
在 test_login.py 文件中增加主函数:
# 需要先导入包: import pytest
if __name__ == '__main__':
pytest.main(["-s", "test_login.py"])
test_login.py ----->test_a
.----->test_b
F
. 表示成功
F 表示失败
让程序代替人为判断测试程序执行结果是否符合预期结果的过程
自动化脚本在执行的时候一般都是无人值守状态,我们不知道执行结果是否符合预期结果,所以我们需要让程序代替人为检测程序执行的结果是否符合预期结果,这就需要使用断言
assert xx -->判断xx为真
assert not xx -->判断xx不为真
assert a in b -->判断b包含a
assert a == b -->判断a等于b
assert a != b -->判断a不等于b
对比UinitTest的断言:
需求:
1. 写一个计算加法的函数, 要求可以输入两个参数
2. 写两个测试脚本, 测试加法计算结果, 一个断言成功, 另一个断言失败
3. 运行并查看结果
test_demo.py
def add(x, y):
return x + y
class TestDemo:
def test_a(self):
assert 2 == add(1, 1)
def test_b(self):
assert 4 == add(2, 1)
pytest 在运行自动化脚本的前后会执行两个特殊的方法,分别是“前置” 和“后置”方法。
在执行脚本前会执行“前置”方法,在执行脚本后会执行 “后置”方法。
有了这两个方法,我们可以在“前置”方法中获取驱动对象,在“后置”方法中关闭驱动对象。
1. 初始化(前置处理):
def setup(self) --> 首先自动执行
2. 销毁(后置处理):
def teardown(self) --> 最后自动执行
3. 运行于测试方法的始末,即:运行一次测试方法就会运行一次setUp和tearDown
需求:在一个测试类中定义多个测试方法,查看每个测试方法执行完所花费的时长
test_demo.py
import time
def add(x, y):
return x + y
class TestDemo:
def setup(self):
print("start-time:", time.time())
def teardown(self):
print("end-time:", time.time())
def test_a(self):
assert 2 == add(1, 1)
def test_b(self):
assert 3 == add(2, 1)
使用配置文件, 可以通过配置项来选择执行哪些目录下的哪些测试模块。
步骤:
1. 项目下新建 scripts 模块
2. 将测试脚本文件放到 scripts 中
3. pytest 的配置文件放在自动化项目目录下
4. 名称为 pytest.ini
5. 第一行内容为 [pytest] , 后面写具体的配置参数
6. 命令行运行时会使用该配置文件中的配置
可以直接拿来复制粘贴
[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -s
表示命令行参数
testpaths,python_files,python_classes,python_functions
表示执行 scripts 文件夹下的 test_ 开头 .py 结尾的文件下的 Test 开头的类下的 test_开头的函数
1. 配置文件是否已经正确的加载?
- 通过控制台的 inifile 进行查看
2. windows中可能出现 “gbk” 错误?
- 删除 ini 文件中的所有中文
3. 在工作中这个文件也需要复制粘贴?
- 是的,一个项目只会用一次,只需理解,会修改即可
自动化测试脚本最终执行是通过还是不通过,需要通过测试报告进行体现
使用命令 pip install pytest-html 进行安装
install pytest-html==1.21.1 在最新的2.0上有错误
在配置文件中的命令行参数中增加 --html=用户路径/report.html
示例: pytest.ini 中
addopts = -s --html=report/report.html
步骤:
1. 命令行输入 pytest 运行脚本
2. 在项目目录下会对一个 report 文件夹,里面有个 report.html 即为测试报告
登录功能都是输入用户名,输入密码,点击登录。但登录的用户名和密码如果想测试多个值时, 数据参数化可以使代码更整洁, 可读性更好
# 数据参数化
# 参数:
# argnames:参数名
# argvalues:参数对应值,类型必须为可迭代类型,一般使用list @pytest.mark.parametrize(argnames, argvalues)
在需要参数化的测试脚本之上加上装饰器 @pytest.mark.parametrize(argnames, argvalues)
# 要求:
# 不使用数据参数化的情况下, 写两个函数, 分别打印用户名 zhangsan 和 lisi
def test_a(self):
print("zhangsan")
def test_b(self):
print("lisi")
示例
# 要求:
# 使用单一参数的数据参数化, 修改上面的代码
@pytest.mark.parametrize("name", ["zhangsan", "lisi"])
def test_a(self, name):
print(name)
结果
scripts\test_login.py zhangsan
.lisi
.
示例
# 要求:
# 使用多个参数的数据参数化, 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222
@pytest.mark.parametrize(("username", "password"), [("zhangsan","111111"), ("lisi","222222")])
def test_a(self, username, password):
print(username + "---" + password)
结果
scripts\test_login.py zhangsan---111111
.lisi---222222
.
示例
# 要求:
# 使用扩展, 增加数据可读性, 使用字典作为参数列表中的每个元素, 修改上面得案例
@pytest.mark.parametrize("params", [{"username": "zhangsan","password": "111"},{"username": "lisi","password": "222"}])
def test_a(self, params):
print(params)
结果
scripts\test_login.py {'username': 'zhangsan', 'password': '111'}
.{'username': 'lisi', 'password': '222'}
.
对TPshop项目的登录模块进行自动化测试
提示:登录模块包含了很多测试用例,比如:账号不存在、密码错误、验证码错误、登录成功等等
为了节省时间我们只选取几个有代表性的用例来演示
登录功能, 账号不存在, test_login_account_not_exist.py
# 账号不存在
import time
from selenium import webdriver
# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get('http://localhost/')
# 1. 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_class_name('red').click()
# 2. 输入一个不存在的用户名
driver.find_element_by_id('username').send_keys('17100000000')
# 3. 输入密码
driver.find_element_by_id('password').send_keys('123456')
# 4. 输入验证码
driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
# msg = driver.find_element_by_css_selector('.layui-layer-content').text
msg = driver.find_element_by_xpath("//*[@id='layui-layer1']/div[2]").text
print(msg)
# 关闭浏览器
time.sleep(5)
driver.quit()
登录功能, 密码错误, test_login_password_error.py
# 密码错误
import time
from selenium import webdriver
# 实例化浏览器驱动
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get('http://localhost/')
# 1. 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_class_name('red').click()
# 2. 输入一个已存在的用户名
driver.find_element_by_id('username').send_keys('17150312012')
# 3. 输入错误密码
driver.find_element_by_id('password').send_keys('error')
# 4. 输入验证码
driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
msg = driver.find_element_by_css_selector('.layui-layer-content').text
print(msg)
# 关闭浏览器
time.sleep(5)
driver.quit()
引入Pytest管理用例, 并断言用例的执行结果
好处
# 1.导包
import time
from selenium import webdriver
# 2.定义测试类
class TestLogin:
def setup(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(10)
self.driver.get('http://localhost/')
def teardown(self):
# 关闭浏览器
time.sleep(5)
self.driver.quit()
# 定义账户不存在测试方法
def test_login_accout_not_exist(self):
# 1. 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_class_name('red').click()
# 2. 输入一个不存在的用户名
self.driver.find_element_by_id('username').send_keys('17100000000')
# 3. 输入密码
self.driver.find_element_by_id('password').send_keys('123456')
# 4. 输入验证码
self.driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
self.driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
msg = self.driver.find_element_by_css_selector('.layui-layer-content').text
print(msg)
# 断言
assert "账号不存在!" == msg
# 定义密码错误的测试
def test_login_password_error(self):
# 1. 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_class_name('red').click()
# 2. 输入一个已存在的用户名
self.driver.find_element_by_id('username').send_keys('17150312012')
# 3. 输入错误密码
self.driver.find_element_by_id('password').send_keys('error')
# 4. 输入验证码
self.driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
self.driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
msg = self.driver.find_element_by_css_selector('.layui-layer-content').text
print(msg)
# 断言
assert "密码错误!" == msg
代码冗余
#获取驱动的代码冗余,如果有多个test文件,则需要多次重复编写创建驱动
概念
是将一些有共性的或多次被使用的代码提取到一个方法中,供其他地方调用
好处
目的
用最少的代码实现最多的功能
使用方法封装的思想,对代码进行优化
定义获取驱动对象的工具类
utils.py
# 获取/关闭浏览器驱动的类
from selenium import webdriver
class DriverUtils:
__driver = None
# 获取浏览器驱动
@classmethod
def get_driver(cls):
if cls.__driver is None:
cls.__driver = webdriver.Chrome()
cls.__driver.maximize_window()
cls.__driver.implicitly_wait(10)
return cls.__driver
# 关闭浏览器驱动
@classmethod
def quit_driver(cls):
if cls.__driver is not None:
cls.__driver.quit()
cls.__driver = None
test_login.py
# 1.导包
import time
from selenium import webdriver
from utils import DriverUtils
# 2.定义测试类
class TestLogin:
def setup(self):
self.driver = DriverUtils.get_driver()
self.driver.get('http://localhost/')
def teardown(self):
DriverUtils.quit_driver()
# 定义账户不存在测试方法
def test_login_accout_not_exist(self):
# 1. 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_class_name('red').click()
# 2. 输入一个不存在的用户名
self.driver.find_element_by_id('username').send_keys('17100000000')
# 3. 输入密码
self.driver.find_element_by_id('password').send_keys('123456')
# 4. 输入验证码
self.driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
self.driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
msg = self.driver.find_element_by_css_selector('.layui-layer-content').text
print(msg)
# 断言
assert "账号不存在!" == msg
# 定义密码错误的测试
def test_login_password_error(self):
# 1. 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_class_name('red').click()
# 2. 输入一个已存在的用户名
self.driver.find_element_by_id('username').send_keys('17150312012')
# 3. 输入错误密码
self.driver.find_element_by_id('password').send_keys('error')
# 4. 输入验证码
self.driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
self.driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
msg = self.driver.find_element_by_css_selector('.layui-layer-content').text
print(msg)
# 断言
assert "密码错误!" == msg
代码冗余
在做UI自动化时定位元素特别依赖页面,一旦页面发生变更就不得不跟着去修改定位元素的代码
举例:
假设要对一个元素进行点击操作,而且会经常用到,那么你就可能会编写多处如下代码
driver.find_element_by_id("login-btn").click()
存在的问题
如何解决呢?
PO是Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一
核心思想:
- 通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化, 只需要调整页面元素封装的代码,提高测试用例的可维护性、可读性
- 页面和测试脚本分离
分层机制,让不同层去做不同类型的事情,让代码结构清晰,增加复用性
主要有以下几种分层方式
两层: 对象操作层+业务数据层
- 对象操作层: 封装页面信息, 包括元素以及对元素的操作
- 对象操作层中,页面的元素是对象,点击输入等行为是操作
- 业务数据层: 封装多操作组合的业务以及测试数据
- 业务数据层中,test脚本中执行的是业务流程只需要关注业务,并且测试数据也是在此层传入
三层:对象库层+操作层+业务数据层
- 对象层:封装页面元素的定位方法
- 操作层:封装对元素的操作 (点击输入等)
- 业务数据层:将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入帐号、密码、点击登录三个操作,以及操作所需要的数据
四层:对象库层+操作层+业务层+数据层
采用PO模式的分层思想对代码进行拆分
对登录页面进行封装
LoginPage
编写测试用例
TestLogin
结构
- utils
-- utils.py
- page
-- login_page.py
- scripts
-- test_login.py
- pytest.ini
核心代码
utils.py
class DriverUtils:
__driver = None
# 获取浏览器驱动
@classmethod
def get_driver(cls):
if cls.__driver is None:
cls.__driver = webdriver.Chrome()
cls.__driver.maximize_window()
cls.__driver.implicitly_wait(10)
return cls.__driver
# 关闭浏览器驱动
@classmethod
def quit_driver(cls):
if cls.__driver is not None:
cls.__driver.quit()
cls.__driver = None
login_page.py
class LoginPage:
def __init__(self, driver):
self.driver = driver
# 1. 点击首页的‘登录’链接,进入登录页面
def click_login_link(self):
return self.driver.find_element_by_class_name('red').click()
# 2. 输入一个不存在的用户名
def input_username(self, username):
return self.driver.find_element_by_id('username').send_keys(username)
# 3. 输入密码
def input_password(self, password):
return self.driver.find_element_by_id('password').send_keys(password)
# 4. 输入验证码
def input_verify_code(self, code):
return self.driver.find_element_by_id('verify_code').send_keys(code)
# 5. 点击登录按钮
def click_login_btn(self):
return self.driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
def get_msg(self):
msg = self.driver.find_element_by_css_selector('.layui-layer-content').text
return msg
test_login.py
import time
from page.login_page import LoginPage
from utils.utils import DriverUtils
class TestLogin:
def setup(self):
self.driver = DriverUtils.get_driver()
self.login_page = LoginPage(self.driver)
self.driver.get('http://localhost/')
def teardown(self):
time.sleep(3)
DriverUtils.quit_driver()
# 定义账户不存在测试方法
def test_login_accout_not_exist(self):
# 1. 点击首页的‘登录’链接,进入登录页面
self.login_page.click_login_link()
# 2. 输入一个不存在的用户名
self.login_page.input_username("17100000000")
# 3. 输入密码
self.login_page.input_password("123456")
# 4. 输入验证码
self.login_page.input_verify_code("8888")
# 5. 点击登录按钮
self.login_page.click_login_btn()
# 6. 获取错误提示信息
print(self.login_page.get_msg())
# 断言
assert "账号不存在!" == self.login_page.get_msg()
# 定义密码错误的测试
def test_login_password_error(self):
# 1. 点击首页的‘登录’链接,进入登录页面
self.login_page.click_login_link()
# 2. 输入一个已存在的用户名
self.login_page.input_username("17150312012")
# 3. 输入错误密码
self.login_page.input_password("error")
# 4. 输入验证码
self.login_page.input_verify_code("8888")
# 5. 点击登录按钮
self.login_page.click_login_btn()
# 6. 获取错误提示信息
print(self.login_page.get_msg())
# 断言
assert "密码错误!" == self.login_page.get_msg()
pytest.ini
[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
可以看到test脚本下方法的操作都是一致的 只是用例的数据不一样,这时候我们可以使用参数化的形式:
@pytest.mark.parametrize("params",[{},{}])
对PO分层后的代码继续优化
只变更了 login_page.py
from selenium.webdriver.common.by import By
class LoginPage:
# 登录链接 按钮
login_link_btn = By.CLASS_NAME, "red"
# 用户名 输入框
username_input = By.ID, "username"
# 密码 输入框
password_input = By.ID, "password"
# 验证码 输入框
verify_code_input = By.ID, "verify_code"
# 登录 按钮
login_btn = By.NAME, "sbtbutton"
# 提示信息
msg_info = By.CSS_SELECTOR, ".layui-layer-content"
def __init__(self, driver):
self.driver = driver
def find_el(self, feature):
return self.driver.find_element(*feature)
# 1. 点击首页的‘登录’链接,进入登录页面
def click_login_link(self):
return self.find_el(self.login_link_btn).click()
# return self.driver.find_element(self.login_link_btn[0], self.login_link_btn[1]).click()
# return self.driver.find_element_by_class_name('red').click()
# 2. 输入一个不存在的用户名
def input_username(self, username):
return self.find_el(self.username_input).send_keys(username)
# return self.driver.find_element_by_id('username').send_keys(username)
# 3. 输入密码
def input_password(self, password):
return self.find_el(self.password_input).send_keys(password)
# return self.driver.find_element_by_id('password').send_keys(password)
# 4. 输入验证码
def input_verify_code(self, code):
return self.find_el(self.verify_code_input).send_keys(code)
# return self.driver.find_element_by_id('verify_code').send_keys(code)
# 5. 点击登录按钮
def click_login_btn(self):
return self.find_el(self.login_btn).click()
# return self.driver.find_element_by_name('sbtbutton').click()
# 6. 获取错误提示信息
def get_msg(self):
msg = self.find_el(self.msg_info).text
# msg = self.driver.find_element_by_css_selector('.layui-layer-content').text
return msg
把共同的方法进行封装
结构
- base
-- base_action.py
- utils
-- utils.py
- page
-- login_page.py
- scripts
-- test_login.py
- pytest.ini
代码
base_aciton.py
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_el(self, feature):
return self.driver.find_element(*feature)
def find_els(self, feature):
return self.driver.find_elements(*feature)
def input(self, feature, context):
return self.find_el(feature).send_keys(context)
def click(self, feature):
return self.find_el(feature).click()
def clear(self, feature):
return self.find_el(feature).clear
login_page.py
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class LoginPage(BaseAction):
# 登录链接 按钮
login_link_btn = By.CLASS_NAME, "red"
# 用户名 输入框
username_input = By.ID, "username"
# 密码 输入框
password_input = By.ID, "password"
# 验证码 输入框
verify_code_input = By.ID, "verify_code"
# 登录 按钮
login_btn = By.NAME, "sbtbutton"
# 提示信息
msg_info = By.CSS_SELECTOR, ".layui-layer-content"
# 1. 点击首页的‘登录’链接,进入登录页面
def click_login_link(self):
return self.click(self.login_link_btn)
# return self.find_el(self.login_link_btn).click()
# 2. 输入一个不存在的用户名
def input_username(self, context):
return self.input(self.username_input, context)
# return self.find_el(self.username_input).send_keys(username)
# 3. 输入密码
def input_password(self, context):
return self.input(self.password_input, context)
# return self.find_el(self.password_input).send_keys(password)
# 4. 输入验证码
def input_verify_code(self, context):
return self.input(self.verify_code_input, context)
# return self.find_el(self.verify_code_input).send_keys(code)
# 5. 点击登录按钮
def click_login_btn(self):
return self.click(self.login_btn)
# return self.find_el(self.login_btn).click()
# 6. 获取错误提示信息
def get_msg(self):
msg = self.find_el(self.msg_info).text
return msg
数据驱动: 是以数据来驱动整个测试用例的执行,也就是测试数据决定测试结果
比如我们要测试加法,我们的测试数据是1和1,测试结果就是2,如果测试数据是1和2,测试结果就是3
JSON的全称是”JavaScript Object Notation”,是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式
特点
对比XML
XML 指可扩展标记语言, XML 被设计用来传输和存储数据
<name>tomname>
<age>18age>
<isMan>trueisMan>
<school />
<address>
<country>中国country>
<city>广州city>
address>
<numbers>2numbers>
<numbers>4numbers>
<numbers>6numbers>
<numbers>8numbers>
<like_links>
<name>Baiduname>
<url>http://www.baidu.comurl>
like_links>
<like_links>
<name>TaoBaoname>
<url>http://www.taobao.comurl>
like_links>
{
"name": "tom",
"age": 18,
"isMan": true,
"school": null,
"address": {
"country": "中国",
"city": "广州"
},
"numbers": [2, 4, 6, 8],
"like_links": [
{
"name": "Baidu",
"url": "http://www.baidu.com"
},
{
"name": "TaoBao",
"url": "http://www.taobao.com"
}
]
}
如果使用XML,需要读取XML文档,然后用XML DOM来遍历文档并读取值并存储在变量中
使用JSON,只需读取JSON字符串
- 大括号保存对象
- 中括号保存数组
- 对象数组可以相互嵌套
- 数据采用键值对表示
- 多个数据由逗号分隔
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 数组(在中括号中)
- 对象(在大括号中)
- null
操作内容
在python中想要操作 JSON, 需要先导入依赖包
import json
把python字典类型转换为JSON字符串
# 定义一个字典对象
dict_str = {
"name": "zhangsan",
"age": 18,
"is_man": False,
"school": None
}
# python字典转换成json格式数据
json_str = json.dumps(dict_str)
把JSON字符串转换为python字典
# 定义一个JSON字符串
json_str = '{"name": "zhangsan", "age": 18, "is_man": false, "school": null}'
# json格式数据转换成python字典
json.loads(json_str)
准备数据
{
"name": "tom",
"age": 18,
"isMan": true,
"school": null,
"address": {
"country": "中国",
"city": "广州"
},
"numbers": [2, 4, 6, 8],
"like_links": [
{
"name": "Baidu",
"url": "http://www.baidu.com"
},
{
"name": "TaoBao",
"url": "http://www.taobao.com"
}
]
}
读取JSON文件 + 写入JSON文件
import json
# 读json文件"data.json"
with open("data.json", "r", encoding="utf-8") as f:
data = json.load(f)
print(data)
# 把data2这个字典写入json文件"data2.json"
data2 = data
with open("data2.json", "w", encoding="utf-8") as f:
json.dump(data2, f)
为什么我们要把json文件的数据转换成字典呢?
如果需要读取数据,我们采用的是字典方式读取,而json数据是字符串,使用之前我们需要先转换成字典。
对tpshop项目的登录模块进行自动化测试, 需要使用数据驱动
为v6版本增加数据驱动
账号不存在!
密码错误!
核心代码
# 分析测试脚本参数化模板
dict1 = {"username": "17100000000", "password": "123456", "code": "8888", "msg": "账号不存在!"}
dict2 = {"username": "17150312012", "password": "error", "code": "8888", "msg": "密码错误!"}
@pytest.mark.parametrize("params", [dict1, dict2])
def test_login(self, params):
self.login_page.click_login_link()
self.login_page.input_username(params["username"])
self.login_page.input_password(params["password"])
self.login_page.input_verify_code(params["code"])
self.login_page.click_login_btn()
print(self.login_page.get_msg())
assert params["msg"] == self.login_page.get_msg()
新建 ./data/login_data.json 文件
{
"login_001_account_exist": {
"username": "17100000000",
"password": "123456",
"code": "8888",
"msg": "账号不存在!"
},
"login_002_password_error": {
"username": "17150312012",
"password": "error",
"code": "8888",
"msg": "密码错误!"
}
}
需要对json数据文件进行解析, 在test_login.py中, 写一个解析函数
def analyze_data():
with open("./data/login_data.json", "r", encoding="utf-8") as f:
list_data = []
dict_data = json.load(f)
for value in dict_data.values():
list_data.append(value)
return list_data
在base包中新建base_analyze.py文件
import json
def analyze_data(filename):
with open("./data/" + filename, "r", encoding="utf-8") as f:
list_data = []
dict_data = json.load(f)
for value in dict_data.values():
list_data.append(value)
return list_data
在test_login.py中进行调用, 核心代码如下
@pytest.mark.parametrize("params", analyze_data("login_data.json"))
def test_login(self, params):
self.login_page.click_login_link()
self.login_page.input_username(params["username"])
self.login_page.input_password(params["password"])
self.login_page.input_verify_code(params["code"])
self.login_page.click_login_btn()
print(self.login_page.get_msg())
assert params["msg"] == self.login_page.get_msg()
对网页计算器,进行加法的测试操作。通过读取数据文件中的数据来执行用例
网址: http://cal.apple886.com/
目录
-- base
-- base_action.py
-- base_analyze.py
-- data
-- cal_data.json
-- page
-- cal_page.py
-- scripts
-- test_add.py
-- utils
-- utils.py
pytest.ini
代码
base和utils包的内容基本不变, 其中base_action.py 有新增数字定位方法
def find_ele_num(self, feature, digit):
return self.driver.find_element(feature[0], feature[1].format(str(digit)))
data/cal_data.json
{
"add_001_xxx": {
"data": [1, 2],
"result": 3
},
"add_001_xxxx": {
"data": [1, 2, 3],
"result": 6
}
}
page/cal_page.py
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class CalPage(BaseAction):
# 数字 按钮
digit_btn = By.ID, "simple{}"
# 加法 按钮
add_btn = By.ID, "simpleAdd"
# 等号 按钮
eq_btn = By.ID, "simpleEqual"
# 计算结果
result = By.ID, "resultIpt"
# 点击数字
def click_digit_btn(self, num):
return self.find_ele_num(self.digit_btn, num).click()
# 点击加法
def click_add_btn(self):
return self.click(self.add_btn)
# 点击等号
def click_eq_btn(self):
return self.click(self.eq_btn)
# 获取结果
def get_result(self):
return self.find_el(self.result).get_attribute("value")
scripts/test_add.py
import time
import pytest
from base.base_analyze import analyze_data
from page.cal_page import CalPage
from utils.utils import DriverUtils
class TestCal:
def setup(self):
self.driver = DriverUtils.get_driver()
self.cal_page = CalPage(self.driver)
self.driver.get('http://cal.apple886.com/')
def teardown(self):
time.sleep(3)
DriverUtils.quit_driver()
@pytest.mark.parametrize("params", analyze_data("cal_data.json"))
def test_add(self, params):
for i in params["data"]:
self.cal_page.click_digit_btn(i)
self.cal_page.click_add_btn()
self.cal_page.click_eq_btn()
# print("---",self.cal_page.get_result())
# print("+++",params["result"])
assert self.cal_page.get_result() == str(params["result"])
日志就是用于记录系统运行时的信息, 对一个事件的记录, 也称为Log
思考:
是否系统记录的所有日志信息重要性都一样?
日志级别: 指日志信息的重要性
常见日志级别
日志级别 | 描述 |
---|---|
DEBUG | 调试级别,打印非常详细的日志信息,通常用于对代码的调试 |
INFO | 信息级别,打印一般的日志信息,突出强调程序的运行过程 |
WARNING | 警告级别,打印警告日志信息,表明会出现潜在错误的情形,一般不影响软件的正常使用 |
ERROR | 错误级别,打印错误异常信息,该级别的错误可能会导致系统的一些功能无法正常使用 |
CRITICAL | 严重错误级别,一个严重的错误,这表明系统可能无法继续运行 |
说明:
- 上面列表中的日志级别是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICA
- 当为程序指定一个日志级别后,程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记录指定级别的日志信息
- 一般建议只使用DEBUG、INFO、WARNING、ERROR这四个级别
Python中有一个标准库模块logging可以直接记录日志
import logging
logging.debug("这是一条调试信息")
logging.info("这是一条普通信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")
说明:
logging中默认的日志级别为WARNING,程序中大于等于该级别的日志才能输出
方法
logging.basicConfig(level=logging.DEBUG)
如何选择日志级别?
在开发环境和测试环境中,为了尽可能详细的查看程序的运行状态来保证上线后的稳定性,可以使用DEBUG或INFO级别的日志获取详细的日志信息,这是非常耗费机器性能的
在生产环境中,通常只记录程序的异常信息、错误信息等(设置成WARNING或ERROR级别),这样既可以减小服务器的I/O压力,也可以提高获取错误日志信息的效率和方便问题的排查
默认格式
日志级别:Logger名称:日志内容
自定义格式
logging.basicConfig(format="%(levelname)s:%(name)s:%(message)s")
format参数中可能用到的格式化信息:
占位符 | 描述 |
---|---|
%(name)s | Logger的名字 |
%(levelno)s | 数字形式的日志级别 |
%(levelname)s | 文本形式的日志级别 |
%(pathname)s | 调用日志输出函数的模块的完整路径名,可能没有 |
%(filename)s | 调用日志输出函数的模块的文件名 |
%(module)s | 调用日志输出函数的模块名 |
%(funcName)s | 调用日志输出函数的函数名 |
%(lineno)d | 调用日志输出函数的语句所在的代码行 |
%(created)f | 当前时间,用UNIX标准的表示时间的浮点数表示 |
%(relativeCreated)d | 输出日志信息时的,自Logger创建以来的毫秒数 |
%(asctime)s | 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896” |
%(thread)d | 线程ID。可能没有 |
%(threadName)s | 线程名。可能没有 |
%(process)d | 进程ID。可能没有 |
%(message)s | 用户输出的消息 |
示例代码
import logging
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
logging.basicConfig(level=logging.DEBUG, format=fmt)
logging.debug("这是一条调试信息")
logging.info("这是一条普通信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")
默认情况下Python的logging模块将日志打印到了标准输出中(控制台)
将日志信息输出到文件的方法
logging.basicConfig(filename="a.log")
示例代码
import logging
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
logging.basicConfig(level=logging.DEBUG, format=fmt, filename="a.log")
logging.debug("这是一条调试信息")
logging.info("这是一条普通信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")
思考:
- 如何将日志信息同时输出到控制台和日志文件中?
- 如何将不同级别的日志输出到不同的日志文件中?
- 如何解决日志文件过大的问题?
logging日志模块四大组件
组件名称 | 类名 | 功能描述 |
---|---|---|
日志器 | Logger | 提供了程序使用日志的入口 |
处理器 | Handler | 将logger创建的日志记录发送到合适的目的输出 |
格式器 | Formatter | 决定日志记录的最终输出格式 |
过滤器 | Filter | 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 |
logging模块就是通过这些组件来完成日志处理的
简单点说就是:
日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作
作用
如何创建Logger对象
logger = logging.getLogger()
logger = logging.getLogger("myLogger")
logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识
如果不提供该参数,则返回root日志器对象
若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用
Logger常用方法
方法 | 描述 |
---|---|
logger.debug() logger.info() logger.warning() logger.error() logger.critical() |
打印日志 |
logger.setLevel() | 设置日志器将会处理的日志消息的最低严重级别 |
logger.addHandler() | 为该logger对象添加一个handler对象 |
logger.addFilter() | 为该logger对象添加一个filter对象 |
作用
Handler对象的作用是将消息分发到handler指定的位置,比如:控制台、文件、网络、邮件等。 Logger对象可以通过addHandler()方法为自己添加多个handler对象
如何创建Handler对象
在程序中不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了Handler应该有的接口。 应该使用Handler实现类来创建对象,logging中内置的常用的Handler包括:
Handler | 描述 |
---|---|
logging.StreamHandler | 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象 |
logging.FileHandler | 将日志消息发送到磁盘文件,默认情况下文件大小会无限增 长 |
logging.handlers.RotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
logging.handlers.HTTPHandler | 将日志消息以GET或POST的方式发送给一个HTTP服务器 |
logging.handlers.SMTPHandler | 将日志消息发送给一个指定的email地址 |
Handler常用方法
方法 | 描述 |
---|---|
handler.setLevel() | 设置handler将会处理的日志消息的最低严重级别 |
handler.setFormatter() | 为handler设置一个格式器对象 |
handler.addFilter() | 为handler添加一个过滤器对象 |
作用
Formatter对象用于配置日志信息的格式
如何创建Formatter对象
formatter = logging.Formatter(fmt=None, datefmt=None, style='%')
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
说明: 可读性好的日志需具备一些共性
- 在控制台和文件都能输出
- 文件输出能够按时间切割
# 1.导包
# 2.创建日志器对象 / 设置日志级别
# 3.创建输出到控制台 / 文件(按时间切割)的处理器对象
# 4.创建格式化器
# 5.将格式化器添加到处理器
# 6.添加处理器到日志器
# 7.打印日志
# 1.导包
import logging.handlers
# 2.创建日志器对象 / 设置日志级别
logger = logging.getLogger("myLogger")
logger.setLevel(level=logging.DEBUG)
# 3.创建输出到控制台 / 文件(按时间切割)的处理器对象
ls = logging.StreamHandler()
lf = logging.handlers.TimedRotatingFileHandler(filename="test.log", when="s", backupCount=5)
# 4.创建格式化器
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt=fmt)
# 5.将格式化器添加到处理器
ls.setFormatter(formatter)
lf.setFormatter(formatter)
# 6.添加处理器到日志器
logger.addHandler(ls)
logger.addHandler(lf)
# 7.打印日志
#试验
while 1:
def test_log():
try:
assert 1==2
except Exception as e:
logger.debug(e)
test_log()
四个步骤
项目名称
TPshop开源商城系统
项目描述
TPshop是一个电子商务B2C电商平台系统,功能强大,安全便捷。适合企业及个人快速构建个性化网上商城
包含PC+IOS客户端+Adroid客户端+微商城,系统PC+后台是基于ThinkPHP MVC构架开发的跨平台开源软件,设计得非常灵活,具有模块化架构体系和丰富的功能,易于与第三方应用系统无缝集成,在设计上,包含相当全面,以模块化架构体系,让应用组合变得相当灵活,功能也相当丰富
项目架构
windows + php + apache + mysql
要求能运用画图表达
项目名称: webAutoTestTPshop
- base 包: 存放基类, 如 base_action.py, base_analyze.py
- config 包: 存放配置文件, 如 logging_config.py
- data 包: 存放测试数据, 如 login_data.json
- log 包/文件夹: 存放日志文件
- page 包: 存放页面类
- report 包/文件夹: 存放html测试报告
- screenshot 包/文件夹: 存放截图
- scripts 包: 存放测试脚本
- utils 包: 存放工具类, 如 utils.py
- pytest.ini 文件
封装驱动工具类
utils包下的 utils.py
封装基类
base包下的 base_action.py 和 base_analyze.py
登录页: login_page.py
首页: index_page.py
个人中心页: home_page.py
商品搜索页: goods_search_page.py
商品详情页: goods_detail_page.py
购物车页: cart_page.py
下订单页: order_page.py
订单支付页: order_pay_page.py
登录模块: test_login.py
购物车模块: test_cart.py
订单模块: test_order.py
定义存放测试文件的目录, 如 data
分模块定义数据文件
login_data.py
cart_data.py
order_data.py
根据业务编写用例数据
使用 @pytest.mark.parametrize() 完成参数化
使用 logging 模块完成日志收集
示例代码
# 1.导包
import logging
import logging.handlers
import os
# BASE_PATH = os.path.dirname(os.path.abspath("__file__"))
BASE_PATH = os.path.abspath(os.path.join(os.getcwd(), ".."))
def set_log_config():
# 2.创建日志器对象 / 设置日志级别
logger = logging.getLogger()
logger.setLevel(level=logging.INFO)
# 3.创建输出到控制台 / 文件(按时间切割)的处理器对象
ls = logging.StreamHandler()
lf = logging.handlers.TimedRotatingFileHandler(filename="./log/tpshop.log", when="d", backupCount=5)
# 4.创建格式化器
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt=fmt)
# 5.将格式化器添加到处理器
ls.setFormatter(formatter)
lf.setFormatter(formatter)
# 6.添加处理器到日志器
logger.addHandler(ls)
logger.addHandler(lf)
在 utils 包下
# 在 __init__.py 中增加
from config.logging_config import set_log_config
set_log_config()
|
| handler.addFilter() | 为handler添加一个过滤器对象 |
作用
Formatter对象用于配置日志信息的格式
如何创建Formatter对象
formatter = logging.Formatter(fmt=None, datefmt=None, style='%')
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
说明: 可读性好的日志需具备一些共性
- 在控制台和文件都能输出
- 文件输出能够按时间切割
# 1.导包
# 2.创建日志器对象 / 设置日志级别
# 3.创建输出到控制台 / 文件(按时间切割)的处理器对象
# 4.创建格式化器
# 5.将格式化器添加到处理器
# 6.添加处理器到日志器
# 7.打印日志
# 1.导包
import logging.handlers
# 2.创建日志器对象 / 设置日志级别
logger = logging.getLogger("myLogger")
logger.setLevel(level=logging.DEBUG)
# 3.创建输出到控制台 / 文件(按时间切割)的处理器对象
ls = logging.StreamHandler()
lf = logging.handlers.TimedRotatingFileHandler(filename="test.log", when="s", backupCount=5)
# 4.创建格式化器
fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
formatter = logging.Formatter(fmt=fmt)
# 5.将格式化器添加到处理器
ls.setFormatter(formatter)
lf.setFormatter(formatter)
# 6.添加处理器到日志器
logger.addHandler(ls)
logger.addHandler(lf)
# 7.打印日志
#试验
while 1:
def test_log():
try:
assert 1==2
except Exception as e:
logger.debug(e)
test_log()