首页 > 其他分享 >Web自动化

Web自动化

时间:2023-08-29 13:11:17浏览次数:34  
标签:Web self driver element 自动化 login find def

第1章-Web自动化入门

Web自动化测试

1、自动化

  • 概念:由机器设备代替人工自动完成指定目标的过程
  • 优点:减少人工劳动力;提高工作效率;产品规格统一标准;规模化(批量生产)

2、自动化测试

软件测试:校验系统是否满足规定的需求、弄清预期结果与实际结果之间的差别

  • 概念:让程序代替人工去验证系统功能的过程
  • 自动化能解决的:
    解决-回归测试(项目在发新版本之后对项目之前的功能进行验证)
    解决-压力测试(可理解为多用户同时操作软件,统计软件服务器处理多用户请求的能力)
    解决-兼容性测试(不同浏览器(IE、Firefox、Chrome等))
    提高测试效率,保证产品质量
  • 优点:
    较少的时间内运行更多的测试用例
    自动化脚本可重复运行
    减少人为的错误
    客服手工测试的局限性
  • 误区
    自动化测试可以完全替代手工测试
    自动化测试一定比手工测试厉害
    自动化测试可以发掘更多的BUG
    自动化测试适用于所有功能
  • 自动化测试分类
    web-自动化测试
    移动-自动化测试
    接口-自动化测试
    单元测试-自动化测试

3、Web自动化测试

  • 概念:让程序代替人工自动验证web项目功能的过程
  • 什么web项目适合做自动化测试
    需求变动不频繁
    项目周期长
    项目需要回归测试
  • Web自动化测试在什么阶段开始
    功能测试完毕(手工测试)
  • Web自动测试所属分类
    1、黑盒测试(功能测试)
    2、白盒测试(单元测试)
    3、灰盒测试(接口测试)
    Web自动化测试属于黑盒测试(功能测试)
    

4、主流的web自动化测试工具

```
1、QTP(QTP是一个商业化的功能测试工具,收费,支持web,桌面自动化测试。)
2、Selenium(Selenium是一个开源的web自动化测试工具,免费,主要做功能测试)
3、Robot framework(Robot Framework是一个基于Python可扩展地关键字驱动的测试自动化框架。)
```
  • Selenium是一个用于Web应用程序的自动化测试工具
  • Selenium特点
    1. 开源软件:源代码开放可以根据需要来增加工具的某些功能
    2. 跨平台:linux、windows、mac
    3. 支持多种浏览器:Firefox、Chrome、IE、Edge、Opera、Safari等
    4. 支持多种语言:Python、Java、C#、JavaScript、Ruby、PHP等
    5. 成熟稳定:目前已经被google、百度、腾讯等公司广泛使用
    6. 功能强大:能够实现类似商业工具的大部分功能,因为开源性,可实现定制化功能
    

5、环境搭建

  • 基于Python环境搭建
    1. Python 开发环境
    2. 安装selenium包
    3. 安装浏览器
    4. 安装浏览器驱动 -- 保证能够用程序驱动浏览器,实现自动化测试
  • 安装selenium包
    PIP工具(pip是一个通用的 Python 包管理工具,提供了对 Python 包的查找、下载、安装、卸载的功能。
    安装
    pip install selenium
    卸载
    pip uninstall selenium
    查看
    pip show selenium
    
  • 安装浏览器驱动
    火狐浏览器
    1. Firefox 48 以上版本
    selenium 3.x + Firefox驱动(geckodriver)
    驱动下载地址:https://github.com/mozilla/geckodriver/releases
    2. Firefox 48 以下版本
    selenium 2.x + 内置驱动
    谷歌浏览器
    selenium 2.x/3.x + Chrome驱动(chromedriver)
    驱动下载地址:https://sites.google.com/a/chromium.org/chromedriver/downloads
    
    安装浏览器驱动的步骤
    1. 下载浏览器驱动
    各个驱动下载地址: http://www.seleniumhq.org/download/
    浏览器的版本和驱动版本要一致!
    2. 把驱动文件所在目录添加到Path环境变量中
    或者直接放到Python安装目录,因为Python已添加到Path中
    

6、入门示例

  • 需求
    通过程序启动浏览器,并打开百度首页,暂停3秒,关闭浏览器
  • 实现步骤
    1.导包
    from selenium import webdriver
    import time
    2.创建浏览器驱动对象
    Firefox浏览器:driver=webdriver.Firefox()
    Chrome浏览器:driver=webdriver.Chrome()
    Edge浏览器:driver=webdriver.Edge()
    3.打开web页面
    driver.get("http://www.baidu.com/")
    4.暂停
    time.sleep(3)
    5.关闭驱动对象
    driver.quit()
    

第2章-Selenium-API操作

元素定位

1、如何进行元素定位

  • 元素定位就是通过元素的信息或元素层级结构来定位元素的。

2、浏览器开发者工具

  • 浏览器开发者工具就是给专业的web应用和网站开发人员使用的工具。 包含了对HTML查看和编辑、Javascript控制台、网络状况监视等功能,是开发JavaScript、CSS、HTML和Ajax的得力助手。
  • 作用:快速定位元素,查看元素信息

3、元素定位方式

Selenium提供了八种定位元素方式
1. id
2. name
3. class_name
4. tag_name
5. link_text
6. partial_link_text
7. XPath
8. CSS
3.1 id定位
  • id定位就是通过元素的id属性来定位元素,HTML规定id属性在整个HTML文档中必须是唯一的。
  • id定位方法
    element=driver.find_element_by_id(id)
    
  • 案例
    需求:打开注册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()
    
3.2 name定位
  • name定位就是根据元素name属性来定位。HTML文档中name的属性值是可以重复的
  • name定位方法
    element=driver.find_element_by_name(name)
    
3.3 class_name定位
  • class_name定位就是根据元素class属性值来定位元素。HTML通过使用class来定义元素的样式
  • class_name定位方法
    element=driver.find_element_by_class_name(class_name)
    
3.4 tag_name定位
  • tag_name定位就是通过标签名来定位
  • tag_name定位方法
    element=driver.find_element_by_tag_name(tag_name)
    如果存在多个相同标签,则返回符合条件的第一个标签
    
  • link_text定位是专门同来定位超链接元素(<a>标签</a>),并且是通过超链接的文本内容来定位元素。
  • link_text定位方法
    element=driver.find_element_by_link_text(link_text)
    link_text:为超链接的全部文本内容
    
  • partial_link_text定位是对link_text定位的补充,link_text使用全部文本内容匹配元素,而partial_link_text可以使用局部来匹配元素,也可以使用全部文本内容匹配元素
  • partial_link_text定位方法
    element=driver.find_element_by_partial_link_text(partial_link_text)
    partial_link_text:可以传入a标签局部文本-能表达唯一性
    

4、定位一组元素

  • find_elements_by_xxx()
    作用:
    1)查找定位所有符合条件的元素
    2)返回的定位元素格式为数组(列表)格式
    说明:
    列表数据格式的读取需要指定下标(下标从0开始)
    
  • 示例代码
    driver.find_elements_by_tag_name("input")[1].send_keys("123456")
    

5、XPath、CSS定位

  • 定位元素没有id、name、class属性,通过name、class、tag_name无法定位到唯一的元素问题
5.1 XPath
  • Xpath即为XML Path的简称,它是一门在XML(一种标记语言,用于数据的存储和传递)文档中查找元素信息的语言,HTML可以看做是XML的一种实现。
  • XPath定位方式
    1.路径-定位
    2.利用元素属性-定位
    3.属性与逻辑结合-定位
    4.层级与属性结合-定位
    
  • XPath定位方法
    element=driver.find_element_by_xpath(xpath)
    
  • 1)路径定位(绝对路径、相对路径)
    绝对路径:从最外层元素到指定元素之间所有经过元素层级的路径
    1). 绝对路径以/html根节点开始,使用/来分隔元素层级;
    如:/html/body/div/fieldset/p[1]/input
    2). 绝对路径对页面结构要求比较严格,不建议使用
    
    相对路径
    1)相对路径以//开始
    2)格式://input或者//*
    
  • 2)利用元素属性
    通过使用元素的属性信息来定位元素
    格式://input[@id='userA']或者//*[@id='userA']
    
  • 3)属性与逻辑结合
    解决元素之间个相同属性重名问题
    格式://*[@name='tel' and @class='tel']
    
  • 4)层级与属性结合
    如果通过元素自身的信息不方便直接定位到该元素,则可以先定位到其父级元素,然后再找到该元素
    格式://*[@id='p1']/input
    
  • XPath延伸
    //*[text()="xxx"]   文本内容是xxx的元素
    //*[contains(@attribute,'xxx')]   属性中含有xxx的元素
    //*[starts-with(@attribute,'xxx')]   属性以xxx开头的元素
    
5.2 CSS定位
  • 什么是CSS定位
    1. CSS(Cascading Style Sheets)是一种语言,它用来描述HTML元素的显示样式;
    2. 在CSS中,选择器是一种模式,用于选择需要添加样式的元素;
    3. 在Selenium中也可以使用这种选择器来定位元素。
    提示:
    1. 在selenium中推荐使用CSS定位,因为它比XPath定位速度要快
    2. css选择器语法非常强大,在这里我们只学习在测试中常用的几个
    
  • CSS定位方法
    element=driver.find_element_by_css_selector(css_selector)
    
  • CSS定位常用策略
    1)id选择器
    2)class选择器
    3)元素选择器
    4)属性选择器
    5)层级选择器
    
  • 1)id选择器
    根据元素id属性来选择
    格式:#id
    例如:#userA(选择id属性值为userA的元素)
    
  • 2)class选择器
    根据元素class属性来选择
    格式:.class
    例如:.telA(选择class属性值为telA的所有元素)
    
  • 3)元素选择器
    根据元素的标签名选择
    格式:element
    例如:input(选择所有input元素)
    
  • 4)属性选择器
    根据元素的属性名和值来选择
    格式:[attribute=value]   element[attribute=value]
    例如:[type="password"(选择type属性值为password的元素)]
    
  • 5)层级选择器
    根据元素的父子关系来选择
    格式1:element1>element2   通过element1来定位element2,并且element2必须为element1的直接子元素
    例如:p[id='p1']>input(定位指定p元素下的直接子元素input)
    格式2:element1 element2     通过element1来定位element2,并且element2为element1的后代元素
    例如:p[id='p1'] input(定位直接p元素下的后代元素input)
    
  • CSS延伸
    input[type^='p']   type属性以p字母开头的元素
    input[type$='d']   type属性以d字母结束的元素
    input[type*='w']   type属性包含w字母的元素
    

6、八种元素定位方式分类汇总

1. id、name、class_name:为元素属性定位
2. tag_name:为元素标签名称
3. link_text、partial_link_text:为超链接定位(a标签)
4. XPath:为元素路径定位
5. CSS:为CSS选择器定位
  • XPath与CSS类似功能对比
    image

7、定位元素的另一种写法-延伸

方法:find_element(by=By.ID, value=None)
备注:需要两个参数,第一个参数为定位的类型由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("18611111111")
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()
7.1 导入By类
导包:from selenium.webdriver.common.by import By
7.2 find_element_by_xxx()和find_element()区别
通过查看find_element_by_id底层实现方法,发现底层是调用find_element方法进行的封
装;
def find_element_by_id(self, id_):
"""Finds an element by id.
:Args:
- id\_ - The id of the element to be found.
:Usage:
driver.find_element_by_id('foo')
"""
return self.find_element(by=By.ID, value=id_)

元素操作、浏览器操作方法

1、元素操作

  • 为什么要学习操作元素方法
    1. 需要让脚本模拟用户给指定元素输入值
    2. 需要让脚本模拟人为删除元素的内容
    3. 需要让脚本模拟点击操作
  • 元素常用操作方法
    1. click()   单击元素
    2. send_keys(value)   模拟输入
    3. clear()   清除文本
    

2、浏览器操作

  • 通过调用Selenium的API来实现浏览器的操作
  • 操作浏览器常用方法
    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
    
  • 示例代码
    # 最大化浏览器
    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
    

3、获取元素信息

3.1 获取元素信息的常用方法
1. size   返回元素大小
2. text   获取元素的文本
3. get_attribute("xxx")   获取属性值,传递的参数为元素的属性名
4. is_displayed()   判断元素是否可见
5. is_enabled()   判断元素是否可用
6. is_selected()   判断元素是否选中,用来检查复选框或单选按钮是否被选中

注意:size、text为属性,调用时无括号

鼠标和键盘操作

1、鼠标操作

  • 鼠标操作的方法
    在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()   执行——》用此方法用来执行以上所有鼠标操作
    
  • 1)鼠标右键-context_click()
    1. 导包:from selenium.webdriver.common.action_chains import ActionChains
    2. 实例化ActionChains对象:action=ActionChains(driver)
    3. 调用右键方法:action.context_click(element)
    4. 执行:action.perform()
    
  • 2)鼠标双击-double_click()
  • 3)鼠标拖动-drag_and_drop()
    拖动关键点:
    1. 源元素   source=driver.find_element_by_id(xxx)
    2. 目标元素   target=driver.find_element_by_id(xxx)
    3. 调用方法   action.drag_and_drop(source,target).perform()
    
  • 4)鼠标悬停-move_to_element
    模拟鼠标悬停在指定的元素上

2、键盘操作

  • 模拟键盘上一些按键或组合键的输入,如:Ctrl+C、Ctrl+V;Selenium中把键盘的按键都封装在Keys类中
  • 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)
    
  • 示例代码
    # 定位用户名
    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')
    

元素等待

  • 概念:在定位页面元素时如果未找到,会在指定时间内一直等待的过程
  • 设置元素等待原因:网络速度慢、电脑配置低、服务器处理请求慢
  • 元素等待类型:隐式等待;显式等待

1、隐式等待

  • 概念:定位元素时,如果能定位到元素则直接返回该元素,不触发等待; 如果不能定位到该元素,则间隔一段时间后再去定位元素; 如果在达到最大时长时还没有找到指定元素,则抛出元素不存在的异常NoSuchElementException 。
  • 实现方式
    方法:driver.implicitly_wait(timeout)
    (timeout:为等待最大时长,单位:秒)
    注:隐式等待为全局设置(只需要设置一次,就会作用于所有元素)
    

2、显式等待

  • 概念:定位指定元素时,如果能定位到元素则直接返回该元素,不触发等待; 如果不能定位到该元素,则间隔一段时间后再去定位元素; 如果在达到最大时长时还没有找到指定元素,则抛出超时异常 TimeoutException 。
  • 在Selenium中把显式等待的相关方法封装在WebDriverWait类中
  • 实现方式
    1. 导包 等待类 ——》 from selenium.webdriver.support.wait import WebDriverWait
    2. WebDriverWait(driver,timeout,poll_frequency=0.5)
    	driver:浏览器驱动对象
    	timeout:超时的时长,单位:秒
    	poll_frequency:检测间隔时间,默认为0.5秒
    3. 调用方法 until(method):直到...时
    	method:函数名称,该函数用来实现对元素的定位
    	一般使用匿名函数来实现:lambda x: x.find_element_by_id("userA")
    4. element=WebDriverWait(driver,10,1).until(lambda x: x.find_element_by_id("userA"))
    
  • 示例代码
    from selenium import webdriver
    from selenium.webdriver.support.wait import WebDriverWait
    driver = webdriver.Firefox()
    driver.get("file:///D:/webAutoTest/page/注册A.html")
    element = WebDriverWait(driver, 10, 1).until(lambda x: x.find_element_by_id("userA"))
    element.send_keys("admin")
    time.sleep(3)
    driver.quit()
    
  • 显式与隐式区别
    1. 作用域:隐式为全局元素,显式等待为单个元素有效
    2. 使用方法:隐式等待直接通过驱动对象调用,而显式等待方法封装在WebDriverWait类中
    3. 达到最大超时时长后抛出的异常不同:隐式为NoSuchElementException,显式等待为TimeoutException
    

下拉选择框、弹出框、滚动条操作

1、下拉选择框操作

  • 下拉框就是HTML中<select>元素
  • 思路:先定位到要操作的option元素,然后执行点击操作
  • Select类
    Select类是Selenium为操作select标签特殊封装的
    实例化对象:
    	select=Select(element)
    		element:<select>标签对应的元素,通过元素定位方式获取
    
    操作方法:
    	1. select_by_index(index)	——》根据option索引来定位,从0开始
    	2. select_by_value(value)	_>根据option属性 value值来定位
    	3. select_by_visible_text(text)	——》根据option显示文本来定位
    
  • 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)
    
  • 示例代码
    from selenium.webdriver.support.select import Select
    select=Select(driver.find_element_by_id("selectA"))
    select.select_by_index(2) #根据索引实现
    select.select_by_value("sh") #根据value属性实现
    select.select_by_visible_text("A北京") #根据文本内容实现
    

2、弹出框处理

  • 网页中常用的弹出框有三种:警告框alert、确认框confirm、提示框prompt
  • 弹出框处理方法
    Selenium中对处理弹出框的操作,有专用的处理方法;并且处理的方法都一样
    1.获取弹出框对象
    	alert=driver.switch_to.alert
    2.调用
    	alert.text   返回alert/confirm/prompt中的文字信息
    	alert.accept()   接受对话框选项
    	alert.dismiss()   取消对话框选项
    
  • 示例代码
    # 定位alerta按钮
    driver.find_element_by_id("alerta").click()
    # 获取警告框
    alert = driver.switch_to.alert
    # 打印警告框文本
    print(alert.text)
    # 接受警告框
    alert.accept()
    # 取消警告框
    # alert.dismiss()
    

3、滚动条操作

  • 实现方式
    说明:selenium中并没有直接提供操作滚动条的方法,但是它提供了可执行JavaScript脚本的方法,所以我们可以通过JavaScript脚本来达到操作滚动条的目的。
    1.设置JavaScript脚本控制滚动条
    js="window.scrollTo(0,1000)"
    	(0:左边距;1000:上边距;单位像素)
    2.selenium调用执行JavaScript脚本的方法
    driver.execute_script(js)
    
  • 示例代码
    # 最底层
    js1 = "window.scrollTo(0,10000)"
    driver.execute_script(js1)
    # 最顶层
    js2 = "window.scrollTo(0,0)"
    driver.execute_script(js2)
    

frame切换、多窗口切换

1、frame切换

frame:HTML页面中的一种框架,主要作用是在当前页面中指定区域显示另一页面元素;
形式一:[了解]
<frameset cols="25%,75%">
<frame src="frame_a.htm">
<frame src="frame_b.htm">
</frameset>
形式二:
<iframe name="iframe_a" src="demo_iframe.htm" width="200" height="200"></iframe>
  • frame切换方法
    说明:在Selenium中封装了如何切换frame框架的方法
    方法:
    1)driver.switch_to.frame(frame_reference)   切换到指定frame的方法
    	frame_reference:可以为frame框架的name、id或者定位到的frame元素
    2)driver.switch_to.default_content()   恢复默认页面方法
    在frame中操作其他页面,必须先回到默认页面,才能进一步操作
    

2、多窗口切换

  • 说明:在HTML页面中,当点击超链接或者按钮时,有的会在新的窗口打开页面。
  • 需求:打开‘注册实例.html’页面,完成以下操作
    1). 点击‘注册A页面’链接
    2). 在打开的页面中,填写注册信息
    问题:1). 无法定位注册A页面中的元素
  • 如何实现多窗口切换
    说明:在Selenium中封装了获取当前窗口句柄、获取所有窗口句柄和切换到指定句柄窗口的方法;
    句柄:英文handle,窗口的唯一识别码
    方法:
    1)driver.current_window_handle    获取当前窗口句柄
    2)driver.window_handles   获取所有窗口句柄
    3)driver.switch_to.window(handle)   切换到指定句柄窗口
    

窗口截图、验证码处理

1、窗口截图

  • 把当前操作的页面,截图保存到指定位置
  • 自动化脚本是由程序去执行的,因此有时候打印的错误信息并不是十分明确。如果在执行出错的时候对当前窗口截图保存,那么通过图片就可以非常直观地看到出错的原因。
  • 窗口截图的方法
    说明:在Selenium中,提供了截图方法,我们只需要调用即可
    方法:
    	driver.get_screenshot_as_file(imgpath)
    		imgpath:图片保存路径
    
  • 示例代码
    driver.find_element_by_id("userA").send_keys("admin")
    driver.get_screenshot_as_file("./img/img01.jpg")
    

2、验证码

  • 说明:一种随机生成的信息(数字、字母、汉字、图片、算术题)等为了防止恶意的请求行为,增加应用的安全性。
  • 验证码的处理方式
    说明:Selenium中并没有对验证码处理的方法,针对验证码的几种常用处理方式有
    方式:
    1)去掉验证码(测试环境下采用)
    2)设置万能验证码(生产环境和测试环境下采用)
    3)验证码识别技术(通过Python-tesseract来识别图片类型验证码,识别率很难达到100%)
    4)记录cookie(通过记录cookie进行跳过登录)
    

3、cookie

3.1 cookie概念

image

1. Cookie是由Web服务器生成的,并且保存在用户浏览器上的小文本文件,它可以包含用户相关的信息。
2. Cookie数据格式:键值对组成(python中的字典)
3. Cookie产生:客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个Cookie数据
4. Cookie使用:当浏览器再次请求该网站时,浏览器把请求的数据和Cookie数据一同提交给服务器,服务器检查该Cookie,以此来辨认用户状态。
3.2 cookie的应用场景
  1. 实现会话跟踪,记录用户登录状态
  2. 实现记住密码和自动登录的功能
  3. 用户未登录的状态下,记录购物车中的商品

4、Selenium操作cookie

说明:Selenium中对cookie操作提供相应的方法
方法:
	1. get_cookie(name)   获取指定cookie,name:为cookie的名称
	2. get_cookies()   获取本网站所有本地cookies
	3. add_cookie(cookie_dict)   添加cookie,cookie_dict:一个字典对象,必选的键包括:"name" and "value"
  • 示例代码
    from selenium import webdriver
    import time
    driver = webdriver.Firefox()
    driver.get("https://www.baidu.com")
    driver.add_cookie({'name':'BDUSS','value':'根据实际填写'})
    time.sleep(3)
    driver.refresh()
    time.sleep(3)
    driver.quit()
    

第3章-UnitTest

UnitTest基本使用

1、UnitTest框架

  • 概念:UnitTest是Python自带的一个单元测试框架,用它来做单元测试。
  • 能够组织多个用例去执行;提供丰富的断言方法;能够生成测试报告。
  • UnitTest核心要素
    1. TestCase
    2. TestSuite
    3. TestRunner
    4. TestLoader
    5. Fixture

2、TestCase

  • TestCase就是测试用例
  • 定义测试用例
    1. 导包:import unittest
    2. 定义测试类:新建测试类必须集成unittest.TestCase
    3. 定义测试方法:测试方法名称命名必须以test开头
    
  • 执行测试用例
    方式一:使用pycharm在代码上点击鼠标右键,选择使用UnitTest运行
    方式二:调用 unittest.main() 来运行

3、TestSuite

说明:(翻译:测试套件)多条测试用例集合在一起,就是一个TestSuite
使用:
	1.实例化:suite=unittest.TestSuite()
		suite:为TestSuite实例化的名称
	2.添加用例:suite.addTest(ClassName("MethodName"))
		ClassName:为类名;MethodName:为方法名
	3.添加扩展:suite.addTest(unittest.makeSuite(ClassName))
		搜索指定ClassName内test开头的方法并添加到测试套件中
	
	注意:TestSuite需要配合TestRunner才能被执行

4、TextTestRunner

说明:TextTestRunner是用来执行测试用例和测试套件的
使用:
	1.实例化:runner=unittest.TextTestRunner()
	2.执行:runner.run(suite) #suite:为测试套件名称

5、TestLoader

说明:
用来加载TestCase到TestSuite中,即加载满足条件的测试用例,并把测试用例封装成测试套件。
使用unittest.TestLoader,通过该类下面的discover()方法自动搜索指定目录下指定开头的.py文件,并将查找到的测试用例组装到测试套件;
用法:
	suite=unittest.TestLoader().discover(test_dir,pattern='test*.py')
		自动搜索指定目录下指定开头的.py文件,并将查找到的测试用例组装到测试套件
		test_dir: 为指定的测试用例的目录
		pattern:为查找的.py文件的格式,默认为'test*.py'
	也可以使用unittest.defaultTestLoader 代替unittest.TestLoader()
运行:
	runner=unittest.TestRunner()
	runner.run(suite)
  • TestLoader与TestSuite区别
    1. TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中某个测试方法)
    2. TestLoader搜索指定目录下指定开头.py文件,并添加测试类中的所有的测试方法,不能指定添加测试方法;

Fixture

1、Fixture

说明:Fixture是一个概述,对一个测试用例环境的初始化和销毁就是一个Fixture
Fixture控制级别:
1. 方法级别
2. 类级别
3. 模块级别
1.1 方法级别
使用:
	1.初始化(前置处理):
		def setUp(self)   首先自动执行
	2.销毁(后置处理):
		def tearDown(self)   最后自动执行
	3.运行于测试方法的始末,即:运行一次测试方法就会运行一次setUp和tearDown
1.2 类级别
使用:
	1.初始化(前置处理):
		@classmethod
		def setUpClass(cls):    首先自动执行
	2.销毁(后置处理):
		@classmethod
		def tearDownClass(cls):   最后自动执行
	3. 运行于测试类的始末,即:每个测试类只会运行一次setUpClass和tearDownClass
1.3 模块级别
使用:
	1. 初始化(前置处理):
		def setUpModule() --> 首先自动执行
	2. 销毁(后置处理):
	def tearDownModule() --> 最后自动执行
	3. 运行于整个模块的始末,即:整个模块只会运行一次setUpModule和tearDownModule
示例代码
import time
import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://localhost")
self.driver.implicitly_wait(10)
self.driver.maximize_window()
def test_login(self):
# 点击登录按钮
self.driver.find_element_by_link_text("登录").click()
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 点击登录按钮
self.driver.find_element_by_css_selector("[name='sbtbutton']").click()
# 获取错误提示信息
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
print("msg=", msg)
def tearDown(self):
time.sleep(3)
self.driver.quit()
1. 必须继承unittest.TestCase类,setUp、tearDown才是一个Fixture
2. setUp:一般做初始化工作,比如:实例化浏览器驱动对象、浏览器最大化、设置隐式等待等
3. tearDown:一般做结束工作,比如:关闭浏览器驱动对象、退出登录等

断言

1、UnitTest断言

  • 断言概念:让程序代替人为判断测试程序执行结果是否符合预期结果的过程

2、常用的UnitTest断言方法

image
image

  • 使用方式
    断言方法已经在unittest.TestCase类中定义好了,而且我们自定义的测试类已经继承了TestCase,所以在测试方法中直接调用即可。
    import unittest
    def add(x,y):
    	return x+y
    class TestAssert(unittest.TestCase):
    	def test01(self):
    		num=add(1,2)
    		self.assertEqual(3,num)
    	def test02(self):
    		num=add(1,2)
    		is_ok=num==3
    		self.assertTrue(is_ok)
    

3、案例

需求:使用UnitTest框架对tpshop项目测试
1). 点击登录,进入登录页面
2). 输入用户名和密码,不输入验证码,直接点击登录按钮
3). 获取错误提示信息
4). 断言错误提示信息是否为“验证码不能为空!”,如果断言失败则保存截图
扩展:
1. 图片名称为动态-时间

案例代码:

import time
import unittest
from selenium import webdriver

class TestLogin(unittest.TestCase):
	def setUp(self):
		self.driver=webdriver.Firefox()
		self.driver.get("http://localhost")
		slef.driver.implicitly_wait(10)
		self.driver.maximize_window()
	
	def test_login(self):
		self.driver.find_element_by_link_text("登录").click()
		self.driver.find_element_by_id("username").send_keys("13012345678")
		self.driver.find_element_by_id("password").send_keys("123456")
		self.driver.find_element_by_css_selector("[name='sbtbutton']").click()
		msg=self.driver.find_element_by_css_selector(".layui-layer-content").text
		print("msg=",msg)
		try:
			self.asseertIn("验证码不能为空",msg)
		except AssertionError as e:
			img_path="./imgs/img{}.png".format(time.strftime("%Y%m%d-%H%M%S"))
			self.driver.get_screenshot_as_file(img_path)
			raise e
	def tearDown(self):
		self.driver.quit()
if __name__=='__main__':
	unittest.main()

参数化

  • 问题:1. 一条测试数据定义一个测试函数,代码冗余度太高
  1. 一个测试函数中测试多条数据,最终只会有一个测试结果
  • 参数化:通过参数的方式来传递数据,从而实现数据和脚本分离。并且可以实现用例的重复执行。unittest测试框架,本身不支持参数化,但是可以通过安装unittest扩展插件parameterized来实现。
  • 安装
    pip install parameterized
    
  • 使用方式
    导包:from parameterized import parameterized
    使用@parameterized.expand装饰器可以为测试函数的参数进行参数化
    #方式一
    @parameterized.expand([(1,1,2),(1,0,1),(0,0,0)])
    def test_add(self,x,y,except):
    	pass
    #方式二
    data=[(1,1,2),(1,0,1),(0,0,0)]
    @parameterized.expand(data)
    def test_add(self,x,y,except):
    	pass
    #方式三
    def build_data():
    	return [(1,1,2),(1,0,1),(0,0,0)]
    @parameterized.expand(build_data)
    def test_add(self,x,y,except):
    	pass
    
    示例代码
    import unittest
    from parameterized import parameterized
    # 求和
    def add(x, y):
    	return x + y
    # 构建测试数据
    def build_data():
    	return [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
    class TestAdd(unittest.TestCase):
    	@parameterized.expand([(1, 1, 2), (1, 0, 1), (0, 0, 0)])
    	def test_add_1(self, x, y, expect):
    		print("x={} y={} expect={}".format(x, y, expect))
    		result = add(x, y)
    		self.assertEqual(result, expect)
    	data = [(1, 1, 2), (1, 0, 1), (0, 0, 0)]
    	@parameterized.expand(data)
    	def test_add_2(self, x, y, expect):
    		print("x={} y={} expect={}".format(x, y, expect))
    		result = add(x, y)
    		self.assertEqual(result, expect)
    	@parameterized.expand(build_data)
    	def test_add_3(self, x, y, expect):
    		print("x={} y={} expect={}".format(x, y, expect))
    		result = add(x, y)
    		self.assertEqual(result, expect)
    

跳过

  • 对于一些未完成的或者不满足测试条件的测试函数和测试类,可以跳过执行
  • 使用方式
    #直接将测试函数标记成跳过
    @unittest.skip('代码为完成')
    #根据条件判断测试函数是否跳过
    @unittest.skipIf(condition,reason)
    
  • 示例代码
    import unittest
    version = 35
    class TestSkip(unittest.TestCase):
    	@unittest.skip("代码未完成")
    	def test_01(self):
    		print("test_01")
    	@unittest.skipIf(version <= 30, "版本大于30才会执行")
    	def test_02(self):
    		print("test_02")
    @unittest.skip("代码未完成")
    class TestSkip2(unittest.TestCase):
    	def test_a(self):
    		print("test_a")
    	def test_b(self):
    		print("test_b")
    

生成HTML测试报告

  • 说明:HTML测试报告就是执行完测试用例后,以HTML(网页)方式将执行结果生成报告
  • HTML生成报告方式
    1. Export Test Results (UnitTest 自带)
    2. HTMLTestRunner(第三方模板)【重点】

1、Export Test Results

  • 测试报告截图
    image
  • 自带报告生成操作图
    image

2、HTMLTestRunner【重点】

  • 测试报告截图
    image
  • 测试报告生成步骤分析
1. 复制HTMLTestRunner.py文件到项目文件夹
2. 导入HTMLTestRunner、unittest包
3. 生成测试套件
suite = unittest.TestSuite()
suite.addTest(TestLogin("test_login"))
suite = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")
4. 设置报告生成路径和文件名
file_name = "./report/report.html"
5. 打开报告 with open(file_name,'wb') as f:
6. 实例化HTMLTestRunner对象:
runner = HTMLTestRunner(stream=f,[title],[description])
参数说明:
stream:文件流,打开写入报告的名称及写入编码格式)
title:[可选参数],为报告标题,如XXX自动化测试报告
description:[可选参数],为报告描述信息;比如操作系统、浏览器等版本
7. 执行:runner.run(suite)
  • 实现代码
import time
import unittest
from day05.tools.HTMLTestRunner import HTMLTestRunner
# 加载指定目录下的测试用例文件
suite = unittest.defaultTestLoader.discover("./case/", "test*.py")
# 报告文件存放路径
report_path = "./report/report{}.html".format(time.strftime("%Y%m%d%H%M%S"))
with open(report_path, "wb") as f:
	# 实例化HTMLTestRunner对象,传入报告文件流f
	runner = HTMLTestRunner(stream=f, title="自动化测试报告", description="FireFox浏览器")
	runner.run(suite)

第4章-PO模式

  • PO模式学习思路
采用版本迭代的方式来学习,便于对不同版本的优缺点进行对比和理解。
V1:不使用任何设计模式和单元测试框架
V2:使用UnitTest管理用例
V3:使用方法封装的思想,对代码进行优化
V4:采用PO模式的分层思想对代码进行拆分
V5:对PO分层之后的代码继续优化
V6:PO模式深入封装,把共同操作提取封装到父类中,子类直接调用父类的方法

无模式

1、案例说明

登录模块代表性用例
账号不存在
1. 点击首页的‘登录’链接,进入登录页面
2. 输入一个不存在的用户名
3. 输入密码
4. 输入验证码
5. 点击登录按钮
6. 获取错误提示信息
密码错误
1. 点击首页的‘登录’链接,进入登录页面
2. 输入用户名
3. 输入一个错误的密码
4. 输入验证码
5. 点击登录按钮
6. 获取错误提示信息

2、V1版本

  • 不使用任何设计模式和单元测试框架。
    每个文件里编写一个用例,完全的面向过程的编程方式。
  • 存在的问题:
    一条测试用例对应一个文件,用例较多时不方便管理维护
    代码高度冗余
  • 示例代码
登录功能-账号不存在
from selenium import webdriver
# 创建浏览器驱动对象,并完成初始化操作
driver = webdriver.Firefox()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost")
"""
登录功能-账号不存在
"""
# 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_link_text("登录").click()
# 输入用户名
driver.find_element_by_id("username").send_keys("13099999999")
# 输入密码
driver.find_element_by_id("password").send_keys("123456")
# 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’按钮
driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
msg = driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
# 关闭驱动对象
driver.quit()

登录功能-密码错误
from selenium import webdriver
# 创建浏览器驱动对象,并完成初始化操作
driver = webdriver.Firefox()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://localhost")
"""
登录功能-密码错误
"""
# 点击首页的‘登录’链接,进入登录页面
driver.find_element_by_link_text("登录").click()
# 输入用户名
driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
driver.find_element_by_id("password").send_keys("error")
# 输入验证码
driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’按钮
driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
msg = driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
# 关闭驱动对象
driver.quit()

3、V2版本

  • 使用UnitTest管理用例,并断言用例的执行结果
  • 引入UnitTest的好处
    方便组织、管理多个测试用例
    提供了丰富的断言方法
    方便生成测试报告
    减少了代码冗余
  • 存在的问题
    代码冗余
  • 示例代码
import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
"""
对登录模块的功能进行测试
"""
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Firefox()
cls.driver.maximize_window()
cls.driver.implicitly_wait(10)
cls.driver.get("http://localhost")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
def setUp(self):
# 打开首页
self.driver.get("http://localhost")
# 点击首页的‘登录’链接,进入登录页面
self.driver.find_element_by_link_text("登录").click()
# 账号不存在
def test_login_username_is_error(self):
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13099999999")
# 输入密码
self.driver.find_element_by_id("password").send_keys("123456")
# 输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’
self.driver.find_element_by_name("sbtbutton").click()
# 断言提示信息
msg = self.driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
self.assertIn("账号不存在", msg)
# 密码错误
def test_login_password_is_error(self):
# 输入用户名
self.driver.find_element_by_id("username").send_keys("13012345678")
# 输入密码
self.driver.find_element_by_id("password").send_keys("error")
# 输入验证码
self.driver.find_element_by_id("verify_code").send_keys("8888")
# 点击‘登录’
self.driver.find_element_by_name("sbtbutton").click()
# 断言提示信息
msg = self.driver.find_element_by_class_name("layui-layer-content").text
print("msg=", msg)
self.assertIn("密码错误", msg)

方法封装

  • 方法封装:是将一些有共性的或多次被使用的代码提取到一个方法中,供其他地方调用。
    封装的好处:
    避免代码冗余
    容易维护
    隐藏代码实现的细节
    目的:用最少的代码实现最多的功能

V3版本

  • 定义获取驱动对象的工具类
对登录流程的代码进行优化,定义获取驱动对象的工具类
# utils.py
class DriverUtil:
"""
浏览器驱动工具类
"""
_driver = None
@classmethod
def get_driver(cls):
"""
获取浏览器驱动对象,并完成初始化设置
:return: 浏览器驱动对象
"""
if cls._driver is None:
cls._driver = webdriver.Firefox()
cls._driver.maximize_window()
cls._driver.implicitly_wait(10)
cls._driver.get("http://localhost")
return cls._driver
@classmethod
def quit_driver(cls):
"""
关闭浏览器驱动
"""
if cls._driver:
cls._driver.quit()
cls._driver = None
  • 封装“获取弹出框的提示消息”
对登录流程的代码进行优化,封装‘获取弹出框的提示消息’的方法
# utils.py
def get_tips_msg():
"""
获取弹出框的提示消息
:return: 消息文本内容
"""
msg = DriverUtil.get_driver().find_element_by_class_name("layui-layer-content").text
return msg

PO模式介绍

  • 存在的问题
在做UI自动化时定位元素特别依赖页面,一旦页面发生变更就不得不跟着去修改定位元素的代码。
举例:假设要对一个元素进行点击操作,而且会经常对该元素进行操作,那么你就可能会编写多处如下代码
driver.find_element_by_id("login-btn").click()
存在的问题:
如果开发人员修改了这个元素的id,这时候你就不得不修改所有对应的代码
存在大量冗余代码
  • PO模式
PO是Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一。
核心思想是通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化, 只
需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。
PO模式可以把一个页面分为三层,对象库层、操作层、业务层。
对象库层:封装定位元素的方法。
操作层:封装对元素的操作。
业务层:将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入帐号、密码、点击登录三个操作。
  • 引入PO模式的好处
引入PO模式前
存在大量冗余代码
业务流程不清晰
后期维护成本大
引入PO模式后
减少冗余代码
业务代码和测试代码被分开,降低耦合性
维护成本低

PO模式实践

1、V4版本

  • PO分层封装
对登录页面进行分层封装:
	对象库层:LoginPage
	操作层:LoginHandle
	业务层:LoginProxy
调用业务层的方法,编写测试用例:
	测试用例:TestLogin
  • 示例代码
from po.utils import DriverUtil
class LoginPage:
	"""对象库层"""
	def __init__(self):
		self.driveer=DriverUtil.get_driver()
		#用户名输入框
		self.username=None
		self.password=None
		self.verify_code=None
		self.login_btn=None
		self.forget_pwd=None
	def find_username(self):
		return self.driver.find_element_by_id("username")
	def find_password(self):
		return self.driver.find_element_by_id("password")
	def find_verify_code(self):
		return self.driver.find_element_by_id("verify_code")
	def find_login_btn(self):
		return self.driver.find_element_by_name("sbtbutton")
	def find_forget_pwd(self):
		return self.driver.find_element_by_partial_link_text("忘记密码")


class LoginHandle:
	"""操作层"""
	def __init__(self):
		self.login_page=LoginPage()
	def input_username(self,username):
		self.login_page.find_username().send_keys(username)
	def input_password(self,pwd):
		self.login_page.find_password().send_keys(pwd)
	def input_verify_code(self,code):
		self.login_page.find_varify_code().send_keys(code)
	def click_login_btn(self):
		self.login_page.find_login_btn().click()
	def click_forget_pwd(self):
		self.login_page.find_forget_pwd().click()


class LoginProxy:
	"""业务层"""
	def __init__(self):
		self.login_handle=LoginHandle()
	#登录
	def login(self,username,password,verify_code):
		self.login_handle.input_username(username)
		self.login_handle.input_password(password)
		self.login_handle.input_verify_code(verify_code)
		self.login_handle.click_login_btn()

	#跳转到忘记密码页面
	def to_forget_pwd_page(self):
		self.login_handle.click_forget_pwd()
import unittest

from po import utils
from po.utils import DriverUtil
from po.v4.page.login_page import LoginProxy

class TestLogin(unittest.TestCase):
	"""对登录模块的功能进行测试"""
	@classmethod
	def setUpClass(cls):
		cls.driver=DriverUtil.get_driver()
		cls.login_proxy=Login_proxy()
	@classmethod
	def tearDownClass(cls):
		DriverUtil.quit_driver()
	def setUp(self):
		self.driver.get("http://localhost")
		self.driver.find_element_by_link_text("登录").click()
	def test_login_username_is_error(self):
		self.login_proxy.login("13099999999","123456", "8888")
		msg=utils.get_tips_msg()
		print("msg=",msg)
		self.seertIn("账号不存在",msg)
	def test_login_password_is_error(self):
		self.login_proxy.login("13012345678","123456", "8888")
		msg=utils.get_tips_msg()
		print("msg=",msg)
		self.assertIn("密码错误",msg)

2、V5版本

  • 对PO分层之后的代码继续优化
  1. 优化对象库层的代码,抽取元素的定位方式,把定位信息定义在对象的属性中,便于集中管理
  2. 优化操作层的代码,针对输入操作应该先清空输入框中的内容再输入新的内容
from selenium.webdriver.common.by import By
from po.utils import DriverUtil
class LoginPage:
"""
对象库层
"""
def __init__(self):
self.driver = DriverUtil.get_driver()
# 用户名
self.username = (By.ID, "username")
# 密码
self.password = (By.ID, "password")
# 验证码输入框
self.verify_code = (By.ID, "verify_code")
# 登录按钮
self.login_btn = (By.NAME, "sbtbutton")
# 忘记密码
self.forget_pwd = (By.PARTIAL_LINK_TEXT, "忘记密码")
def find_username(self):
return self.driver.find_element(self.username[0], self.username[1])
def find_password(self):
return self.driver.find_element(self.password[0], self.password[1])
def find_verify_code(self):
return self.driver.find_element(self.verify_code[0], self.verify_code[1])
def find_login_btn(self):
return self.driver.find_element(self.login_btn[0], self.login_btn[1])
def find_forget_pwd(self):
return self.driver.find_element(self.forget_pwd[0], self.forget_pwd[1])
class LoginHandle:
"""
操作层
"""
def __init__(self):
self.login_page = LoginPage()
def input_username(self, username):
self.login_page.find_username().clear()
self.login_page.find_username().send_keys(username)
def input_password(self, pwd):
self.login_page.find_password().clear()
self.login_page.find_password().send_keys(pwd)
def input_verify_code(self, code):
self.login_page.find_verify_code().clear()
self.login_page.find_verify_code().send_keys(code)
def click_login_btn(self):
self.login_page.find_login_btn().click()
def click_forget_pwd(self):
self.login_page.find_forget_pwd().click()
class LoginProxy:
"""
业务层
"""
def __init__(self):
self.login_handle = LoginHandle()
# 登录
def login(self, username, password, verify_code):
# 输入用户名
self.login_handle.input_username(username)
# 输入密码
self.login_handle.input_password(password)
# 输入验证码
self.login_handle.input_verify_code(verify_code)
# 点击登录按钮
self.login_handle.click_login_btn()
# 跳转到忘记密码页面
def to_forget_pwd_page(self):
# 点击忘记密码
self.login_handle.click_forget_pwd()

PO模式深入封装

1、V6版本

  • 把共同操作提取封装到父类中,子类直接调用父类的方法,避免代码冗余
  1. 对象库层-基类,把定位元素的方法定义在基类中
  2. 操作层-基类,把对元素执行输入操作的方法定义在基类中
  • 示例代码
#base_page.py

from po.utils import DriverUtil
class BasePage:
	"""基类-对象库层"""
	def __init__(self):
		self.driver=DriverUtil.get_driver()
	def find_element(self,location):
		return self.driver.find_element(location[0],location[1])

class BaseHandle:
	"""基类-操作层"""
	def input_text(self,element,text):
		element.clear()
		element.send_keys(text)
from selenium.webdriver.common.by import By
from po.v6.common.base_page import BasePage, BaseHandle
class LoginPage(BasePage):
"""
对象库层
"""
def __init__(self):
super().__init__()
# 用户名输入框
self.username = (By.ID, "username")
# 密码
self.password = (By.ID, "password")
# 验证码
self.verify_code = (By.ID, "verify_code")
# 登录按钮
self.login_btn = (By.NAME, "sbtbutton")
# 忘记密码
self.forget_pwd = (By.PARTIAL_LINK_TEXT, "忘记密码")
def find_username(self):
return self.find_element(self.username)
def find_password(self):
return self.find_element(self.password)
def find_verify_code(self):
return self.find_element(self.verify_code)
def find_login_btn(self):
return self.find_element(self.login_btn)
def find_forget_pwd(self):
return self.find_element(self.forget_pwd)
class LoginHandle(BaseHandle):
"""
操作层
"""
def __init__(self):
self.login_page = LoginPage()
def input_username(self, username):
self.input_text(self.login_page.find_username(), username)
def input_password(self, pwd):
self.input_text(self.login_page.find_password(), pwd)
def input_verify_code(self, code):
self.input_text(self.login_page.find_verify_code(), code)
def click_login_btn(self):
self.login_page.find_login_btn().click()
def click_forget_pwd(self):
self.login_page.find_forget_pwd().click()
class LoginProxy:
"""
业务层
"""
def __init__(self):
self.login_handle = LoginHandle()
# 登录
def login(self, username, password, verify_code):
# 输入用户名
self.login_handle.input_username(username)
# 输入密码
self.login_handle.input_password(password)
# 输入验证码
self.login_handle.input_verify_code(verify_code)
# 点击登录按钮
self.login_handle.click_login_btn()
# 跳转到忘记密码页面
def to_forget_pwd_page(self):
# 点击忘记密码
self.login_handle.click_forget_pwd()

第5章-数据驱动

数据驱动介绍

  • 概念:数据驱动:是以数据来驱动整个测试用例的执行,也就是测试数据决定测试结果。
    比如我们要测试加法,我们的测试数据是1和1,测试结果就是2,如果测试数据是1和2,测试结果就是3。
  • 特点
    数据驱动本身不是一个工业级标准的概念,因此在不同的公司都会有不同的解释。
    可以把数据驱动理解为一种模式或者一种思想。
    数据驱动技术可以将用户把关注点放在对测试数据的构建和维护上,而不是直接维护脚本,可以利用同样的过程对不同的数据输入进行测试。
    数据驱动的实现要依赖参数化的技术。
  • 传入数据的方式(测试数据的来源)
    直接定义在测试脚本中(简单直观,但代码和数据未实现真正的分离,不方便后期维护)
    从文件读取数据,如JSON、excel、xml、txt等格式文件
    从数据库中读取数据
    直接调用接口获取数据源
    本地封装一些生成数据的方法

JSON操作

1、JSON介绍

  • JSON的全称是”JavaScript Object Notation”,是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式。
  • JSON特点
    • JSON是纯文本
    • JSON具有良好的自我描述性,便于阅读和编写
    • JSON具有清晰的层级结构
    • 有效地提升网络传输效率
      image
  • JSON语法规则
    • 大括号保存对象
    • 中括号保存数组
    • 对象数组可以相互嵌套
    • 数据采用键值对表示
    • 多个数据由逗号分隔
  • JSON值
  • JSON值可以是:
    数字(整数或浮点数)
    字符串(在双引号中)
    逻辑值(true 或 false)
    数组(在中括号中)
    对象(在大括号中)
    null

示例:

{
"name": "tom",
"age": 18,
"isMan": true,
"school": null,
"address": {
"country": "中国",
"city": "江苏苏州",
"street": "科技园路"
},
"numbers": [2, 6, 8, 9],
"links": [
{
"name": "Baidu",
"url": "http://www.baidu.com"
},
{
"name": "TaoBao",
"url": "http://www.taobao.com"
}
]
}

2、数据操作

python字典与JSON之间的转换
JSON文件读写
2.1 导入依赖包
import json
2.2 Python字典与JSON之间的转换
把Python字典类型转换为JSON字符串
data={
	'id':1,
	'name':'Tom',
	'address':'北京市海定区'
	'school':None
}
json_str=json.dumps(data)
把JSON字符串转换为Python字典
json_str='{"id":1,"name":"Tom","address":"北京市海定区","school":null}'
dict_data=json.loads(json_str)
2.3 JSON文件读写
读取JSON文件
with open('data.json',encoding='UTF-8') as f:
	data=json.load(f)  #返回的数据类型为字典或列表
写入JSON文件
param={'name':'tom','age':20}
with open('data2.json','w',encodig='UTF-8') as f:
	json.dump(param,f)

数据驱动实战一

1、案例

  • 对网页计算器,进行加法的测试操作。通过读取数据文件中的数据来执行用例。网址:http://cal.apple886.com/
1.1 实现步骤
  1. 采用PO模式的分层思想对页面进行封装
  2. 编写测试脚本
  3. 使用参数化传入测试数据
  4. 把测试数据定义到JSON数据文件中
1.2 数据文件
  • 第一个数字加第二个数字等于第三个数字,每一行数据代表一个用例
    image
1.3 示例代码
from selenium import webdriver

class DriverUtil:
	"""浏览器驱动工具类"""
	_driver=None
	@classmethod
	def get_driver(cls):
		if cls._driver is None:
			cls._driver=webdriver.Chrom()
			cls._driver.maximize_window()
			cls._driver.implicitly_wait(10)
			cls._driver.get("http://cal.apple886.com/")
		return cls._driver

	@classmethod
	def quit_driver(cls):
	if cls.driver:
		cls.driver.quit()
		cls.driver=None
from selenium.webdriver.common.by import By
from ddt.calculator.utils import DriverUtil
class CalculatorPage:
	"""计算器页面-对象库层"""
	def __init__(self):
		self.driver=DriverUtil.get_driver()
		self.digit_btn=(By.ID,"simple{}")
		self.add_btn=(By.ID,"simpleAdd")
		self.eq_btn = (By.ID, "simpleEqual")
		self.result = (By.ID, "resultIpt")
	def find_digit_btn(self,digit):
		location=(self.digit_btn[0],self.digit_btn[1].format(digit))
		return self.driver.find_element(*location)
	def find_add_btn(self):
		return self.driver.find_element(*self.add_btn)
	def find_eq_btn(self):
		return self.driver.find_element(*self.eq_btn)
	def find_result_btn(self):
		return self.driver.find_element(*self.result)

class CalculatorHandle:
	"""计算器页面-操作层"""
	def __init__(self):
		self.calculator_page=CalculatorPage()
	def click_digit_btn(self,digit):
		self.calculator_page.find_digit_btn().click()
	def click_add_btn(self):
		self.calculator_page.find_add_btn().click()
	def click_eq_btn(self):
		self.calculator_page.find_eq_btn().click()
	def get_result(self):
		return self.calculator_page.find_result_btn().get_attribute("value")
	def input_numbers(self, numbers):
		for num in numbers:
			self.click_digit_btn(num)

class CalculatorProxy:
	"""计算器页面-业务层"""
	def __init__(self):
		self.calculator_handle=CalculatorHandle()
	def add(self,num1,num2):
		self.calculator_handle.input_numbers(str(num1))
		self.calculator_handle.click_add_btn()
		self.calculator_handle.input_numbers(str(num2))
		self.calculator_handle.click_eq_btn()
	def get_result(self):
		return self.calculator_handle.get_result()
import json
import time
import unittest
from parameterized import parameterized
from ddt.calculator.page.calculator_page import CalculatorProxy
from ddt.calculator.utils import DriverUtil

def build_data():
	test_data=[]
	with open("../data/calculator.json",encoding='UTF-8') as f:
		test_data=json.load(f)
		print("test_data=", test_data)
		return test_data

class TestCalculator(unittest.TestCase):
	@classmethod
	def setUpClass(cls):
		cls.driver=DriverUtil.get_driver()
		cls.calculatorProxy=CalculatorProxy()
	@classmethod
	def tearDownClass(cls):
		DriverUtil.quit_driver()
	@parameterized.expand(build_data)
	def test_add(self,a,b,except):
		print('a={} b={} expect={}'.format(a, b, expect))
		self.calculatorProxy.add(a, b)
		result = self.calculatorProxy.get_result()
		print("result=", result)
		self.assertEqual(result, str(expect))

数据驱动实战二

1、案例

对TPshop网站的登录模块进行单元测试

1.1 实现步骤
  1. 编写测试用例
  2. 采用PO模式的分层思想对页面进行封装
  3. 编写测试脚本
  4. 定义数据文件,实现参数化
1.2 用例设计

image

1.3 数据文件

image

1.4 示例代码
from selenium import webdriver
def get_tips_msg():
"""
获取弹出框的提示消息
:return: 消息文本内容
"""
msg = DriverUtil.get_driver().find_element_by_class_name("layui-layer-content").text
return msg
class DriverUtil:
"""
浏览器驱动工具类
"""
_driver = None
@classmethod
def get_driver(cls):
"""
获取浏览器驱动对象,并完成初始化设置
:return: 浏览器驱动对象
"""
if cls._driver is None:
cls._driver = webdriver.Firefox()
cls._driver.maximize_window()
cls._driver.implicitly_wait(10)
cls._driver.get("http://localhost")
return cls._driver
@classmethod
def quit_driver(cls):
"""
关闭浏览器驱动
"""
if cls._driver:
cls._driver.quit()
cls._driver = None
# login_page.py
from selenium.webdriver.common.by import By
from ddt.tpshop.utils import DriverUtil
class LoginPage:
"""
登录页面-对象库层
"""
def __init__(self):
self.driver = DriverUtil.get_driver()
# 用户名输入框
self.username = (By.ID, "username")
# 密码
self.password = (By.ID, "password")
# 验证码
self.verify_code = (By.ID, "verify_code")
# 登录按钮
self.login_btn = (By.NAME, "sbtbutton")
def find_username(self):
return self.driver.find_element(*self.username)
def find_password(self):
return self.driver.find_element(*self.password)
def find_verify_code(self):
return self.driver.find_element(*self.verify_code)
def find_login_btn(self):
return self.driver.find_element(*self.login_btn)

class LoginHandle:
"""
登录页面-操作层
"""
def __init__(self):
self.login_page = LoginPage()
def input_username(self, username):
self.login_page.find_username().send_keys(username)
def input_password(self, pwd):
self.login_page.find_password().send_keys(pwd)
def input_verify_code(self, code):
self.login_page.find_verify_code().send_keys(code)
def click_login_btn(self):
self.login_page.find_login_btn().click()

class LoginProxy:
"""
登录页面-业务层
"""
def __init__(self):
self.login_handle = LoginHandle()
def login(self, username, password, code):
self.login_handle.input_username(username)
self.login_handle.input_password(password)
self.login_handle.input_verify_code(code)
self.login_handle.click_login_btn()

# test_login.py
import json
import time
import unittest
from parameterized import parameterized
from ddt.tpshop import utils
from ddt.tpshop.page.login_page import LoginProxy
from ddt.tpshop.utils import DriverUtil
# 构建测试数据
def build_data():
test_data = []
with open("../data/testData.json", encoding='UTF-8') as f:
json_data = json.load(f)
for login_data in json_data.values():
test_data.append((login_data.get("username"),
login_data.get("password"),
login_data.get("code"),
login_data.get("is_success"),
login_data.get("expect")))
print("test_data=", test_data)
return test_data

class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = DriverUtil.get_driver()
cls.login_proxy = LoginProxy()
@classmethod
def tearDownClass(cls):
DriverUtil.quit_driver()
def setUp(self):
# 进入首页
self.driver.get("http://localhost")
# 点击登录链接
self.driver.find_element_by_link_text("登录").click()
@parameterized.expand(build_data)
def test_add(self, username, password, code, is_success, expect):
print('username={} password={} code={} is_success={} expect={}'.
format(username, password, code, is_success, expect))
# 登录
self.login_proxy.login(username, password, code)
time.sleep(3)
# 登录成功的用例
if is_success:
self.assertIn(expect, self.driver.title)
else:
# 获取提示框消息
msg = utils.get_tips_msg()
print("msg=", msg)
self.assertIn(expect, msg)

第6章-日志收集

日志相关概念

1、日志

  • 概念:日志就是用于记录系统运行时的信息,对一个事件的记录;也称为Log。
1.1 日志的作用
  • 调试程序
  • 了解系统程序运行的情况,是否正常
  • 系统程序运行故障分析与问题定位
  • 用来做用户行为分析和数据统计
1.2 日志级别
  • 日志级别:是指日志信息的优先级、重要性或者严重程度
  • 常见的日志级别
    image
    上面列表中的日志级别是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL;
    当为程序指定一个日志级别后,程序会记录所有日志级别大于或等于指定日志级别的日志信息,而不是仅仅记
    录指定级别的日志信息;
    一般建议只使用DEBUG、INFO、WARNING、ERROR这四个级别
    

日志的基本用法

1、logging模块

  • Python中有一个标准库模块logging可以直接记录日志
1.1 基本用法
import logging

logging.debug("这是一条调试信息")
logging.info("这是一条普通信息")
logging.warning("这是一条警告信息")
logging.error("这是一条错误信息")
logging.critical("这是一条严重错误信息")
1.2 设置日志级别
  • logging中默认的日志级别为WARNING,程序中大于等于该级别的日志才能输出,小于该级别的日志不会被打印出来。
  • 设置日志级别
    logging.basicConfig(level=logging.DEBUG)
    
  • 如何选择日志级别
    • 在开发环境和测试环境中,为了尽可能详细的查看程序的运行状态来保证上线后的稳定性,可以使用DEBUG或INFO级别的日志获取详细的日志信息,这是非常耗费机器性能的。
    • 在生产环境中,通常只记录程序的异常信息、错误信息等(设置成WARNING或ERROR级别),这样既可以减小服务器的I/O压力,也可以提高获取错误日志信息的效率和方便问题的排查。
1.3 设置日志格式
```
默认的日志的格式为:
日志级别:Logger名称:日志内容
自定义日志格式:
logging.basicConfig(format="%(levelname)s:%(name)s:%(message)s")
```
  • format参数中可能用到的格式化信息:
    image
  • 示例代码
    import logging
    
    fmt='%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
    logging.basicConfig(level=logging.INFO,format=fmt)
    
    logging.debug("调试")
    logging.info("信息")
    logging.warning("警告")
    logging.error("错误")
    
1.4 将日志信息输出到文件中
  • 默认情况下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(filename="a.log",level=logging.INFO,format=fmt)
    logging.debug("调试")
    logging.info("信息")
    logging.warning("警告")
    logging.error("错误")
    

日志的高级用法

1、logging日志模块四大组件

image
logging模块就是通过这些组件来完成日志处理的

1.1 组件之间的关系
  • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
  • 不同的处理器(handler)可以将日志输出到不同的位置;
  • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
  • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。
  • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
    简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过
    滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。
1.2 Logger类
  • Logger对象的任务
    • 向程序暴露记录日志的方法
    • 基于日志级别或Filter对象来决定要对哪些日志进行后续处理
    • 将日志消息传送给所有感兴趣的日志handlers
  • 如何创建Logger对象
    logger=logging.getLogger()
    logger=logging.getLogger("myLogger")
    
    logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则返回root日志器对象。 若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
    
  • Logger常用的方法
    image
1.3 Handler类
  • Handler对象的作用是将消息分发到handler指定的位置,比如:控制台、文件、网络、邮件等。 Logger对象可以通过addHandler()方法为自己添加多个handler对象。
  • 如何创建Handler对象
    • 在程序中不应该直接实例化和使用Handler实例,因为Handler是一个基类,它只定义了Handler应该有的接口。 应该使用Handler实现类来创建对象,logging中内置的常用的Handler包括:
      image
  • Handler常用的方法
    handler.setLevel()   #设置handler将会处理的日志消息的最低严重级别
    handler.setFormatter()   #为handler设置一个格式器对象
    handler.addFilter()   #为handler添加一个过滤器对象
    
1.4 Formatter类
  • Formatter对象用于配置日志信息的格式。
  • 如何创建Formatter对象
    formatter=logging.Formatter(fmt=None,datefmt=None,style='%')
    	fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
    	datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
    	style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
    

2、将日志信息同时输出到控制台和文件中

  • 实现步骤
    1.创建日志器对象
    2.创建控制台处理器对象
    3.创建文件处理器对象
    4.创建格式化器对象
    5.把格式化器添加到处理器中
    6.把处理器添加到日志器中
    
  • 定义日志格式
    fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s'
    formatter=logging.Formatter(fmt)
    
  • 把日志输出到控制台
    logger=logging.getLogger()
    sh=logging.StreamHandler()
    sh.setFormatter(formatter)
    logger.addHandler(sh)
    
  • 把日志输出到文件中
    fh=logging.FileHandler("./b.log")
    fh.setFormatter(formatter)
    logging.addHandler(fh)
    

3、每日生成一个日志文件

定义Handler对象

fh = logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0)
将日志信息记录到文件中,以特定的时间间隔切换日志文件。
filename: 日志文件名
when: 时间单位,可选参数
S - Seconds
M - Minutes
H - Hours
D - Days
midnight - roll over at midnight
W{0-6} - roll over on a certain day; 0 - Monday
interval: 时间间隔
backupCount: 日志文件备份数量。如果backupCount大于0,那么当生成新的日志文件时,
将只保留backupCount个文件,删除最老的文件。

示例代码:

import logging.handlers

logger=logging.getLogger()
logger.setLevel(logging.DEBUG)
#日志格式
fmt="%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter=logging.Formatter(fmt)
#输出到文件,每日一个文件
fh=logging.handlers.TimeRotatingFileHandler("./a.log",when='MIDNIGHT',interval==1,backupCont=3)
fh.setFormatter(formatter)
fh.setLevel(logging.INFO)
logger.addHandler(fh)

第7章-项目实战

  • 目标
1. 熟悉自动化测试的流程
2. 能够对一个web项目实现自动化测试
3. 熟练使用selenium常用的API
4. 能够把UnitTest应用到项目中
5. 能够把PO模式应用到项目中
6. 能够把数据驱动应用到项目中
7. 能够把日志收集功能应用到项目中

自动化测试流程

1. 需求分析
2. 挑选适合做自动化测试的功能
3. 设计测试用例
4. 搭建自动化测试环境 [可选]
5. 设计自动化测试项目的架构 [可选]
6. 编写代码
7. 执行测试用例
8. 生成测试报告并分析结果

项目介绍

  • 项目名称
    TPshop开源商城系统
  • 项目描述
    TPshop是一个电子商务B2C电商平台系统,功能强大,安全便捷。适合企业及个人快速构建个性化网上商城。
    包含PC+IOS客户端+Adroid客户端+微商城,系统PC+后台是基于ThinkPHP MVC构架开发的跨平台开源软件,设计得非常灵活,具有模块化架构体系和丰富的功能,易于与第三方应用系统无缝集成,在设计上,包含相当全面,以模块化架构体系,让应用组合变得相当灵活,功能也相当丰富。
  • 项目架构
    image

用例设计

1、编写自动化测试用例的原则

1. 自动化测试用例一般只实现核心业务流程或者重复执行率较高的功能。
2. 自动化测试用例的选择一般以“正向”逻辑的验证为主。
3. 不是所有手工用例都可以使用自动化测试来执行。
4. 尽量减少多个用例脚本之间的依赖。
5. 自动化测试用例执行完毕之后,一般需要回归原点。

2、编写测试用例

image

项目搭建

1、初始化项目

  • 新建项目
    项目名称:webAutoTestTPshop
  • 创建目录结构
    image
  • 安装依赖包
    安装 selenium 包
    安装 parameterized 包
    添加 HTMLTestRunner

2、初始化代码

封装驱动工具类
封装PO基类,定义BasePage 和 BaseHandle

编写代码

1、抽取PO

根据用例分析待测功能,提取页面对象

  • 定义页面对象文件
    登录页: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
    我的订单页:my_order_page.py
    
  • 分别编写对象库层、操作层、业务层的代码

2、编写测试脚本

  • 定义测试脚本文件
    登录模块:test_login.py
    购物车模块:test_cart.py
    订单模块:test_order.py
    
  • 使用unittest管理测试脚本

3、执行测试脚本

  1. 使用unittest执行测试脚本
  2. 调试代码

完善代码

1、数据驱动

  • 定义数据文件
  1. 定义存放测试数据的目录,目录名称:data
  2. 分模块定义数据文件
    登录模块:login.json
    购物车模块:cart.json
    订单模块:order.json
    

3.根据业务编写用例数据

  • 测试数据参数化
    修改测试脚本,使用parameterized实现参数化

2、日志收集

使用logging模块实现日志的收集
  • 示例代码
import logging.handlers
import os

#工程目录
BASE_DIR=os.path.dirname(os.path.abspath(__file__))
def init_log_config():
	"""初始化日志配置"""
	#日志输出格式
	fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
	#创建日志器
	logger=logging.getLogger()
	logger.setLevel(logging.INFO)
	#创建格式化器
	formatter=logging.Formatter(fmt)
	#输出到控制台
	sh=logging.StreamHandler()
	sh.setFormatter(formatter)
	logger.addHandler(sh)
	#输出到文件,每日一个文件
	log_path=os.path.join(BASE_DIR,"log","tpshop.log")
	fh=logging.handlers.TimeRotatingFileHandler(log_path,when='MIDNIGHT',interval=1,backCount=3)
	fh.setFormatter(formatter)
	logger.addHandler(fh)

3、生成测试报告

  • 使用HTMLTestRunner生成测试报告
    report_file="./report/report{}.html".format(time.strftime(%Y%m%d-%H%M%S))
    with open(report_file,"wb") as f:
    	runner=HTMLTestRunner(f,title="TPshop商城自动化测试报告",description="Win10.Firefox")
    	runner.run(suite)
    

标签:Web,self,driver,element,自动化,login,find,def
From: https://www.cnblogs.com/hzf666/p/17657983.html

相关文章

  • MacOS 搭建Appium自动化测试环境
    一、软件安装1.JDK安装1) JDK安装在Oacle官网下载JDK8,网页如下:https//www.oracle.com/java/technologies/downloads/#java8-mac2) 配置环境变量安装成功后,使用指令 /usr/libexec/java_home-V,可以查看Java8的安装目录。或使用whichjava查看可执行文件的路径,进入这个路径后再用......
  • Web服务器项目详解
    文章目录一、新连接到来的处理流程二、Channel、TcpConnection、TcpServer、Poller、EventLoop类详解1、Channel类2、TcpConnection类3、TcpServer类4、Poller类5、EventLoop类三、这几个类之间的关系一、新连接到来的处理流程一个新的连接到来后,首先被MainReactor接收,然后通过轮......
  • 为什么自动化运维难以推广
    从价值流的角度去分析:devops为何能做到高度自动化,而自动化运维难以推广,因为devops是从价值流出发,是服务于最终用户的,提升最终用户获得价值的效率,有巨大的推动力。而自动化运维只是从运维内部来推动,帮助运维团队提升工作效率,说白了直接受益者还是运维内部,客户感知不强烈。从价值角......
  • 编译wasm Web应用
    刚学完WebAssembly的入门课,卖弄一点入门知识。首先我们知道wasm是目标语言,是一种新的V-ISA标准,所以编写wasm应用,正常来说不会直接使用WAT可读文本格式,更不会用wasm字节码;而是使用其他高级语言编写源代码,经过编译后得到wasm应用。课程中使用了C++来编写源代码,所以这里我也用C++来......
  • Web服务器项目中常问的开放性问题
    文章目录一、对后端开发的看法?二、为什么要做静态http服务器?三、为什么要使用ET模式?四、多线程编程的注意事项1、为什么要用多线程2、多线程编程的特点3、确保线程安全的几种方式4、与多线程有关的编程方法五、使用Linux系统有什么好处?六、对云计算的看法?七、服务器突然崩溃退出,怎......
  • spring boot WebSocket @ServerEndpoint注解标识的class无法获取spring容器中的bean
    在@ServerEndpoint类中直接使用@Autowired注解注入Spring管理的bean可能不会成功,因为@ServerEndpoint并不受Spring容器的管理。通过创建一个静态的成员遍历属性和一个带有@Autowired注解的setter方法,你可以在类加载时将bean注入到静态属性中。但是,请注意这样做......
  • 怎么搭建web组态
    web组态是指通过可视化拖拽组件的方式,低代码搭建监控大屏、web页面。物联网各行业的数据以及监控场景,每个行业的业务不同,需要展示的页面也不同。组态快速搭建页面的优势,能更好的满足不同定制化监控页面的需求。BY组态软件,专注于能源电力、工业互联网、智能制造、原型设计等领域的......
  • Web组态可视化软件之BY组态可视化平台介绍
    Web组态可视化软件之BY组态可视化平台介绍关于组态软件,首先要从组态的概念开始说起。 什么是组态组态(Configure)的概念来自于20世纪70年代中期出现的第一代集散控制系统(DistributedControlSystem),可以理解为“配置”、“设定”、“设置”等,是指通过人机开发界面,用类似“搭积木”......
  • Unity UnityWebRequest.Post传参Json数据
    UnityWebRequest.PostUnity中的HTTP通信主要依赖的是Unity自带的UnityWebRequest类,之前的WWW类已被弃用Post请求,向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。对应的调用方法:UnityWebR......
  • Fooocus:一个简单且功能强大的Stable Diffusion webUI
    StableDiffusion是一个强大的图像生成AI模型,但它通常需要大量调整和提示工程。Fooocus的目标是改变这种状况。Fooocus的创始人LvminZhang(也是ControlNet论文的作者)将这个项目描述为对“StableDiffusion”和“Midjourney”设计的重新设计。Fooocus就像是Midjourney的免费离线......