自动化测试

7

自动化测试

概念

用程序替代人工去执行测试的过程

应用场景

解决重复性操作的问题

  • 解决回归测试

  • 解决压力测试

  • 解决兼容性测试

正确认识

  • 优点

    • 较少的时间内运行更多的测试用例

    • 自动化脚本可以重复运行

    • 减少人为错误

    • 克服手工测试的局限性

  • 误区

    • 自动化测试可以完全代替手工测试

    • 自动化测试一定比手工测试厉害

    • 自动化测试可以发现更多bug

    • 自动化测试适用于所有功能

分类

  • 接口-自动化测试

  • web-自动化测试

  • 移动-自动化测试

  • 单元-自动化测试

web自动化测试

概念

用程序替代人工去执行web测试的过程

哪些项目适合web自动化

  • 需求变动不频繁

  • 项目周期较长

  • 项目需要回归测试

从什么时候开始

  • 手工测试结束后

所属分类

  • 功能测试

selenium - web自动化工具

特点

  • 开源

  • 跨平台

  • 支持多种浏览器

  • 支持多语言

  • 成熟稳定

  • 功能强大

环境搭建

# 搭建步骤
1. 语言开发环境
2. 安装selenium
3. 安装浏览器驱动     

演示

# 通过查询启动谷歌浏览器,打开百度网站,延迟三秒,关闭浏览器

# 导包
import time
from selenium import webdriver 
# 创建浏览器驱动对象
drive = webdriver.Chrome()
# 打开百度
drive.get("https://www.baidu.com")
# 延迟三秒
time.sleep(3)
# 关闭浏览器
drive.quit()

selenium - API

元素定位基础

为什么进行元素定位

让程序操作指定元素,就必须先找到此元素

如何进行元素定位

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

八种定位方式
  • id

  • name

  • class

  • tag_name

  • link_text

  • partial_link_text

  • XPath

  • CSS

XPath定位

什么是XPath

概念

XPath就是XML Path,它是一门在XML文档中查找元素信息的语言

HTML可以看作是XML的一种实现

总体介绍

四种定位方式
  1. 路径

  2. 元素属性

  3. 属性和逻辑结合

  4. 层级与属性结合

方法
from selenium import webdriver 
from selenium.webdriver.common.by import By
element = driver.find_element(By.XPATH,xpath表达式)

路径定位

概念
  • 绝对路径

    • 从最外层元素到指定元素之间所有经过元素层级的路径

      • 绝对路径以 /html 根节点开始,使用 / 来分割元素层级

      • 如/html/body/div/p[1]

    • 绝对路径对页面结构要求比较严格,不建议使用

  • 相对路径

    • 匹配任意层级的元素,不限制元素的位置

      • 相对路径 // 开始

      • 格式://input,//*

代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("https://www.baidu.com")

driver.find_element(By.XPATH,"//*[@id='kw']").send_keys("123")
driver.find_element(By.XPATH,"//*[@id='su']").click()

time.sleep(5)

driver.quit()

元素属性定位

说明

通过元素属性信息来定位元素

格式
//input[@id='id'] 或者 //*[@id='id']
查询该页面中所有input当中对应id值的input
查询该页面中所有元素当中对应id值的元素
代码
driver.find_element(By.XPATH,"//*[@id='su']").click()

属性和逻辑结合定位

说明

为了解决元素之间存在相同属性值的问题

格式

//*[@name='tel' and @class='tel']

代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("https://www.baidu.com")

driver.find_element(By.XPATH,"//*[@name='wd' and @class='s_ipt']").send_keys("123")
driver.find_element(By.XPATH,"//*[@id='su']").click()

time.sleep(5)

driver.quit()

层级与属性结合定位

说明

如果通过元素自身的属性不方便直接定位该元素,则可以先定位到其父元素,然后再找到该元素

格式

//*[@id='id']/input

代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("https://www.baidu.com")

driver.find_element(By.XPATH,"//*[@id='form']/span[1]/input").send_keys("123")
driver.find_element(By.XPATH,"//*[@id='su']").click()

time.sleep(5)

driver.quit()

XPath扩展

说明
# 可以使用函数:
	//*[text()='xxx'] 定位文本内容是 xxx 的元素
    //*[contains(@attribute,'xxx')] 定位属性中含有 xxx 的元素
    //*[starts-with(@attribute,'xxx')] 定位属性以 xxx 开头的元素
代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://limestart.cn/")

# driver.find_element(By.XPATH, "//*[text()='奋进强国路 阔步新征程']").click()
# driver.find_element(By.XPATH, "//*[contains(@placeholder,'索')]").send_keys("123")
driver.find_element(By.XPATH, "//*[starts-with(@placeholder,'搜')]").send_keys("12334")


time.sleep(3)

driver.quit()

CSS定位

常用定位方式

  • id选择器

  • class选择器

  • 元素选择器

  • 属性选择器

  • 层级选择器

方法
element = driver.find_element(By.CSS_SELECTOR,CSS表达式)

id选择器

说明

根据元素的id属性来定位元素

格式
#id
// 例如:选择id属性值为 userA 的元素
#userA 
代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")

driver.find_element(By.CSS_SELECTOR,"#kw").send_keys("python")
driver.find_element(By.CSS_SELECTOR,"#su").click()

time.sleep(3)

driver.quit()

class选择器

说明

根据元素的class属性来定位元素

格式
.class值
代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")

driver.find_element(By.CSS_SELECTOR,".s_ipt").send_keys("python")
driver.find_element(By.CSS_SELECTOR,".s_btn").click()

time.sleep(3)

driver.quit()

元素选择器

说明

根据元素的标签名选择元素

格式

标签名

代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")

driver.find_element(By.CSS_SELECTOR,"a").click()

time.sleep(3)

driver.quit()

属性选择器

说明

根据元素的属性名和属性值来定位元素

格式
[属性名=属性值]
# 如,选择属性名为 type 的,值为 password的元素
[type='password']
代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")

driver.find_element(By.CSS_SELECTOR,"span.quickdelete-wrap>input").send_keys("测试")
driver.find_element(By.CSS_SELECTOR,"form[name='f'] span input").send_keys("测试")
driver.find_element(By.CSS_SELECTOR,"span.s_btn_wr>input").click()

time.sleep(3)

driver.quit()

层级选择器

说明

根据元素的父子关系来选择

格式

element1 > element2

element1[属性名=属性值] > element2

通过element1来定位element2,并且element2必须为element1的直接子元素

element1 element2

通过element1来定位element2,并且element2为element1的后代元素

代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")

driver.find_element(By.CSS_SELECTOR,"span.quickdelete-wrap>input").send_keys("测试")
driver.find_element(By.CSS_SELECTOR,"span.s_btn_wr>input").click()

time.sleep(3)

driver.quit()

CSS扩展

input[type^='p'] type属性以p字母开头的元素
input[type$='d'] type属性以d字母结束的元素
input[type*='w'] type属性包含了w字母的元素

元素定位总结

元素定位分类汇总

  1. id,name,class:元素属性定位

  2. tag_name:元素标签名定位

  3. link_text,partial_link_text:通过文本定位超链接

  4. XPath:通过路径定位元素

  5. CSS:使用CSS选择器定位元素

元素操作

应用场景

  • 需要让脚本模拟用户的点击操作

    click()
  • 需要让脚本模拟用户输入一些内容

    send_keys(value)
  • 需要让脚本模拟用户去清空输入框的内容

    clear()
代码
import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")
# 输入内容
driver.find_element(By.CSS_SELECTOR,"span.quickdelete-wrap>input").send_keys("测试")
# 睡眠3s
time.sleep(3)
# 清空内容
driver.find_element(By.CSS_SELECTOR,"span.quickdelete-wrap>input").clear()
# 输入内容
driver.find_element(By.CSS_SELECTOR,"span.quickdelete-wrap>input").send_keys("测试")
# 点击
driver.find_element(By.CSS_SELECTOR,"span.s_btn_wr>input").click()
time.sleep(3)

driver.quit()

浏览器操作

代码

from optparse import Option

from selenium import webdriver
from selenium.webdriver.common.by import By

# 2.实例化浏览器对象
# 获取配置对象 => 什么样的浏览器就选择什么浏览器配置
option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)
driver = webdriver.Chrome(options=option)
driver.get("https://www.baidu.com")
# 浏览器窗口最大化
driver.maximize_window()
# 设置窗口大小,单位是像素点
driver.set_window_size(1920, 1080)
# 设置窗口的位置,参数:横纵坐标
driver.set_window_position(0, 0)
# 页面后退
driver.back()
# 页面前进
driver.forward()
# 页面刷新
driver.refresh()

# 获取title
print(driver.title)
# 当前页面地址
print(driver.current_url)
# 关闭当前浏览器窗口
driver.find_element(By.XPATH, "//*[text()='习近平总书记关切事']").click()
# driver.find_element(By.LINK_TEXT,"习近平总书记关切事").click()
driver.close()
# 关闭浏览器驱动对象(关闭浏览器)
driver.quit()

获取元素信息

应用场景

用于判断,获取定位的元素是否准确。

通过获取元素信息来确认定位到的元素是否正确

方法

get_attribute("属性名")	获取属性值
is_displayed()			  元素是否可见
is_enabled()			  元素是否可用
is_selected()			  元素是否选中

属性

size					  返回元素大小(宽高)
text					  获取元素文本

代码

import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

# 1.获取输入框大小
print(driver.find_element(By.ID, "kw").size)
# 2.获取超链接的文本内容
print(driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a > span.title-content-title").text)
# 2.获取超链接的地址
print(driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a").get_attribute("href"))
# 元素是否可见
print(driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a").is_displayed())
# 元素是否可用
print(driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a").is_enabled())
# 元素是否选中
print(driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a").is_selected())

time.sleep(3)
driver.quit()

鼠标操作

应用场景

现在web场景存在丰富的鼠标交互方式,作为一个web自动化测试框架,需要应对这些鼠标操作的场景

常用方法

说明:在selenium中将鼠标操作的方法封装在ActionChains类中

action = ActionChains(driver)
# 右键 element是想要在哪个元素上右击
action.context_click(element)
# 双击 element是想要在哪个元素上双击
action.double_click(element)
# 悬停 element是想要在哪个元素上悬停 
action.move_to_element(element)
# 拖拽 source源元素,target目标元素
action.drag_and_drop(source,target)
# 执行
action.perform()
右击 - context_click
"""
context_click(element)
说明:
	对于鼠标右键,如果弹出的是浏览器的默认菜单,Selenium并没有提供操作菜单的方法
	如果是自定义的右键菜单,则可以通过元素定位来操作菜单中的选项
"""	
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
action = ActionChains(driver)
element = driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a")
# 右键点击元素
action.context_click(element).perform()

time.sleep(3)
driver.quit()
双击 - double_click
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
action = ActionChains(driver)
element = driver.find_element(By.CSS_SELECTOR, "#hotsearch-content-wrapper > li:nth-child(1) > a")

# 双击
action.double_click(element).perform()

time.sleep(3)
driver.quit()
悬停 - move_to_element
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
action = ActionChains(driver)

element = driver.find_element(By.CSS_SELECTOR,"#form > span.bg.s_ipt_wr.new-pmd.quickdelete-wrap > span.soutu-btn")

# 悬停
action.move_to_element(element).perform()

time.sleep(5)
driver.quit()
拖拽 - drag_and_drop
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
action = ActionChains(driver)

element = driver.find_element(By.XPATH, "//*[text()='习近平总书记关切事']")
element1 = driver.find_element(By.ID,"kw")

# 悬停
# action.move_to_element(element).perform()
action.drag_and_drop(element,element1).perform()
time.sleep(5)
driver.quit()

键盘操作

应用场景

  1. 模拟键盘上一些按键或者组合键的输入

  2. Selenium中把键盘的按键操作都封装在Keys类中

常用操作

# 删除键
# 1. send_keys(Keys.BACK_SPACE)
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.BACKSPACE)

time.sleep(5)
driver.quit()

# 空格键
# 2. send_keys(Keys.SPACE)
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.SPACE)

time.sleep(5)
driver.quit()

# 制表键
# 3. send_keys(Keys.TAB)
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.TAB)

time.sleep(5)
driver.quit()

# 回退键
# 4. send_keys(Keys.ESCAPE)
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.ESCAPE)

time.sleep(5)
driver.quit()

# 回车键
# 5. send_keys(Keys.EBTER)
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.EBTER)

time.sleep(5)
driver.quit()

# 全选 Ctrl + a
# 6. send_keys(Keys.CONTROL,'a')
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.CONTROL,'a')

time.sleep(5)
driver.quit()

# 复制 Ctrl + c
# 7. send_keys(Keys.CONTROL,'c')
import time
from optparse import Option

from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")

driver.find_element(By.ID,"kw").send_keys("123")
driver.find_element(By.ID,"kw").send_keys(Keys.CONTROL,'c')

time.sleep(5)
driver.quit()
...

元素等待

概念

定位元素时,如果未找到,在指定时间内一直等待的过程

分类

  • 隐式等待

  • 显式等待

应用场景

由于一些原因,我们想找的元素没有立刻出来,此时如果直接定位会报错

原因:

  1. 网络速度慢

  2. 服务器处理请求速度慢

  3. 硬件配置原因

思考:是否定位每个元素时,都需要元素等待?

是的,因为不能完全保证资源已经完全加载完成

隐式等待

方法
# 全局设置隐式等待
# 参数timeout:超时时长,单位是秒
# driver.implicitly_wait(timeout)
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.implicitly_wait(10)
driver.find_element(By.CSS_SELECTOR,"input[placeholder='延时加载的输入框']").send_keys("admin")

time.sleep(3)
driver.quit()

显式等待

说明

在Selenium中把显式等待的相关方法封装在 WebDriverWait 类中

方法
# 显示等待,为定位不同的元素的超时时间设置不同的值
1. 导包
# driver : 浏览器驱动对象
# timeout : 超时时长,单位 : 秒
# poll_frequency : 检测的间隔时间,默认为 0.5s
2. wait =  WebDriverWait(driver, timeout, poll_frequency=0.5)
# method : 函数名称,该函数用来实现元素定位
# 一般使用匿名函数来实现 : lambda x: x.find_element(By.xx,"xxx")
3. wait.until(method)
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait

driver = webdriver.Chrome()
driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
wail = WebDriverWait(driver, 10,1)
element = wail.until(lambda x: x.find_element(By.CSS_SELECTOR,"input[placeholder='延时加载的输入框']"))
element.send_keys("admin")

time.sleep(3)
driver.quit()

隐式等待和显式等待的区别

  1. 作用域

    • 隐式等待为全局有效

    • 显式等待为单个元素有效

  2. 使用方法

    • 隐式等待直接通过驱动对象调用

    • 显式等待方法封装在 WebDriverWait 类中

  3. 达到最大超出时长后抛出异常不同

    • 隐式等待为 NotSuchElementException

    • 显式等待为 TimeoutException

下拉框/弹出框/滚动条操作

下拉框

方法一
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
time.sleep(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()
time.sleep(3)
driver.quit()
方法二
说明 : 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内容来定位的
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select

driver = webdriver.Chrome()

driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
select = Select(driver.find_element(By.ID,"selectA"))
time.sleep(2)
select.select_by_index(2)
time.sleep(2)
select.select_by_value("sh")
time.sleep(2)
select.select_by_visible_text("北京")
time.sleep(3)

driver.quit()

弹出框

说明

Selenium中对弹出框的处理,有专用的方法,且处理的方法都一样

  1. 获取弹出框对象

    alert = driver.switch_to.alert
  2. 调用

    alert.text		返回alert/confirm/prompt文字信息
    alert.accept	接受对话框选项(确认)
    alert.dismiss	取消对话框选项(取消)
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select

driver = webdriver.Chrome()

driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element(By.ID,"alerta").click()
time.sleep(2)
alert = driver.switch_to.alert
print(alert.text)
time.sleep(2)
alert.accept()
time.sleep(2)
driver.find_element(By.ID,"userA").send_keys("admin")


time.sleep(3)
driver.quit()

滚动条

说明

Selenium中没有提供操作滚动条的方法,但是提供了执行JS脚本的方法,所以我们可以通过 JS脚本来操作滚动条

操作步骤
1. 设置JS脚本控制滚动条
	window.scrollTo(0,1000) # (0: 左边距,1000: 上边距,单位: 像素)
2. Selenium调用执行JS脚本的方法
	driver.execute_script(js)
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
# JS脚本 滚动到底部
js1 = "window.scrollTo(0,10000)"
# JS脚本 滚动到顶部
js2 = "window.scrollTo(0,0)"
time.sleep(2)
# 执行JS脚本
driver.execute_script(js1)
time.sleep(2)
driver.execute_script(js2)
time.sleep(3)
driver.quit()

frame切换

说明

Selenium中封装了切换frame框架的方法

步骤
# frame_reference : 可以传frame框架的id,name,定位的frame元素
1. driver.switch_to.frame(frame_reference)	切换到指定frame
# 操作完frame页面后必须返回原页面才能进行下一步操作
2. driver.switch_to.default_content() 		恢复默认页面
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8C%E5%AE%9E%E4%BE%8B.html?userA=&passwordA=&telA=&emailA=&selecta=bj&fruit=lia&hobby=%E6%97%85%E6%B8%B8&upfilea=")
driver.find_element(By.ID,"userA").send_keys("admin")
time.sleep(2)
# 切换到 A 页面
# 通过ID
driver.switch_to.frame("idframe1")
# 通过Name
driver.switch_to.frame("myframe1")
# 通过定位元素
driver.switch_to.frame(driver.find_element(By.ID,"idframe1"))
driver.find_element(By.ID,"userA").send_keys("admin1")
time.sleep(2)
# 回到原页面
driver.switch_to.default_content()
time.sleep(2)
# 切换到 B 页面
driver.switch_to.frame("idframe2")
driver.find_element(By.ID,"userA").send_keys("admin2")
time.sleep(3)
driver.quit()

多窗口切换

说明

在Selenium中封装了获取当前窗口句柄,获取所有窗口句柄和切换到指定句柄窗口的方法

句柄 : 英文名handle, 窗口的唯一标识码

方法
1. driver.current_window_handle		获取当前窗口句柄
2. driver.window_handles			获取所有窗口句柄
3. driver.switch_to.window(handle)	切换到指定句柄的窗口
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
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])
time.sleep(1)
driver.find_element(By.XPATH,"//*[@name='SerchKey']").clear()
time.sleep(1)
driver.find_element(By.XPATH,"//*[@name='SerchKey']").send_keys("新浪搜索")
time.sleep(2)
# 切换为原窗口,输入用户名admin
driver.switch_to.window(handles[0])
driver.find_element(By.ID,"userA").send_keys("admin")
time.sleep(3)
driver.quit()

窗口截图

为什么要进行窗口截图

有时候打印的错误信息不一定十分准确,需要窗口截图辅助定位错误

方法

driver.get_screenshot_as_file(imgpath)
	imgpath : 图片保存路径 + 图片名

代码

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("file:///C:/Data/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element(By.ID,"userA").send_keys("admin")
time.sleep(1)
imgpath = "./png/test_{}.png".format(time.strftime("%Y%m%d%H%M%S"))
driver.get_screenshot_as_file(imgpath) #需要提前创建目录
time.sleep(3)
driver.quit()

验证码处理

使用cookie跳过登录

方法

1. driver.get_cookies()				获取本网站所有本地cookie
2. driver.get_cookie(name)			获取指定cookie
3. driver.add_cookie(cookie_dict)	添加cookie

代码

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://www.baidu.com")
driver.add_cookie({'name':'','value':''})
time.sleep(3)
# 刷新
driver.refresh()
time.sleep(3)
driver.quit()

pytest

断言

让程序代替人工判断测试脚本执行结构是否符合预期的过程

方法

1. assert xx		判断 xx 是否为真
2. assert not xx 	判断 xx 不为真
3. assert a in b	判断 b 包含 a
4. assert a==b		判断 a 等于 b
5. assert a != b	判断 a 不等于 b

代码

def add(x, y):
    return x + y

class TestPlus:
    # 判断 1+1 等于 2
    def test_a(self):
        assert 2 == add(1, 1)

    # 判断 1+2 !等于 4
    def test_b(self):
        assert 4 != add(1, 1)

setup和teardow

概念

1. setup()		初始化
2. teardown()	销毁
# 用于初始化和清理测试环境,可以保证测试用例的独立性。
# pytest的setup/teardown方法包括:
 	1.模块级别(setup_module/teardown_module)
	2.函数级别(setup_function/teardown_function)
	3.类级别(setup_class/ teardown_class)
 	4.方法级别(setup_method/teardown_methond)

代码

import time


def add(x, y):
    return x + y

class TestPlus:
    # 获取并打印开始时间, 初始化,作用于方法,每一个测试方法执行前都打印一次
    def setup_method(self):
        print("start-time",time.time())
        # 获取并打印开始时间, 初始化,作用于方法,每一个测试方法执行后都打印一次
    def teardown_method(self):
        print("end-time",time.time())
    # 判断 1+1 等于 2
    def test_a(self):
        assert 2 == add(1, 1)

    # 判断 1+2 !等于 4
    def test_b(self):
        assert 4 != add(1, 1)

配置文件

应用场景

使用配置文件,可以通过配置项来选择执行哪些目录下的哪些测试模块

步骤

  1. 在项目下新建一个scripts模块

  2. 测试脚本放置在scripts中

  3. 项目根目录下新建一个pytest.ini配置文件

  4. 配置文件内容,第一行必须是[pytest]

  5. 命令行运行时,会使用该配置文件中的配置

文件内容

pytest.ini:

[pytest]
; 命令行参数
addopts = -s --html=./repost/repost.html
; 测试脚本目录名
testpaths = ./scripts
; 测试脚本文件名以什么开头
python_files = test_*.py
; 测试类名以什么开头
python_classes = Test*
; 测试方法名以什么开头
python_functions = test_*

数据参数化

应用场景

需要测试多组值的时候,使用数据参数化可以使代码更简洁,可读性更好

使用方法

使用注解,作用于方法上
@pytest.mark.parametize(key,value)
	key	  : 参数名
	value : 参数值,类型必须是可迭代类型,一般使用列表 list

单一参数

import pytest

class TestDemo:
    @pytest.mark.parametrize("name",["zhangsan","lisi"])
    def test_a(self,name):
        print(name)

多个参数

import pytest

class TestDemo:
    @pytest.mark.parametrize(
        ("username","password"),[("zhangsan","111111"),("list","222222")]
    )
    def test_a(self,username,password):
        print(username,password)

推荐用法

import pytest

class TestDemo:
    @pytest.mark.parametrize(
       "param",[{"username":"zhangsan","password":"111111"},{"username":"lisi","password":"222222"}]
    )
    def test_a(self,param):
        print(param["username"],param["password"])

PO模式

PO模式介绍

PO是Page Object 的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一

核心思想 :

  • 通过对页面元素的封装,减少冗余代码,同时在后期维护中,若元素发生变化,只需要调整页面元素封装的代码即可,提高了测试用例的可维护性,可读写

  • 页面和测试分离

PO模式分层

介绍

分层机制,让不同层去做不同类型的事情,让代码结果清晰,增加复用性

分层方式

  1. 两层

    • 对象操作层 : 封装页面信息,包括元素以及元素的操作

      • 业务数据层 : 封装多种操作组合的业务已经测试数据

  2. 三层

    • 对象库 :

    • 操作层

    • 业务数据层

  3. 四层

    • 对象库

    • 操作层

    • 业务层

    • 数据层

对比

  • 引入PO之前

    • 存在大量冗余代码

    • 业务流程不清晰

    • 后期维护成本大

  • 引入PO之后

    • 减少冗余代码

    • 业务代码和测试数据被分开,降低耦合性

    • 维护成本低

1. V1

介绍

无模块

不使用任何设计模式和单元测试框架

每个文件编写一个用例,完全的面向过程的编程方式

代码

# 登录功能 账号不存在
import time

# 实例化浏览器驱动
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://192.168.10.139/Home/Index/index.html")

# 执行用例
driver.find_element(By.CLASS_NAME,"red").click()
driver.find_element(By.ID,"username").send_keys("15274004447")
driver.find_element(By.ID,"password").send_keys("123456")
driver.find_element(By.ID,"verify_code").send_keys("8888")
driver.find_element(By.CLASS_NAME,"J-login-submit").click()
message = driver.find_element(By.CSS_SELECTOR,".layui-layer-content").text
print(message)
# 关闭浏览器驱动
time.sleep(3)
driver.quit()
# 登录功能 密码错误
import time

# 实例化浏览器驱动
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
driver.get("http://192.168.10.139/Home/Index/index.html")

# 执行用例
driver.find_element(By.CLASS_NAME,"red").click()
driver.find_element(By.ID,"username").send_keys("15274004447")
driver.find_element(By.ID,"password").send_keys("123456")
driver.find_element(By.ID,"verify_code").send_keys("8888")
driver.find_element(By.CLASS_NAME,"J-login-submit").click()
message = driver.find_element(By.CSS_SELECTOR,".layui-layer-content").text
print(message)
# 关闭浏览器驱动
time.sleep(3)
driver.quit()

存在的问题

  1. 一条测试用例对应一个文件,用例多时,不方便维护管理

  2. 代码高度冗余

2. V2

介绍

引入pytest管理测试用例

  1. 方便组织和管理多个测试用例

  2. 提供了丰富的断言方法

  3. 方便生成测试报告

  4. 减少了代码冗余

代码

# 导包
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 定义测试类
class TestLogin():
    def setup_method(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.implicitly_wait(10)
        self.driver.get("http://192.168.10.139/Home/Index/index.html")

    def teardown_method(self):
        time.sleep(3)
        self.driver.quit()

    # 定义账号不存在的方法
    def test_login_account_not_exist(self):
        self.driver.find_element(By.CLASS_NAME, "red").click()
        self.driver.find_element(By.ID, "username").send_keys("13454564565")
        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.CLASS_NAME, "J-login-submit").click()
        message = self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        assert "账号不存在!" == message
    # 定义密码错误的方法
    def test_login_password_error(self):
        self.driver.find_element(By.CLASS_NAME, "red").click()
        self.driver.find_element(By.ID, "username").send_keys("15274004447")
        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.CLASS_NAME, "J-login-submit").click()
        message = self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        assert "密码错误!" == message

存在的问题

代码冗余

3. V3

介绍

使用方法封装的思想,对代码进行优化

概念

将一些有共性的或者多次被使用的代码提取到一个方法中,供其他地方调用

好处

  1. 避免代码冗余

  2. 容易维护

  3. 隐藏代码实现细节

代码

驱动工具栏
# 获取/关闭浏览器驱动的类
from selenium import webdriver

from v1.test_login_account_not_exist import driver


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
测试类
# 导包
import time
from selenium.webdriver.common.by import By

from v3.driver_utils import DriverUtils


# 定义测试类
class TestLogin():
    def setup_method(self):
        self.driver = DriverUtils.get_driver()
        self.driver.get("http://192.168.10.139/Home/Index/index.html")

    def teardown_method(self):
        time.sleep(3)
        DriverUtils.quit_driver()

    # 定义账号不存在的方法
    def test_login_account_not_exist(self):
        self.driver.find_element(By.CLASS_NAME, "red").click()
        self.driver.find_element(By.ID, "username").send_keys("13454564565")
        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.CLASS_NAME, "J-login-submit").click()
        message = self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        assert "账号不存在!" == message
    # 定义密码错误的方法
    def test_login_password_error(self):
        self.driver.find_element(By.CLASS_NAME, "red").click()
        self.driver.find_element(By.ID, "username").send_keys("15274004447")
        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.CLASS_NAME, "J-login-submit").click()
        message = self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        assert "密码错误!" == message

存在的问题

代码还是冗余

4. V4

介绍

采用PO模式的分层思想对代码进行拆分,分离Page

代码

utils

driver_utils.py

# 获取/关闭浏览器驱动的类
from selenium import webdriver

from v1.test_login_account_not_exist import driver


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
page

login_page.py

from selenium.webdriver.common.by import By


class LoginPage:
    def __init__(self, driver):
        self.driver = driver
    def click_login_link(self):
        return self.driver.find_element(By.CLASS_NAME, "red").click()

    def input_username(self, username):
        return self.driver.find_element(By.ID, "username").send_keys(username)

    def input_password(self, password):
        return self.driver.find_element(By.ID, "password").send_keys(password)

    def input_verify_code(self, code):
        return  self.driver.find_element(By.ID, "verify_code").send_keys(code)

    def click_login_button(self):
        return self.driver.find_element(By.CLASS_NAME, "J-login-submit").click()

    def get_message(self):
        message = self.driver.find_element(By.CSS_SELECTOR, ".layui-layer-content").text
        return message
scripts

test_login.py

# 导包
import time
from selenium.webdriver.common.by import By

from v1.test_login_account_not_exist import message
from v4.page.login_page import LoginPage
from v4.utils.driver_utils import DriverUtils


# 定义测试类
class TestLogin():
    def setup_method(self):
        self.driver = DriverUtils.get_driver()
        self.login_page = LoginPage(self.driver)
        self.driver.get("http://192.168.10.139/Home/Index/index.html")

    def teardown_method(self):
        time.sleep(3)
        DriverUtils.quit_driver()

    # 定义账号不存在的方法
    def test_login_account_not_exist(self):
        self.login_page.click_login_link();
        self.login_page.input_username("12345652456")
        self.login_page.input_password("123456")
        self.login_page.input_verify_code("8888")
        self.login_page.click_login_button()
        assert "账号不存在!" == self.login_page.get_message()
    # 定义密码错误的方法
    def test_login_password_error(self):
        self.login_page.click_login_link()
        self.login_page.input_username("15274004447")
        self.login_page.input_password("123456")
        self.login_page.input_verify_code("8888")
        self.login_page.click_login_button()
        assert "密码错误!" == self.login_page.get_message()

5. V5

介绍

对PO分层之后的代码继续优化,分离Page中的元素和操作

优化内容

  • 分离page页面中的元素和操作

  • 优化元素定位的方法

6. V6

介绍

PO模式深入封装,把共同的操作提取封装

优化内容

  • 封装操作基类

    • 封装查找元素的方法

    • 封装对元素的操作方法:点击\清空\输入

  • page继承操作基类

数据驱动

概念

以数据来驱动整个测试用例的执行,也就是测试数据决定测试结果

特点

  • 数据驱动是一种模式或者一种思想

  • 数据驱动技术可以让用户把关注点放在对测试数据的构建和维护上,而不是直接维护测试脚本,可以利用同样的业务流程,对不同的输入数据进行测试

  • 数据驱动的实现要依赖参数化的技术

操作

JSON转换

import json
dict = {
    "name" : "zhangsan",
    "age" : 18,
    "is_man" : True,
    "school" : None
}

# json格式化
# python字典转换成 json字符串
json_str = json.dumps(dict)
print(json_str)


json_str2 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}'
# json字符串转换成 python字典
print(json.loads(json_str2))

JSON读写

# 读取json文件
with open("data.json","r",encoding="utf8") as f
	data1 = json.load(f)

# 写入json文件
data2 = data1
with open("data2.json","w",encoding="utf8") as f
	data1 = json.load(data2,f,ensure_ascii=False)

日志收集

概述

概念:日志就是用于记录系统运行时的信息,也叫Log

作用:

  • 调试程序

  • 了解程序运行的情况是否正常

  • 程序运行故障分析与问题定位

  • 用来做用户行为分析和数据统计

级别

  • debug

  • info

  • warning

  • error

  • critical

基本用法

  • logging:

    • python中有一个标准库,logging模块可以直接记录日志

    • 默认为warning,大于或等于warning的才会输出

  • 使用

    • 导入logging包

    • 输出日志

代码

import logging
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')

设置日志级别

# 设置日志级别
logging.basicConfig(level=logging.DEBUG)

怎么选择日志级别?

  • 在开发和测试环境中,为了尽可能详细的查看程序的运行状态,可以使用DEBUG或INFO级别的日志获取详细的日志信息,但非常消耗计算机的性能

  • 在生产环境中,通常只记录程序的异常和错误信息,设置日志级别为 WARNING 和 ERROR 即可,这样减少服务器的压力,也方便问题的排查

设置日志格式

默认格式

日志级别 : Logger名称 :日志内容

自定义方式
fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logging.basicConfig(level=logging.DEBUG,format=fmt)

输出到文件

logging。basicConfig(filename="文件名.log")

高级用法

四大组件

介绍
  • 日志器

    • Logger : 提供了程序使用日志的入口

  • 处理器

    • Handler : 将logger创建的日志记录发送到合适的输出

  • 格式器

    • Formatter : 决定日志的输出格式

  • 过滤器

    • Filter : 过滤掉不想要的日志记录

组件之间的关系

  • 日志器需要通过处理器将日志信息输出到目标位置

  • 不同的处理器可以将日志输出到不同位置

  • 日志器可以设置多个处理器,将同一条日志记录输出到不同的位置

  • 每个处理器都可以设置自己的格式器实现同一条日志以不同的格式输出

  • 每个处理器都可以设置自己的过滤器实现日志过滤

Logger类

如何创建
logger = logging.getLogger(name)	name为日志器名称,可以自定义
常用方法
# 打印日志
logger.debug()
logger.info()
logger.warning()
logger.error()
logger.critiacl()
# 设置日志级别
logger.setLevel()
# 为logger对象添加一个handler对象
logger.addHandler()
# 为logger对象添加一个filter对象
logger.addFilter()

Handler类

如何创建

在程序中不应该直接实例化和使用Handler实例,因为Handler是一个接口,它只定义了Handler应该有的方法,应该使用Handler实现类来创建对象

# 输出日志到控制台
logger.StreamHandler
# 输出到本地硬盘,按时间切割
logger.handlers.TimedRotatingFileHandler
常用方法
# 为handler设置格式器对象
handler.setFormatter()

Formatter类

如何创建
logger.Formatter(fmt=None,datefmt=None)
    fmt: 消息字符串格式化,有默认值,可以指定
    datefmt: 日期字符串格式化,使用 %Y-%m-%d

示例代码

import logging
import logging.handlers
# 创建日志器对象,设置日志级别
# logger = logging.getLogger()
logger = logging.getLogger("Loong")
logger.setLevel = logging.DEBUG
# 创建处理器,输出到控制台 + 文件 ,按时间切割
ls = logging.StreamHandler()
lf = logging.handlers.TimedRotatingFileHandler(filename="local.log",when="s",backupCount=3)
# 创建格式器
formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 将格式器添加到处理器
ls.setFormatter(formatter)
lf.setFormatter(formatter)
# 将处理器添加到日志器
logger.addHandler(ls)
logger.addHandler(lf)
# 打印日志
while 1:
    logger.debug("aaaaaaaaa")

项目实战

自动化测试流程

1. 需求分析
2. 挑选适合做自动化测试的功能
	人力不充足: 选择冒烟测试相关用例
	人力充足: 可以考虑100%执行功能测试的用例
3. 设计筛选测试用例,并且评审
4. 搭建自动化测试环境[可选]
5. 设计自动化测试项目的架构[可选]
6. 编写代码
7. 执行测试用例
8. 生成测试报告并分析结果

测试用例编写规则

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

编写测试用例

image-20240922113056704

pytest-高级用法