Web的UI自动化基础知识
Anthony_231 2024-07-20 11:33:01 阅读 60
目录
1 Web自动化入门基础1.1 自动化知识以及工具1.2 主流web自动化测试工具1.3 入门案例
2 使用工具的API2.1 元素定位2.1.1 id选择器2.1.2 name2.1.3 class_name选择器2.1.4 tag_name选择器2.1.5 link_text选择器2.1.6 partial_link_text选择器2.1.7 xpath选择器2.1.8 CSS选择器2.1.9 Xpath和CSS区别2.1.10 元素定位分类2.1.11 元素定位的另一种写法
2.2 元素操作2.3 浏览器操作2.4 获取元素信息2.5 鼠标操作2.5.1 常用方法2.5.2 执行的方法2.5.3 鼠标右击2.5.4 鼠标双击2.5.5 鼠标悬停2.5.6 鼠标拖动
2.6 键盘操作2.6.1 常用操作2.6.2 键盘操作2.6.3 元素等待2.6.4 隐式等待2.6.5 显式等待2.6.6 隐式和显式区别2.6.7 下拉框2.6.8 弹出框分类
2.7 滚动条实现方法2.8 frame切换2.8.1 多窗口切换2.8.2 窗口截图
2.9 验证码处理2.10 cookie
3 Pytest框架3.1 总体介绍3.2 断言方法3.3 setup和teardown3.4 配置文件3.5 测试报告插件3. 6 数据参数化3.6.1 单一参数3.6.2 多个参数3.6.3 推荐用法
4 PO模式4.1 递进学习路线4.2 无模式4.2.1 案例说明4.2.2 选择测试用例
4.3 V1版本4.4 V2版本4.4.1 方法封装
4.5 V3版本4.6 PO模式4.6.1 概念4.6.2 PO模式分层4.6.3 PO模式优点
4.7 V4版本4.8 v5版本4.9 v6版本4.9.1 示例代码
5 数据驱动5.1 JSON基本介绍5.2 字典与JSON转换5.3 JSON文件读写
6 项目实战6.1 项目结构6.2 base包6.3 data包6.4 page包6.5 scripts包6.6 utils包
7 日志收集7.1 日志收集7.2 日志高级用法7.3 四大组件7.3.1 Logger类7.3.2 Handler类7.3.3 Formatter类
8 面试题
1 Web自动化入门基础
1.1 自动化知识以及工具
自动化概念 :由机器设备代替人工自动完成指定目标的过程
优点:
减少人工劳动力提高工作效率产品规格统一标准规模化安全
自动化测试概念 :由程序代替人工去执行测试的过程
应用场景 :
解决回归测试
已实现的功能需要回归已解决的bug需要回归 解决压力测试:例如使用Jmeter做接口自动化解决兼容性测试:在不同浏览器上做兼容性测试解决操作重复性问题
1.2 主流web自动化测试工具
QTP :收费且支持web/桌面自动化测试selenium:开源web自动化测试工具(功能测试)【跨平台、支持多种浏览器、支持多种语言、稳定】robot framework :基于python的可扩展的关键字驱动的自动化测试框架
1.3 入门案例
<code># 导包
import time
from selenium import webdriver
# 创建浏览器驱动
driver = webdriver.Chrome()
# 打开百度首页
driver.get("http://www.taobao.com")
# 暂停3秒
time.sleep(10)
# 关闭浏览器
driver.quit()
2 使用工具的API
2.1 元素定位
八种定位方式 :
2.1.1 id选择器
案例:
打开https://parabank.parasoft.com/parabank/admin.htm网站首页,完成以下操作
最大化页面
使用ID定位,输入用户名:admin
使用ID定位,输入密码:12345
等待5s,关闭浏览器
<code>import time
from selenium import webdriver
# 获取驱动对象
driver = webdriver.Chrome()
# 获取地址
driver.get("https://parabank.parasoft.com/parabank/admin.htm")
# 页面最大化
driver.maximize_window()
# 根据id查找元素
driver.find_element_by_id('username').send_key("admin")
driver.find_element_by_id('password').send_key("123456")
# 等待五秒
time.sleep(5)
# 关闭浏览器
driver.quit()
2.1.2 name
案例:
打开https://parabank.parasoft.com/parabank/admin.htm 网站首页,完成以下操作
最大化页面
使用name定位,输入用户名:admin
使用name定位,输入密码:12345
等待5s,关闭浏览器
import time
from selenium import webdriver
# 获取驱动对象
driver = webdriver.Chrome()
# 获取地址
driver.get("https://parabank.parasoft.com/parabank/admin.htm")
# 页面最大化
driver.maximize_window()
# 根据id查找元素
driver.find_element_by_name('username').send_key("admin")
driver.find_element_by_name('password').send_key("123456")
# 等待五秒
time.sleep(5)
# 关闭浏览器
driver.quit()
2.1.3 class_name选择器
案例:
打开https://parabank.parasoft.com/parabank/admin.htm网站首页,完成以下操作
最大化页面
使用class_name定位,输入用户名:admin
使用class_name定位,输入密码:12345
等待5s,关闭浏览器
import time
from selenium import webdriver
# 获取驱动对象
driver = webdriver.Chrome()
# 获取地址
driver.get("https://parabank.parasoft.com/parabank/admin.htm")
# 页面最大化
driver.maximize_window()
# 根据id查找元素
driver.find_element_by_class_name('username').send_key("admin")
driver.find_element_by_class_name('password').send_key("123456")
# 等待五秒
time.sleep(5)
# 关闭浏览器
driver.quit()
2.1.4 tag_name选择器
# 1.导包
import time
from selenium import webdriver
# 2.创建浏览器驱动对象
driver = webdriver.Chrome()
# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_tag_name("input").send_keys("xxxxxx")
# 4.暂停5秒
time.sleep(5)
# 5.关闭驱动对象
driver.quit()
2.1.5 link_text选择器
# 1.导包
import time
from selenium import webdriver
# 2.创建浏览器驱动对象
driver = webdriver.Chrome()
# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_link_text("访问 新浪 网站").click()
# 4.暂停5秒
time.sleep(5)
# 5.关闭驱动对象
driver.quit()
2.1.6 partial_link_text选择器
# 1.导包
import time
from selenium import webdriver
# 2.创建浏览器驱动对象
driver = webdriver.Chrome()
# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# driver.find_element_by_partial_link_text("访问 新浪 网站").click() # 通过全部文本定位超链接
driver.find_element_by_partial_link_text("访问").click() # 通过局部文本定位超链接
# 4.暂停5秒
time.sleep(5)
# 5.关闭驱动对象
driver.quit()
定位一组元素
# 1.导包
import time
from selenium import webdriver
# 2.创建浏览器驱动对象
driver = webdriver.Chrome()
# 3.业务操作
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
elements = driver.find_elements_by_tag_name("input")
elements[1].send_keys("123456")
# 4.暂停5秒
time.sleep(5)
# 5.关闭驱动对象
driver.quit()
2.1.7 xpath选择器
四种定位方式
路径元素属性属性与逻辑结合层级与属性结合
方法
element = driver。find_element_by_xpath(xpath)
路径
绝对路径:
从外层元素到指定元素之间所有经过元素层级的路径绝对路径以/html根节点开始,使用/来分割元素层级,如:/html/body/div/fieldset/p[1]/input绝对路径对页面要求严格,不建议使用 相对路径
匹配任意层级的元素,不限制元素的位置相对路径//开始格式://input 或者 //*
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 定位用户名输入框, 输入 admin
driver.find_element_by_xpath("/html/body/div/fieldset/form/p[1]/input").send_keys("admin")
# 暂停3s
time.sleep(3)
# 定位密码输入框, 输入 123
driver.find_element_by_xpath("//*[@id='passwordA']").send_keys("123")code>
time.sleep(5)
driver.close()
使用谷歌浏览器获取 XPath 表达式的过程:
元素上右键 -> 检查
在F12对应的文档中的对应元素上 右键 -> Copy -> Copy XPath 或者 Copy full XPath
使用函数
不使用函数时:
//*[@id='xxx']code>
使用函数后
//*[text()='xxx'] 文本内容是 xxx 的元素
//*[contains(@attribute, 'xxx')] 属性中含有 xxx 值的元素
//*[starts-with(@attribute, 'xxx')] 属性以xxx开头的元素
案例
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 利用元素属性通过XPath 定位用户名输入框, 并输入 admin
# driver.find_element_by_xpath("//*[@name='userA']").send_keys("admin")code>
# driver.find_element_by_xpath("//*[@id='userA']").send_keys("admin")code>
# driver.find_element_by_xpath("//*[@placeholder='请输入用户名']").send_keys("admin")code>
driver.find_element_by_xpath("//*[@type='text']").send_keys("admin")code>
time.sleep(5)
driver.close()
2.1.8 CSS选择器
常用的定位方式
id选择器class选择器元素选择器属性选择器层级选择器
方法
element = driver.find_element_by_css_selector(css表达式)
id
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面, 使用CSS定位方式中的id选择器, 定位用户名输入框, 并输入 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector("#userA").send_keys("admin")
time.sleep(5)
driver.close()
class
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面, 使用CSS定位方式中的class选择器, 定位电话号码输入框, 并输入 13100000000
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector(".telA").send_keys("13100000000")
time.sleep(5)
driver.close()
元素选择器
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面, 使用CSS定位方式中的元素选择器, 定位注册按钮, 并点击
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector("button").click()
time.sleep(5)
driver.close()
属性选择器
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面, 使用CSS定位方式中的属性选择器, 定位密码输入框, 并输入 123456
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
driver.find_element_by_css_selector("[type='password']").send_keys("123456")code>
time.sleep(5)
driver.close()
层级选择器
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面, 使用CSS定位方式中的层级选择器, 定位用户名输入框, 并输入 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# driver.find_element_by_css_selector("p[id='pa']>input").send_keys("admin")code>
driver.find_element_by_css_selector("div[class='zc'] input").send_keys("admin")code>
time.sleep(5)
driver.close()
CSS扩展
input[type^=‘p’] type属性以p字母开头的元素input[type$=‘d’] type属性以d字母结束的元素input[type*=‘w’] type属性包含w字母的元素
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面, 使用CSS定位扩展的方式, 定位用户名输入框, 并输入 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# driver.find_element_by_css_selector("input[type^='t']").send_keys("admin")
# driver.find_element_by_css_selector("input[name^='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")
time.sleep(5)
driver.close()
2.1.9 Xpath和CSS区别
XPath和CSS对比
通过标签名定位
XPath
//input
CSS
input
通过id属性定位
XPath
//*[@id='userA']code>
CSS
#userA
通过class属性定位
XPath
//*[@class='telA']code>
CSS
.telA
通过其他属性定位
XPath
//*[starts-with(@type,'x')]
以x字母开头的type值的元素
//*[contains(@type, 'x')]
包含x字母的type值的元素
//*[text()='x']
文本内容为 x 的元素
CSS
[type^='x']
以x字母开头的type值的元素
[type*='x']
包含x字母的type值的元素
[type$='x']
以x字母结尾的type值的元素
2.1.10 元素定位分类
id, name, class_name: 元素属性定位tag_name: 元素标签名定位link_text, partial_link_text: 通过文本定位超链接XPath: 通过路径定位元素CSS: 使用CSS选择器定位
2.1.11 元素定位的另一种写法
方法
方法: driver.find_element(方式, 值)
备注:
需要2个参数, 第1个参数为定位的类型(由By提供), 第2个参数传入具体的值如果要使用By, 需要导包
示例
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 八中定位方法都适用"另一种方法"
# driver.find_element(By.ID, "userA").send_keys("admin")
driver.find_element(By.XPATH, "//*[@placeholder='请输入电子邮箱']").send_keys("123456@qq.com")code>
time.sleep(5)
driver.close()
2.2 元素操作
方法
click()
单击元素
send_keys()
模拟输入
clear()
清除文本
案例
需求:打开注册A页面,完成以下操作
通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com
间隔3秒,修改电话号码为:18600000000
间隔3秒,点击‘注册’按钮
间隔3秒,关闭浏览器
注意:元素定位方法不限
代码
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
# 打开注册A页面,完成以下操作
# 1.通过脚本执行输入用户名:admin;密码:123456;电话号码:18611111111;电子邮件:123@qq.com
# 2.间隔3秒,修改电话号码为:18600000000
# 3.间隔3秒,点击‘注册’按钮
# 4.间隔3秒,关闭浏览器
# ps: 元素定位方法不限
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("userA").send_keys("admin")
driver.find_element_by_id("passwordA").send_keys("123456")
driver.find_element_by_id("telA").send_keys("18611111111")
driver.find_element_by_name("emailA").send_keys("123@qq.com")
# 2
time.sleep(3)
driver.find_element_by_id("telA").clear()
driver.find_element_by_id("telA").send_keys("18600000000")
# 3
time.sleep(3)
driver.find_element_by_css_selector("body > div > fieldset > form > p:nth-child(5) > button").click()
# 4
time.sleep(3)
driver.close()
2.3 浏览器操作
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
## maximize_window() 浏览器窗口最大化
driver.maximize_window()
## set_window_size() 设置窗口大小(单位:像素点) set_window_position() 设置窗口的位置
driver.set_window_size(300, 300)
driver.set_window_position(300, 300)
## back() 后退 forward() 前进 refresh() 刷新
driver.back()
driver.forward()
time.sleep(3)
driver.refresh()
## title 获取页面标题 current_url 获取当前页面url
print("页面标题:", driver.title)
print("当前页面地址:", driver.current_url)
## driver.close() 关闭当前浏览器窗口 ==> 执行结果, 留下了新浪网站, 关闭了注册A页面
time.sleep(3)
driver.find_element_by_link_text("访问 新浪 网站").click()
time.sleep(3)
driver.close()
### 序号 30~48 的脚本应该使用 driver.quit() 关闭浏览器驱动 而不是 driver.close()
## driver.quit() 关闭浏览器驱动对象(关闭浏览器) ==> 执行结果, 关闭所有窗口, 关闭浏览器驱动
time.sleep(3)
driver.find_element_by_link_text("访问 新浪 网站").click()
time.sleep(3)
driver.quit()
2.4 获取元素信息
应用场景
用于校验, 判断定位的元素是否准确
常用方法
size
返回元素大小
text
获取元素文本
get_attribute("xxx")
获取属性值, 参数是元素的属性名
is_displayed()
判断元素是否可见
is_enabled()
判断元素是否可用
is_selected()
判断元素是否选中, 用来检查复选框或单选按钮
案例
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 需求: 打开A页面, 完成以下操作:
# 1.获取用户名输入框的大小
print(driver.find_element_by_id("userA").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())
time.sleep(3)
driver.quit()
2.5 鼠标操作
什么是鼠标操作
单击, 右击, 双击, 悬停, 拖拽等
为什么要用到鼠标操作
现在web产品中存在丰富的鼠标交互方式, 作为一个web自动化测试框架, 需要应对这些鼠标操作的场景
2.5.1 常用方法
说明: 在Selenium中将鼠标操作的方法封装在 ActionChains 类中
实例化对象: action = ActionChains(driver)
方法:
context_click(element) 右击
double_click(element) 双击
move_to_element(element) 悬停
drag_and_drop(source, target) 拖拽
perform() 执行
2.5.2 执行的方法
说明: 在 ActionChains 类中所有提供的鼠标事件方法, 在调用的时候, 所有行为都存储在 ActionChains 对象中, 而 perform() 方法就是真正去执行所有的鼠标事件
强调: 必须调用 perform() 方法才能执行鼠标事件
2.5.3 鼠标右击
说明: 对于点击鼠标右键, 如果弹出的是浏览器的默认菜单, Selenium并没有提供操作菜单的方法
如果是自定义的右键菜单, 则可以通过元素定位来操作菜单中的选项
需求: 打开A页面, 在用户名文本框上点击鼠标右键
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
# 需求: 打开A页面, 在用户名文本框上点击鼠标右键
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 定位用户名输入框
element = driver.find_element_by_id("userA")
# 执行右键点击操作
action = ActionChains(driver)
action.context_click(element).perform()
time.sleep(3)
driver.quit()
2.5.4 鼠标双击
说明: 模拟鼠标双击左键的操作
需求: 打开A页面, 输入用户名 admin, 暂停3s, 双击鼠标左键(选中admin)
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
# 需求: 打开A页面, 输入用户名 admin, 暂停3s, 双击鼠标左键(选中admin)
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
element = driver.find_element_by_id("userA")
element.send_keys("admin")
time.sleep(3)
action = ActionChains(driver)
action.double_click(element).perform()
time.sleep(3)
driver.quit()
2.5.5 鼠标悬停
说明: 模拟鼠标悬停在指定元素上
需求: 打开A页面, 模拟鼠标悬停在 注册 按钮上
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
# 需求: 打开A页面, 模拟鼠标悬停在 注册 按钮上
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
element = driver.find_element_by_tag_name("button")
action = ActionChains(driver)
action.move_to_element(element).perform()
time.sleep(3)
driver.quit()
2.5.6 鼠标拖动
说明: 模拟鼠标拖动动作, 选定拖动源元素释放到目标元素
源元素 source = driver.find_element_by_xxx("xxx")
目标元素 target = driver.find_element_by_xxx("xxx")
调用方法 action.drag_and_drop(source, target).perform()
需求: 打开 drag.html 页面, 把红色方框拖动到蓝色方框上
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
driver = webdriver.Chrome()
# 需求: 打开 drag.html 页面, 把红色方框拖动到蓝色方框上
driver.get("file:///C:/Users/57769/Desktop/pagetest/drag.html")
red = driver.find_element_by_id("div1")
blue = driver.find_element_by_id("div2")
ActionChains(driver).drag_and_drop(red, blue).perform()
time.sleep(3)
driver.quit()
2.6 键盘操作
说明:
模拟键盘上的一些按键或者组合键的输入, 如: 复制/粘贴Selenium中把键盘的按键都封装在 Keys 类中
2.6.1 常用操作
导包
send_keys(Keys.BACK_SPACE)
删除键(Backspace)send_keys(Keys.SPACE)
空格键(Space)send_keys(Keys.TAB)
制表键(Tab)send_keys(Keys.ESCAPE)
回退键(ESC)send_keys(Keys.ENTER)
回车键(Enter)send_keys(Keys.CONTROL, 'a')
全选(Ctrl + A)send_keys(Keys.CONTROL, 'c')
复制(Ctrl + C)
提示: 以上方法很多, 不会逐一讲解, 因为调用方法都一样
2.6.2 键盘操作
需求
打开 A 页面, 完成以下操作
输入用户名 admin1, 暂停2s, 删除1全选用户名 admin 暂停2s复制用户名 admin 暂停2s粘贴到电话输入框
代码
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1. 输入用户名 admin1, 暂停2s, 删除1
element = driver.find_element_by_id("userA")
element.send_keys("admin1")
time.sleep(2)
element.send_keys(Keys.BACK_SPACE)
# 2. 全选用户名 admin 暂停2s
element.send_keys(Keys.CONTROL, "a")
time.sleep(2)
# 3. 复制用户名 admin 暂停2s
element.send_keys(Keys.CONTROL, "c")
time.sleep(2)
# 4. 粘贴到电话输入框
driver.find_element_by_id("telA").send_keys(Keys.CONTROL, "v")
time.sleep(5)
driver.quit()
2.6.3 元素等待
概念
定位页面元素, 如果未找到, 在指定时间内一直等待的过程
分类
隐式等待显式等待
由于一些原因, 我们想找的元素并没有立刻出来, 此时直接定位会报错, 场景如下:
网络速度慢服务器计算慢硬件配置差
思考: 是否定位每个元素时, 都需要元素等待?
2.6.4 隐式等待
方法
隐式等待为全局设置 (只需要设置1次,会作用于所有元素)
参数:
timeout
: 超时的时长, 单位: 秒
driver.implicitly_wait(timeout)
案例
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 需求: 打开A页面, 使用隐式等待定位 "延时加载的输入框", 并输入 admin
driver.implicitly_wait(10)
driver.find_element_by_css_selector("input[placeholder='延时加载的输入框']").send_keys("admin")code>
time.sleep(3)
driver.quit()
# 不使用元素等待时, 如果找不到元素会报 NoSuchElementException 异常
# 使用隐式等待时, 如果找不到元素会报 NoSuchElementException 异常
注意点
单个元素定位超时会报 NoSuchElementException
2.6.5 显式等待
说明: 在Selenium中把显式等待的相关方法封装在 WebDriverWait 类中
方法 :
显式等待, 为定位不同的元素的超时时间设置不同的值
导包
WebDriverWait(driver, timeout, poll_frequency=0.5)
driver: 浏览器驱动对象timeout: 超时时长, 单位: 秒poll_frequency: 检测的间隔时间, 默认为0.5s
调用 until(method)
method: 函数名称, 该函数用来实现元素定位一般使用匿名来实现: lambda x: x.find_element_by_xxx("xxx")
如:element = WebDriverWait(driver,10,1).until(lambda x: x.find_element_by_xxx("xxx"))
案例
import time
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 需求: 打开A页面, 使用显式等待定位 "延时加载的输入框", 并输入 admin
wait = WebDriverWait(driver, 10, 1)
element = wait.until(lambda x: x.find_element_by_css_selector("input[placeholder='延时加载的输入框']"))code>
element.send_keys("admin")
time.sleep(3)
driver.quit()
# 单个元素定位超时会报错 TimeoutException
注意点
单个元素定位超时会报错
TimeoutException
2.6.6 隐式和显式区别
作用域: 隐式等待为全局有效, 显式等待为单个元素有效使用方法: 隐式等待直接通过驱动对象调用, 而显式等待方法封装在 WebDriverWait
类中达到最大超时时长后抛出异常不同: 隐式等待为 NoSuchElementException
, 显式等待为 TimeoutException
2.6.7 下拉框
案例
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面,完成以下下拉框操作
# 1. 暂停2s, 选择广州
# 2. 暂停2s, 选择上海
# 3. 暂停2s, 选择北京
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[3]").click()code>
# 2
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[2]").click()code>
# 3
time.sleep(2)
driver.find_element_by_xpath("//*[@id='selectA']/option[1]").click()code>
time.sleep(3)
driver.quit()
案例
说明: Select类是Selenium为操作select标签封装的
实例化对象:
select = Select(element)
element: <select>标签对应的元素, 通过元素定位方式获取
例如: driver.find_element_by_id("selectA")
操作方法:
select_by_index(index)
根据option索引来定位, 从0开始select_by_value(value)
根据option属性 value值来定位select_by_visible_text(text)
根据option显示文本内容来定位
步骤分析
导包实例化Select类 select = Select(driver.find_element_by_id("selectA"))
调用方法
案例
import time
from selenium import webdriver
from selenium.webdriver.support.select import Select
driver = webdriver.Chrome()
# 需求: 打开A页面,完成以下下拉框操作
# 1. 暂停2s, 选择广州
# 2. 暂停2s, 选择上海
# 3. 暂停2s, 选择北京
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
select = Select(driver.find_element_by_id("selectA"))
# 1
time.sleep(2)
select.select_by_index(2)
# 2
time.sleep(2)
select.select_by_value("sh")
# 3
time.sleep(2)
select.select_by_visible_text("北京")
time.sleep(3)
driver.quit()
2.6.8 弹出框分类
alert 警告框confirm 确认框prompt 提示框
弹出框的错误示范
错误代码
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面,完成以下弹出框操作
# 1.点击 alert 按钮
# 2.暂停2s, 输入用户名 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("alerta").click()
# 2
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")
time.sleep(3)
driver.quit()
# 思考
# 1.什么问题导致的?
# driver的焦点在弹出框页面, 并不在A页面, 无法为你输入admin(找不到用户名输入框)
# 2.如何处理弹出框?
弹出框方法
说明: Selenium中对弹出框的处理, 有专用的方法, 且处理的方法都一样(alert/confirm/prompt)
1.获取弹出框对象
alert = driver.switch_to.alert
2.调用
alert.text
返回alert/confirm/prompt文字信息
alert.accept()
接受对话框选项(确认)
alert.dismiss()
取消对话框选项(取消)
案例
正确代码
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面,完成以下弹出框操作
# 1.点击 alert 按钮
# 2.暂停2s, 输入用户名 admin
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("alerta").click()
time.sleep(2)
alert = driver.switch_to.alert
print(alert.text)
time.sleep(2)
alert.accept()
# 2
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")
time.sleep(3)
driver.quit()
2.7 滚动条实现方法
方法
说明: Selenium中没有提供滚动条的操作方法, 但是它提供了执行 JS 的方法, 所有我们可以通过 JS脚本来操作滚动条
设置 JS 脚本控制滚动条
js = "window.scrollTO(0,1000)"
(0:左边距, 1000:上边距 单位:像素(px))Selenium 调用执行 JS 脚本的方法
driver.execute_script(js)
案例
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开A页面
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# js1 滚动到最底部
js1 = "window.scrollTo(0, 10000)"
# js2 滚动到最顶部
js2 = "window.scrollTo(0, 0)"
# 执行第一个脚本
time.sleep(2)
driver.execute_script(js1)
# 执行第二个脚本
time.sleep(2)
driver.execute_script(js2)
time.sleep(3)
driver.quit()
2.8 frame切换
概念
frame : html页面中的一种框架, 主要作用是在当前页面指定区域显示另一个页面元素
形式一:
<frameset cols="25%,75%">code>
<frame src="a.html">code>
<frame src="b.html">code>
</frameset>
形式二:
<iframe name="iframe_a" src="demo.html" width="200" height="200"></iframe>code>
方法
说明: 在Selenium中封装了如何切换frame框架的方法
步骤:
1.driver.switch_to.frame(frame_reference)
切换到指定frame
frame_reference
: 可以传frame框架的id,name,定位的frame元素
2.driver.switch_to.default_content()
恢复默认页面
必须回到默认页面才能进一步操作
解决方案
在主页面输入用户名 admin切换到A页面, 再输入用户名 adminA恢复默认页面切换到B页面, 再输入用户名 adminB
正确代码
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开"注册实例"页面
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8C%E5%AE%9E%E4%BE%8B.html")
# 1.填写主页面的用户名 admin
time.sleep(2)
driver.find_element_by_id("userA").send_keys("admin")
# 2.填写注册页面A中的用户名 adminA
time.sleep(2)
# driver.switch_to.frame("idframe1") # 从主页面, 切换到了A页面, 通过 id
# driver.switch_to.frame("myframe1") # 从主页面, 切换到了A页面, 通过 name
driver.switch_to.frame(driver.find_element_by_id("idframe1")) # 从主页面, 切换到了A页面, 通过 定位到的元素
driver.find_element_by_id("userA").send_keys("adminA")
# 3.回到主页面
time.sleep(1)
driver.switch_to.default_content()
# 4.填写注册页面B中的用户名 adminB
time.sleep(1)
driver.switch_to.frame("idframe2") # 从主页面, 切换到B页面
driver.find_element_by_id("userA").send_keys("adminB")
time.sleep(3)
driver.quit()
2.8.1 多窗口切换
概念
什么是窗口? 窗口类似于浏览器中的标签页, 每个窗口就对应了一个标签页
为什么要切换窗口? 在html页面中, 当点击按钮或超链接时, 有的会在新窗口打开页面
如果点击按钮或超链接在当前窗口打开新页面, 就不需要切换窗口
需求
打开A页面
在新窗口打开新浪页面在新浪的搜索框输入"新浪搜索"在A页面输入用户名 admin
方法
说明: 在Selenium中封装了获取当前窗口句柄,获取所有窗口句柄和切换到指定句柄窗口的方法
句柄: 英文handle, 窗口的唯一识别码
方法:
1. `driver.current_window_handle`获取当前窗口句柄
2. `driver.window_handles`获取所有窗口句柄
3. `driver.switch_to.window(handle)`切换到指定句柄的窗口
对于需求的解决方案:
打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄根据句柄, 切换到新浪窗口, 对输入框输入 “新浪搜索”切换回原本窗口(A页面), 输入用户名 admin
注意: 新浪页面需要访问网络, 可能加载慢, 可能需要用到元素等待
代码
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 隐式等待10秒, 以防新浪窗口加载慢, 定位不到输入框
driver.implicitly_wait(10)
# 1. 打开A页面, 获取当前窗口句柄(拿到的是A页面的句柄)
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
print("当前A页面窗口句柄:", driver.current_window_handle)
# 2. 在A页面点击"访问 新浪 网站" 这个超链接, 获取所有窗口句柄
driver.find_element_by_id("fw").click()
handles = driver.window_handles
print("所有窗口句柄:", handles)
# 3. 根据句柄, 切换到新浪窗口, 对输入框输入 "新浪搜索"
driver.switch_to.window(handles[1])
time.sleep(1)
driver.find_element_by_class_name("inp-txt").clear()
time.sleep(1)
driver.find_element_by_class_name("inp-txt").send_keys("新浪搜索")
time.sleep(2)
# 4. 切换回原本窗口(A页面), 输入用户名 admin
driver.switch_to.window(handles[0])
driver.find_element_by_id("userA").send_keys("admin")
time.sleep(3)
driver.quit()
2.8.2 窗口截图
概念
什么是窗口截图?
把当前操作的页面, 截图保存到指定的位置
为什么要窗口截图?
有时候打印的错误信息不十分准确, 需要窗口截图辅助定位错误
方法
说明: 在Selenium中提供了截图方法, 我们只需要调用即可
方法:
driver.get_screenshot_as_file(imgpath)
imgpath: 图片保存路径 + 图片名
案例
import time
from selenium import webdriver
driver = webdriver.Chrome()
# 需求: 打开 A 页面, 完成以下操作
# 1.输入用户名 admin
# 2.截图保存
driver.get("file:///C:/Users/57769/Desktop/pagetest/%E6%B3%A8%E5%86%8CA.html")
# 1
driver.find_element_by_id("userA").send_keys("admin")
# 2
time.sleep(1)
# 每次都是用固定文件名, 会股改上一次生成的图片文件
# driver.get_screenshot_as_file("./png/123.png") # 需要提前创建 png 目录
# 使用时间去格式化文件名, 可以使每次截图保存的文件名都不同, 不会覆盖之前保存的文件, 更有效
imgpath = "./png/test_{}.png".format(time.strftime("%Y%m%d%H%M%S"))
driver.get_screenshot_as_file(imgpath)
time.sleep(3)
driver.quit()
2.9 验证码处理
概念
什么是验证码?
一种随机生成的信息 (数字, 字母, 汉字, 图片, 算术题…) 等为了防止恶意的请求行为, 增加应用的安全性
为什么要学习验证码?
在web应用中, 大部分系统在用户登录注册的时候都需要输入验证码, 而我们自动化脚本也要面临处理验证码的问题
常用方法
说明: Selenium中并没有对验证码处理的方法, 在这里我们介绍几种常用的处理方式
方法:
1.去掉验证码
(测试环境下采用)
2.设置万能验证码
(生产和测试环境下采用)
3.验证码识别技术
(通过 python-tesseract 来识别图片类型的验证码: 识别率很难达到100%)
4.记录 cookie
(通过记录 cookie 进行跳过登录)
注意
1 和 2, 都是开发人员来完成
3 验证码识别技术成功率不高, 不太合适
4 记录cookie 比较实用, 推荐
2.10 cookie
概念
cookie是由web服务器生成的, 并且保存在用户浏览器上的小文本文件, 它可以包含用户信息cookie数据格式: 键值对 (python中的字典)cookie产生: 客户端请求服务器, 如果服务器需要记录该用户状态, 就向客户端浏览器颁发一个cookie数据cookie使用: 当浏览器再次请求该网站时, 浏览器把请求的数据和cookie数据一同提交给服务器, 服务器检查该cookie, 以此来辨认用户
应用场景
实现会话跟踪, 记录用户登录状态实现记住密码和自动登录的功能用户未登录状态下, 记录购物车中的商品
方法
说明: Selenium中对cookie操作提供相应的方法
方法:
1.driver.get_cookies()
获取本网站所有本地cookies
2.driver.get_cookie(name)
获取指定cookie
name: 为cookie中键值对数据的 键名
3.driver.add_cookie(cookie_dict)
添加cookie
cookie_dict: 一个字典对象, 必选的内容包括: “name” 和 “value”
案例需求
使用cookie 实现跳过百度登录
1.手动登录百度, 获取cookie
2.请求百度, 并且带上cookie
步骤分析
BDUSS是登录百度后的唯一身份凭证, 拿到BDUSS就等于拿到了账号的控制权,通行贴吧,知道,文库…主要产品
登录百度, 抓取BDUSS添加 BDUSS 的键值对调用刷新的方法
代码
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.maximize_window()
# 需求: 使用cookie 实现跳过百度登录
# 1.手动登录百度, 获取cookie
# 2.请求百度, 并且带上cookie
# 没有cookie的时候
driver.get("http://www.baidu.com")
# 添加cookie操作
driver.add_cookie({ "name": "BDUSS", "value": "VZMUEl0WFJQYkxNSXk0c0VMUk5ZNGYteWVYNG01aVJtZXFCV056alk5M3V3SUZlSVFBQUFBJCQAAAAAAAAAAAEAAAC2KUFmTFhKX0pheQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO4zWl7uM1peQ"})
time.sleep(3)
# 刷新, 再次请求百度首页, 验证是否带上身份信息
driver.refresh()
time.sleep(3)
driver.quit()
3 Pytest框架
3.1 总体介绍
什么是断言
让程序代替人工去判断测试程序的执行结果是否符合预期的过程
为什么学习断言
自动化脚本在执行的时候一般都是无人值守的状态, 我们不知道执行结果是否符合预期, 所以我们需要让程序代替人工去检测程序的执行结果是否符合预期, 这就需要断言
3.2 断言方法
assert xx
判断 xx 为真
assert not xx
判断 xx 不为真
assert a in b
判断 b 包含 a
assert a == b
判断 a 等于 b
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+1 的结果等于 2
def test_b(self):
assert add(1, 1) == 2
# 判断 1+2 的结果不等于4
def test_c(self):
assert 4 != add(1, 2)
# 误判: 1+2 等于 4 了
def test_d(self):
assert 4 == add(1, 2)
3.3 setup和teardown
应用场景
pytest 在运行自动化脚本的前后会执行两个特殊的方法, 分别是"前置"和"后置"方法
在脚本执行前会执行"前置"方法,在脚本执行后会执行"后置"方法
概念和方法
1.初始化(前置处理方法):
def setup(self)
2.销毁(后置处理方法):
def teardown(self)
3.运行于测试方法的始末, 即:运行一次测试方法就会运行一次 setup 和 teardown
案例
import time
def add(x, y):
return x + y
class TestPlus:
# 获取并打印开始时间, 每个测试函数执行前都打印一次
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 add(1, 1) == 2
3.4 配置文件
应用场景
使用配置文件, 可以通过配置项来选择执行哪些目录下的哪些测试模块
用法
步骤:
新建 scripts 模块, 测试脚本放到模块中新建 pytest.ini 文件, 名称为 pytest.ini, 第一行为 [pytest], 并且补全配置项命令行运行 pytest 即可
示例
pytest.ini
[pytest]
addopts = -s
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*
3.5 测试报告插件
应用场景
需要测试报告来体现自动化脚本测试是否通过
安装
pip install pytest-html==1.21.1
使用
在配置文件中的命令行参数中, 增加 --html=用户路径/report.html
生成报告
步骤:
命令行输入 pytest 运行脚本在项目目录下会有一个 report文件夹, 里面有个 report.html 就是测试报告
3. 6 数据参数化
应用场景
需要测试多组值得时候, 使用数据参数化可以使代码更简洁, 可读性更好
方法
数据参数化, 装饰器需要放在要传多组值的函数上
@pytest.mark.parametrize(argnames, argvalues)
参数:
argnames
: 参数名
argvalues
: 参数对应值, 类型必须是可迭代类型, 一般使用 list
3.6.1 单一参数
代码
import pytest
class TestDemo:
# 需求: 不使用数据参数化, 分别打印用户名 "zhangsan" 和 "lisi"
def test_a(self):
print("zhangsan")
def test_b(self):
print("lisi")
# 需求: 使用数据参数化 (单一参数), 修改上面的代码
@pytest.mark.parametrize("name", ["zhangsan", "lisi"])
def test_c(self, name):
print(name)
3.6.2 多个参数
代码
import pytest
class TestDemo:
# 需求: 使用数据参数化 (多个参数), 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222
@pytest.mark.parametrize(("username", "password"), [("zhangsan", "111111"), ("lisi", "222222")])
def test_c(self, username, password):
print(username + "-----" + password)
# 使用元组可以传多个值 ("zhangsan", "111111"), 列表行不行?
3.6.3 推荐用法
代码
import pytest
class TestDemo:
# 需求: 使用数据参数化 (推荐用法), 分别打印2组账号和密码: zhangsan / 111111 和 lisi / 222222
# @pytest.mark.parametrize(("username", "password"), [("zhangsan", "111111"), ("lisi", "222222")])
# def test_c(self, username, password):
# print(username + "-----" + password)
@pytest.mark.parametrize("dict", [{ "username": "zhangsan", "password": "111111"}, { "username": "lisi", "password": "222222"}])
def test_d(self, dict):
print(dict)
print(dict["username"])
print(dict["password"])
#("zhangsan", "111111", "13000000000", "1", "1", "30", "......")
#("lisi", "222222", "13100000000", ??????)
# 推荐的用法是用字典表示参数值
# {"username": "zhangsan", "password": "111111"}
4 PO模式
4.1 递进学习路线
v1: 不使用任何设计模式和单元测试框架v2: 使用 pytest 管理用例v3: 使用方法封装的思想, 对代码进行优化v4: 采用PO模式的分层思想对代码进行拆分, 分离pagev5: 对PO分层后的代码继续优化, 分离page中的元素和操作v6: PO模式深入封装, 把共同操作提取封装
4.2 无模式
4.2.1 案例说明
对 TPshop 项目的登录模块进行自动化测试
登录模块包含了很多测试用例, 如: 账号不存在, 密码错误, 验证码错误, 登录成功等等
为了节省时间, 我们只选取几个有代表性的用例来演示: 账号不存在, 密码错误
4.2.2 选择测试用例
账号不存在
点击首页的"登录"链接, 进入登录页面输入一个不存在的用户名输入密码输入验证码点击登录按钮获取错误提示信息
密码错误
点击首页的"登录"链接, 进入登录页面
输入用户名
输入一个错误的密码
输入验证码
点击登录按钮
获取错误提示信息
4.3 V1版本
不使用任何设计模式和单元测试框架每个文件对应编写一个测试用例, 完全的面向过程的编程方式
示例代码
登录功能, 账号不存在
# 账号不存在
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("18800000000")
# 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
print(msg)
# 关闭浏览器驱动
time.sleep(5)
driver.quit()
登录功能, 密码错误
# 密码错误
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()
存在的问题
一条测试用例对应一个文件, 用例多时, 不方便维护管理代码高度冗余
4.4 V2版本
引入pytest管理测试用例, 并断言用例的执行结果
好处
方便组织和管理多个测试用例提供了丰富的断言方法方便生成测试报告减少了代码冗余
示例代码
# 导包
import time
from selenium import webdriver
# 定义测试类
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_account_not_exist(self):
# 1. 点击首页的"登录"链接, 进入登录页面
self.driver.find_element_by_class_name("red").click()
# 2. 输入一个不存在的用户名
self.driver.find_element_by_id("username").send_keys("18800000000")
# 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
存在问题
依然是代码冗余
4.4.1 方法封装
概念
是将一些有共性的或多次被使用的代码提取到一个方法中, 供其他地方调用
好处
避免代码冗余容易维护隐藏代码实现的细节
目的
用最少的代码实现最多的功能
4.5 V3版本
驱动工具类
# 获取/关闭浏览器驱动的类
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
测试类
# 导包
import time
from v3.driver_utils_121 import DriverUtils
# 定义测试类
class TestLogin:
def setup(self):
self.driver = DriverUtils.get_driver()
self.driver.get("http://localhost/")
def teardown(self):
time.sleep(5)
DriverUtils.quit_driver()
# 定义用户不存在的测试方法
def test_login_account_not_exist(self):
# 1. 点击首页的"登录"链接, 进入登录页面
self.driver.find_element_by_class_name("red").click()
# 2. 输入一个不存在的用户名
self.driver.find_element_by_id("username").send_keys("18800000000")
# 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
注意: 如果想要引用其他类, 那么被引用的类的文件名要符合要求, 比如不能出现 []
存在的问题
代码冗余
4.6 PO模式
在做UI自动化时, 元素定位特别依赖页面, 如果页面变更, 自动化脚本就需要被修改
存在的问题
如果前端工程师改了某个元素, 你就得修改所有对应的代码存在大量的冗余
如果解决?
答案就是 PO模式
4.6.1 概念
PO是Page Object的缩写, PO模式是自动化测试开发的最佳设计模式之一
核心思想:
通过对页面元素的封装减少冗余代码, 同时在后期维护中, 若元素发生变化, 只需要调整页面元素封装的代码即可, 提高了测试用例的可维护性, 可读性页面和测试脚本分离
4.6.2 PO模式分层
分层机制, 让不同层去做不同类型的事情, 让代码结构清晰, 增加复用性
分层方式
两层: 对象操作层 + 业务数据层
对象操作层: 封装页面信息, 包括元素以及元素的操作业务数据层: 封装多种操作组合的业务以及测试数据
三层: 对象库 + 操作层 + 业务数据层 / 对象操作层 + 业务层 + 数据层
四层: 对象库 + 操作层 + 业务层 + 数据层
4.6.3 PO模式优点
引入PO模式前
存在大量冗余代码业务流程不清晰后期维护成大 引入PO模式后
减少冗余代码业务代码和测试数据被分开, 降低耦合性维护成本低
4.7 V4版本
介绍
采用PO模式的分层思想对代码进行拆分
PO封装
对登录页面进行封装: 封装到类 LoginPage
对测试用例进行封装: 封装到类 TestLogin
代码结构
utils包
driver_utils.py
page包
login_page.py
scripts包
test_login.py
pytest.ini
PO封装
login_page.py
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_btn(self):
return self.driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
def get_msg(self):
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
return msg
test_login.py
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_btn(self):
return self.driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
def get_msg(self):
msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
return msg
4.8 v5版本
介绍
对PO分层后的代码继续优化
优化内容
分离page页面中的元素和操作优化元素定位方式
示例代码
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)
# return self.driver.find_elment(feature[0], feature[1])
# 点击首页的"登录"链接, 进入登录页面
def click_login_link(self):
return self.find_el(self.login_link_btn).click()
# return self.driver.find_elment(self.login_link_btn[0], self.login_link_btn[1]).click()
# return self.driver.find_element_by_class_name("red").click()
# 输入用户名
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)
# 输入密码
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)
# 输入验证码
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)
# 点击登录按钮
def click_login_btn(self):
return self.find_el(self.login_btn).click()
# return self.driver.find_element_by_name("sbtbutton").click()
# 获取提示信息
def get_msg(self):
return self.find_el(self.msg_info).text
# msg = self.driver.find_element_by_css_selector(".layui-layer-content").text
# return msg
4.9 v6版本
介绍
把共同的方法进行封装
优化内容
封装操作基类
封装查找元素的方法封装基本操作方法: 点击/ 清空/ 输入等等 page继承操作基类
结构
utils包
driver_utils.py
page包
login_page.py
scripts包
test_login.py
pytest.ini
base包
base_action.py
4.9.1 示例代码
base_action.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 click(self, feature):
return self.find_el(feature).click()
def input(self, feature, content):
return self.find_el(feature).send_keys(content)
def clear(self, feature):
return self.find_el(feature).clear()
注意: page页面要继承 BaseAction
5 数据驱动
概念
是以数据来驱动整个测试用例的执行, 也就是测试数据决定测试结果
特点
可以把数据驱动理解为一种模式或者一种思想数据驱动技术可以让用户把关注点放在测试数据的构建和维护上, 而不是直接维护脚本, 可以利用同样的过程, 对不同的输入数据进行测试数据驱动要依赖参数化技术
数据来源
直接定义在测试脚本中 (简单直观, 但测试方法和测试数据未分离, 不方便后期维护)从文件中读取数据, 如 txt, excel, xml, JSON等格式文件从数据库读取数据
5.1 JSON基本介绍
概念
JSON全称是" JavaScript Object Notation", 是JavaScript 对象表示法, 它是一种基于文本, 独立于语言的轻量级数据交换格式
特点
JSON是纯文本JSON具有良好的自我描述性, 便于阅读和编写JSON具有清晰的层级结构有效的提升网络传输效率
对比XML
XML指可扩展标记语言, 被设计用来传输数据如果使用XML, 需要读取XML, 然后通过标签结点来遍历文档, 并读取对应的值, 然后传输使用JSON, 只需要读取JSON字符串
JSON语法规格
大括号保存对象中括号保存数组对象和数组可以相互嵌套数据采用键值对来表示多个数据用逗号分隔
JSON值
数字 (整数或者浮点数)字符串 (在双引号中)逻辑值 (true 或者 false)数组 (在中括号中)对象 (在大括号中)null
JSON中空值用 null 表示python中对应的用 None 表示
JSON基本操作
操作内容
python字典与JSON之间的转换
JSON文件读写
在python中想要操作 JSON, 需要先导入依赖包
import json
5.2 字典与JSON转换
代码
import json
# 把python字典类型转换为JSON字符串
dict1 = {
"name": "zhangsan",
"age": 18,
"is_man": True,
"school": None
}
# 使用 dumps 方法, 得到的结果是 json 字符串
json_str1 = json.dumps(dict1)
print(json_str1)
# 把JSON字符串转换为python字典
json_str2 = '{"name": "zhangsan", "age": 18, "is_man": true, "school": null}'
# 使用 loads 方法, 得到的结果是 python字典
dict2 = json.loads(json_str2)
print(dict2)
把python字典类型转换为JSON字符串: 使用 dumps 方法
把JSON字符串转换为python字典: 使用 loads 方法
5.3 JSON文件读写
代码
import json
# 读取 data.json 文件
with open("data.json", "r", encoding="utf-8") as f:code>
data1 = json.load(f)
print(data1)
# 把字典写入json文件 "data2.json"
data2 = data1
with open("data2.json", "w", encoding="utf-8") as f:code>
json.dump(data2, f)
# 把字典写入json文件 "data3.json" ------解决写入中文的问题
data3 = data1
with open("data3.json", "w", encoding="utf-8") as f:code>
json.dump(data2, f, ensure_ascii=False)
实现步骤
编写测试用例敲代码
采用PO模式的分层思想对页面进行封装编写测试脚本定义数据文件, 实现参数化
6 项目实战
在线计算器项目 http://cal.apple886.com/
6.1 项目结构
base ----> 存储页面对象的父类(便于子类调用)data ----> 存储测试用例数据page ----> 存储页面对象scripts ----> 存储测试脚本utils ----> 存储经常使用的工具类pytest.ini ----> 运行项目的配置
6.2 base包
新建base_action.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 click(self, feature):
return self.find_el(feature).click()
# 查找输入元素
def input(self, feature, content):
return self.find_el(feature).send_keys(content)
# 清空
def clear(self, feature):
return self.find_el(feature).clear()
# 定位数字按钮
def find_el_num(self, feature, num):
# 将num格式化为字符串
return self.driver.find_element(feature[0], feature[1].format(str(num)))
6.3 data包
新建cal_data.json
{
"cal_001": {
"data": [1, 3],
"result": 4
},
"cal_002": {
"data": [1, 2],
"result": 3
},
"cal_002": {
"data": [1, 2, 3],
"result": 6
}
}
6.4 page包
新建cal_page.py
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class CalPage(BaseAction):
# 数字按钮
number_btn = By.ID, "simple{}"
# 加号按钮
add_btn = By.ID, "simpleAdd"
# 等号按钮
equal_btn = By.ID, "simpleEqual"
# 结果
result = By.ID, "resultIpt"
# 点击数字
def click_number_btn(self, num):
return self.find_el_num(self.number_btn, num).click()
# 点击加号
def click_add_btn(self):
return self.click(self.add_btn)
# 点击等于号
def click_equal_btn(self):
return self.click(self.equal_btn)
# 显示结果
def get_result(self):
return self.find_el(self.result).get_attribute("value")
6.5 scripts包
新建tesst_cal.py
# 导包
import time
import pytest
from page.cal_page import CalPage
from utils.driver_utils import DriverUtils
from utils.read_data import read_data
# 定义测试类
class TestCal:
def setup_method(self):
self.driver = DriverUtils.get_driver()
self.cal_page = CalPage(self.driver)
self.driver.get("http://cal.apple886.com/")
def teardown_method(self):
time.sleep(5)
DriverUtils.quit_driver()
# 加法算数
@pytest.mark.parametrize("params", read_data("cal_data.json"))
def test_2_add(self, params):
for i in params["data"]:
self.cal_page.click_number_btn(i)
self.cal_page.click_add_btn()
time.sleep(5)
# 4. 点击等于号按钮
self.cal_page.click_equal_btn()
time.sleep(5)
# 断言
assert str(params["result"]) == self.cal_page.get_result()
6.6 utils包
新建driver_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
新建read_data.py用于读取data包的数据
# 读取data数据文件
import json
def read_data(filename):
with open("./data/" + filename, "r", encoding="utf-8") as f:code>
list_data = []
dict_list = json.load(f)
for value in dict_list.values():
list_data.append(value)
return list_data
7 日志收集
7.1 日志收集
概念 : 日志就是用于记录系统运行时的信息, 也称为Log
作用:
调试程序
旧的方式: print(“xxxx”)
low 新的方式: 通过日志 了解程序运行的情况, 是否正常程序运行故障分析与问题定位用来做用户行为分析和数据统计
需要学好 sql
级别:
思考
是否记录的所有日志信息重要性都一样? 日志级别, 指日志信息的重要性
常见日志级别:
DEBUG === 调试INFO === 信息WARNING === 警告ERROR === 错误CRITICAL === 严重错误
日志基本用法:
logging:python中有一个标准库, logging模块可以直接记录日志
使用
导入 logging 包输出日志默认的日志级别被设置为 warning
设置日志级别
方法
<code>logging.basicConfig(level=logging.DEBUG)
设置日志格式
默认格式
日志级别 : Logger名称 : 日志内容 自定义格式
logging.basicConfig(format="xxxxxx")code>
将日志信息输出到文件
默认
python的logging模块将日志打印到了标准输出中(控制台) 将日志输出到文件的方法
logging.basicConfig(filename=“xxx.log”)
7.2 日志高级用法
思考
如何将日志信息同时输出到控制台和日志文件中?如何将不同级别的日志输出到不同的日志文件?如何解决日志文件过大的问题?
7.3 四大组件
日志器(Logger):提供了程序使用日志的入口处理器(Handler):将logger创建的日志记录发送到合适的输出格式器(Formatter):决定日志的输出格式过滤器(Filter): 提供了更细粒度的控制工具来决定输出哪条日志记录, 丢弃哪条日志记录
组件之间的关系
日志器 (Logger) 是入口,真正干活的是处理器 (Handler),处理器还可以通过格式器 (Formatter)过滤器 (Filter) 对输出的日志内容做格式化和过滤
7.3.1 Logger类
如何创建Logger对象
logger = logging.getLogger(name)
可选参数 name
如果不写name, 日志器名称默认为 root
如果写了name, 如, logger = logging.getLogger(“myLogger”) 那么日志器的名称为 myLogger
Logger常用方法
打印日志
logger.debug()logger.info()logger.warninglogger.errorlogger.critical()
设置日志级别
logger.setLevel()
为logger对象添加一个handler对象
logger.addHandler()
为logger对象添加一个filter对象
logger.addFilter
7.3.2 Handler类
如何创建Handler对象
在程序中不应该直接实例化和使用Handler实例, 因为Handler是一个基类, 它只定义了Handler应该有的接口, 应该使用Handler实现类来创建对象创建方式
输出日志到控制台
logging.StreamHandler 输出到磁盘文件, 默认文件大小会无限增长输出到文件, 按文件大小切割输出到文件, 按时间切割
logging.hanlders.TimedRotatingFileHandler 将日志消息以get或post的方式发送给http服务器将日志消息发送给一个指定的email地址 常用方法
为handler设置格式器对象
handler.setFormatter()
7.3.3 Formatter类
**作用:**Formatter对象用于配置日志信息的格式
如何创建Formatter对象
logging.Formatter(fmt=None, datefmt=None) fmt: 消息格式化字符串, 如果不指定该参数则默认使用message的原始值 datefmt: 日期格式化字符串, 如果不指定该参数则默认使用 “%Y-%m-%d %H:%M:%S”
案例
说明
可读性好的日志需要具备一些特征
在控制台和文件都能输出文件输出能够按时间切割
步骤
导包创建日志器对象 / 设置日志级别创建处理器对象: 输出到控制台 + 文件(按时间切割)创建格式器对象将格式器添加到处理器将处理器添加到日志器打印日志
代码
# 1. 导包
import logging
import logging.handlers
# 2. 创建日志器对象 / 设置日志级别
logger = logging.getLogger() # 默认日志器名称为 root
# logger = logging.getLogger("An") # 自定义日志器名称为 An
logger.setLevel(level=logging.DEBUG)
# 3. 创建处理器对象: 输出到控制台 + 文件(按时间切割)
ls = logging.StreamHandler()
lf = logging.handlers.TimedRotatingFileHandler(filename="172.log", when="s", backupCount=3)code>
# 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:
logger.debug("===================================================================")
8 面试题
说明样的项目适合做web自动化?
①需求变动不频繁
②项目周期长
③项目需要回归测试
web自动化一个什么时候开始?
①手工测试结束后
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。