目录:
- 浏览器复用
- Cookie 复用
- pageobject设计模式
- 异常自动截图
- 测试用例流程设计
- 电子商务产品实战
1.浏览器复用
复用浏览器简介
为什么要学习复用浏览器?
- 自动化测试过程中,存在人为介入场景
- 提高调试UI自动化测试脚本效率
复用已有浏览器-配置步骤
-
需要退出当前所有的谷歌浏览器(特别注意)
-
输入启动命令,通过命令启动谷歌浏览器
- 找到 chrome 的启动路径
- 配置环境变量
-
验证是否启动成功
- windows:chrome –remote-debugging-port=9222
- mac:Google Chrome –remote-debugging-port=9222
代码示例:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
option = Options()
option.debugger_address = 'localhost:9222'
driver = webdriver.Chrome(options=option)
driver.get("https://work.weixin.qq.com/wework_admin/frame")
driver.find_element(By.CSS_SELECTOR,'.ww_indexImg_AddMember').click()
2.Cookie 复用
cookie 是什么?
- Cookie 是一些认证数据信息,存储在电脑的浏览器上
- 当 web 服务器向浏览器发送 web 页面时,在连接关闭后,服务端不会记录用户的信息
为什么要使用Cookie自动化登录?
- 复用浏览器仍然在每次用例开始都需要人为介入
- 若用例需要经常执行,复用浏览器则不是一个好的选择
- 大部分cookie的时效性都很长,扫一次可以使用多次
常见问题
- 企业微信cookie有互踢机制。在获取cookie成功之后。不要再进行扫码操作!!!!
- 获取cookie的时候,即执行代码获取cookie时,一定要确保已经登录
- 植入cookie之后需要进入登录页面,刷新验证是否自动登录成功。
代码示例:
import time
import yaml
from selenium import webdriver
class TestCookieLogin:
def setup_class(self):
self.drvier = webdriver.Chrome()
self.drvier.implicitly_wait(3)
def teardown_class(self):
self.drvier.quit()
def test_get_cookies(self):
# 1. 访问企业微信主页/登录页面
self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
# 2. 等待20s,人工扫码操作
time.sleep(15)
# 3. 等成功登陆之后,再去获取cookie信息
cookie = self.drvier.get_cookies()
print(cookie)
# 4. 将cookie存入一个可持久存储的地方,文件
# 打开文件的时候添加写入权限
with open("./datas/doc/cookie.yaml", "w") as f:
# 第一个参数是要写入的数据
yaml.safe_dump(cookie, f)
def test_add_cookie(self):
# 1. 访问企业微信主页面
self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
# 2. 定义cookie,cookie信息从已经写入的cookie文件中获取
cookie = yaml.safe_load(open("./datas/doc/cookie.yaml"))
# 3. 植入cookie
for c in cookie:
self.drvier.add_cookie(c)
time.sleep(3)
# 4.再次访问企业微信页面,发现无需扫码自动登录,而且可以多次使用
self.drvier.get("https://work.weixin.qq.com/wework_admin/frame#contacts")
代码结构:
3.pageobject设计模式
传统 UI 自动化的问题
- 无法适应 UI 频繁变化
- 无法清晰表达业务用例场景
- 大量的样板代码 driver/find/click
POM 模式的优势
- 降低 UI 变化导致的测试用例脆弱性问题
- 让用例清晰明朗,与具体实现无关
POM 建模原则
- 字段意义
- 不要暴露页面内部的元素给外部
- 不需要建模 UI 内的所有元素
- 方法意义
- 用公共方法代表 UI 所提供的功能
- 方法应该返回其他的 PageObject 或者返回用于断言的数据
- 同样的行为不同的结果可以建模为不同的方法
- 不要在方法内加断言
POM 使用方法
- 把元素信息和操作细节封装到 PageObject 类中
- 根据业务逻辑,在测试用例中链式调用
PO简介:
Page Object(简称PO)模式,是Selenium实战中最为流行,并且是自动化测试中最为熟悉和推崇的一种设计模式。在设计自动化测试时,把页面元素和元素的操作方法按照页面抽象出来,分离成一定的对象,然后再进行组织。做web自动化最头疼的一个问题,莫过于页面变化了,如果没有使用PO设计模式,页面一变化就意味着之前的元素定位甚至元素的操作方法不能用了,需要重新修改。你需要一个一个从测试脚本中把需要修改的元素定位方式、元素的操作方法找出来,然后一一地修改。这样的自动化脚本不但繁琐,维护成本也极高。
而page object模式就可以很好地解决这个问题,优点:
- 减少代码冗余。
- 业务和实现分离。
- 降低维护成本。
那到底什么是Page Object模式,见名知意,就是页面对象,在实际自动化测试中,一般对脚本分为三层:
- 对象层: 用于存放页面元素定位(object_layer)
- 逻辑层: 用于存放一些封装好的功能用例模块(logical_layer)
- 业务层: 用于存放我们真正的测试用例的操作部分(business_layer)
除了以上三层,还有一个基础层(base_layer),基础层主要是针对selenium的一些常用方法,根据实际业务需要进行二次封装,如点击、输入等操作加入一些等待、日志输入、截图等操作,方便以后查看脚本的运行情况及问题排查。
代码示例:(传统的测试方法)
test_tradition_search.py
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestSearch:
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(3)
def teardown_class(self):
self.driver.quit()
def test_search(self):
self.driver.get("https://xueqiu.com/")
# 输入搜索关键词
self.driver.find_element(By.NAME, "q").send_keys("阿里巴巴-SW")
# 点击搜索按钮
self.driver.find_element(By.CSS_SELECTOR, "i.search").click()
# 获取搜索结果
name = self.driver.find_element(By.XPATH, "//table//strong").text
# 断言
assert name == "阿里巴巴-SW"
代码示例:(po模式)
template: 用来存放开发模版 - Gitee.com
项目结构:
4.异常自动截图
实现原理
- 装饰器
- 自动化关键数据记录
- 截图
- 日志
- page_source
代码示例:
test_record_exception_zhuangshiqi.py
import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By
from web_automation_testing.first_web_automation_testing.utils.log_util import logger
def ui_exception_record(func):
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
logger.warning('执行过程发生异常!')
driver = args[0].driver
timestamp = int(time.time())
img_path = f"./datas/screenshot/image_{timestamp}.png"
driver.save_screenshot(img_path)
page_source_path = f"./datas/pagesource/page_source_{timestamp}.html"
with open(page_source_path, 'w', encoding='utf8') as f:
f.write(driver.page_source)
allure.attach.file(img_path, name="picture", attachment_type=allure.attachment_type.PNG)
allure.attach.file(page_source_path, name="pagesource", attachment_type=allure.attachment_type.TEXT)
# allure.attach.file(page_source_path,name="pagesource",attachment_type=allure.attachment_type.HTML)
raise Exception
return inner
class TestRecord:
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.implicitly_wait(5)
def teardown_class(self):
self.driver.quit()
@ui_exception_record
def test_baidu(self):
self.driver.get("https://www.baidu.com/")
self.driver.find_element(By.ID, 'su1')
项目结构:
运行结果:
5.测试用例流程设计
现有测试用例的问题
- 可维护性不高
- 可读性较差
- 稳定性较差
用例结构设计
- 测试用例的编排
- 测试用例的项目结构
web自动化测试-用例设计
类型 | 框架对应 | 作用 |
---|---|---|
前置 | setup_class/BeforeAll | 准备测试数据 实例的初始化 |
setup/BeforeEach | 恢复用例初始状态 数据清理(也可以在用例级别完成) | |
后置 | teardown_class/AfterAll | driver进程退出 |
teardown/AfterEach | 恢复用例初始状态 数据清理(也可以在用例级别完成) |
恢复用例初始状态
- 用例1 执行过程经过A->B->C 三个页面
- 用例2 执行过程经过A->B->C 三个页面
- 用例1 执行完成之后执行用例2
问题:单条用例执行完成之后如果不恢复下一条用例的开始状态(回复用例初始页面),则会影响下一条用例的执行。
- 解决方案:
- 每条用例执行完成都
quit()
(影响执行效率) - 封装一个方法,用例执行完成之后回到首页
- 每条用例执行完成都
数据清理
- 清理策略
- 在前置处理中执行
- 在后置处理中执行
- 清理方式
- 调用业务接口
- 通过UI自动化方式操作
- 连接数据库执行SQL(不推荐)
6.电子商务产品实战
产品分析
- 产品:Litemall商城系统
- 功能:商品类目管理
- litemall
测试用例-新增类目
- 用户登录
- 进入商品类目菜单
- 点击添加
- 创建商品类目
- 获取操作结果
- 断言测试结果
测试用例-删除类目
- 用户登录
- 进入商品类目菜单
- 点击添加
- 创建商品类目
- 点击删除
- 获取操作结果
- 断言测试结果
代码示例:(传统示例)
test_tradition_litemall.py
import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from web_automation_testing.test_litemall_1.utils.log_util import logger
class TestLitemall:
# 前置动作
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(3)
# 登录
self.driver.get("http://litemall.hogwarts.ceshiren.com/")
# 问题,输入框内有默认值,此时send——keys不回清空只会追加
# 解决方案: 在输入信息之前,先对输入框完成清空
# 输入用户名密码
self.driver.find_element(By.NAME, "username").clear()
self.driver.find_element(By.NAME, "username").send_keys("manage")
self.driver.find_element(By.NAME, "password").clear()
self.driver.find_element(By.NAME, "password").send_keys("manage123")
# 点击登录按钮
self.driver.find_element(By.CSS_SELECTOR,
".el-button--primary").click()
# 窗口最大化
self.driver.maximize_window()
# 后置动作
def teardown_class(self):
self.driver.quit()
def get_screen(self):
timestamp = int(time.time())
# 注意:!! 一定要提前创建好images 路径
image_path = f"./datas/screenshot/image_{timestamp}.PNG"
# 截图
self.driver.save_screenshot(image_path)
# 讲截图放到报告的数据中
allure.attach.file(image_path, name="picture",
attachment_type=allure.attachment_type.PNG)
# 新增功能
def test_add_type(self):
# 点击商场管理/商品类目,进入商品类目页面
# 进入商品类目页面
self.driver.find_element(By.XPATH, "//*[text()='商场管理']").click()
self.driver.find_element(By.XPATH, "//*[text()='商品类目']").click()
# 添加商品类目操作
self.driver.find_element(By.XPATH, "//*[text()='添加']").click()
self.driver.find_element(By.CSS_SELECTOR,
".el-input__inner").send_keys("新增商品测试")
# ==============显示等待优化方案2: 自定义显式等待条件
def click_exception(by, element, max_attempts=5):
def _inner(driver):
# 多次点击按钮
actul_attempts = 0 # 实际点击次数
while actul_attempts < max_attempts:
# 进行点击操作
actul_attempts += 1 # 每次循环,实际点击次数加1
try:
# 如果点击过程报错,则直接执行 except 逻辑,并切继续循环
# 没有报错,则直接return 循环结束
driver.find_element(by, element).click()
return True
except Exception:
logger.debug("点击的时候出现了一次异常")
# 当实际点击次数大于最大点击次数时,结束循环并抛出异常
raise Exception("超出了最大点击次数")
# return _inner() 错误写法
return _inner
WebDriverWait(self.driver, 10).until(click_exception(By.CSS_SELECTOR,
".dialog-footer .el-button--primary"))
# ===========================使用显式等待优化
# 如果没找到,程序也不应该报错
res = self.driver.find_elements(By.XPATH,
"//*[text()='新增商品测试']")
self.get_screen()
# 数据的清理一定到放在断言操作之后完成,要不然可能会影响断言结果
self.driver.find_element(By.XPATH,
"//*[text()='新增商品测试']/../..//*[text()='删除']").click()
logger.info(f"断言获取到的实际结果为{res}")
# 断言产品新增后是否成功找到
assert res != []
# 删除功能
def test_delete_type(self):
# ================ 造数据步骤
# 点击商场管理/商品类目,进入商品类目页面
# 进入商品类目页面
self.driver.find_element(By.XPATH, "//*[text()='商场管理']").click()
self.driver.find_element(By.XPATH, "//*[text()='商品类目']").click()
# 添加商品类目操作
self.driver.find_element(By.XPATH, "//*[text()='添加']").click()
self.driver.find_element(By.CSS_SELECTOR,
".el-input__inner").send_keys("删除商品测试")
ele = WebDriverWait(self.driver, 10).until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, ".dialog-footer .el-button--primary")))
ele.click()
# ============完成删除步骤
self.driver.find_element(By.XPATH,
"//*[text()='删除商品测试']/../..//*[text()='删除']").click()
# 断言
WebDriverWait(self.driver, 10).until_not(
expected_conditions.visibility_of_any_elements_located((By.XPATH,
"//*[text()='删除商品测试']")))
# 问题: 因为代码执行速度过快,元素还未消失就捕获了。
# 解决: 确认该元素不存在后,再捕获
res = self.driver.find_elements(By.XPATH,
"//*[text()='删除商品测试']")
logger.info(f"断言获取到的实际结果为{res}")
assert res == []
PO模式设计原则
- 不要暴露页面内部的元素给外部
- 不需要建模 UI 内的所有元素
- 要用公共方法代表 UI 所提供的功能
- 同样的行为不同的结果可以建模为不同的方法
- 方法应该返回其他的 PageObject ,或者返回用于断言的数据
- 不要在方法内加断言
PO模式改造
梳理业务操作流程
梳理前置和后置
代码示例:
template: 用来存放开发模版
项目结构: