文章目录
- 一、PO模式介绍
-
- [1.1 version_03的问题](#1.1 version_03的问题)
- [1.2 PO模式](#1.2 PO模式)
-
- [1.2.1 PO模式概念](#1.2.1 PO模式概念)
- [1.2.2 PO模式编写步骤](#1.2.2 PO模式编写步骤)
- 二、PO模式代码实现
一、PO模式介绍
1.1 version_03的问题
- 多个测试用例对于同样的场景可能重复多次,例如登录元素的定位代码,在新增地址、下单、收藏商品、取消订单...都会出现。
- 如前端人员稍微修改一下页面的HTML信息,所有该元素的定位全部都得找出来进行修改。
- UI自动化代码对于页面的依赖非常严重。
python
## 1.3 引入PO模式的好处
引入PO模式前:
- 存在大量冗余代码
- 业务流程不清晰
- 后期维护成本大
引入PO模式后:
- 减少冗余代码
- 业务代码和测试代码被分开, 降低耦合性
- 维护成本低
1.2 PO模式
PO是Page Object的缩写, PO模式是自动化测试项目开发实践的最佳设计模式之一。
核心思想:通过对界面元素的封装减少冗余代码, 同时在后期维护中, 若元素定位发生变化, 只需要调整页面元素封装的代码, 提高测试用例的可维护性、 可读性。
python
PO能解决什么问题?
- 1、代码复用性
- 2、便于维护(脚本层与业务分离)-- 如果元素信息发生变化了,也不用去修改脚本。
1.2.1 PO模式概念
- 将页面当做一个对象,将页面上的元素信息当做对象的属性,将页面上的操作步骤当做对象的方法。
- 在完成测试用例组织时,只需要调用测试用例在各页面中所执行的业务方法即可完成。

1.2.2 PO模式编写步骤
1、定义页面元素实例属性
- 根据测试用例场景定义好所要用到的元素的实例属性
2、定义页面业务实例方法
- 一个页面可能存在多个业务方法,如登陆页面:登陆、找回密码等
3、抽取定位信息到实例属性
- 将元素定位的信息抽离到实例属性中进行统一管理,方便维护
二、PO模式代码实现
yacas
version_04 (包)
①page
- __init__.py
- login_page.py
②script
- __init__.py
- test_address.py
- test_login.py
- ③utils.py
2.1 page
2.1.1 login_page.py
1、创建代表指定页面的py文件。
2、定义页面对象
3、定义该页面中所用的元素(实例属性)
4、定义该页面中业务方法(实例方法) -- 测试用例在该页面中所执行的操作步骤
python
from selenium.webdriver.common.by import By
from version_04.utils import DriverUtils
# 定义页面对象
class LoginPage:
# 定义页面实例属性:页面上的元素信息 -->存储对应元素的定位信息(定位方式、该定位方式所需要对应的值)
def __init__(self):
# 驱动对象
self.driver = DriverUtils.get_driver()
# 用户名输入框
self.username = (By.ID, "username")
# 密码输入框
self.password = (By.ID, "password")
# 验证码输入框
self.verify_code = (By.ID, "verify_code")
# 登录按钮
self.login_btn = (By.XPATH, "//*[@class='login_bnt']/a")
# 定义哪些元素:测试工程师所要测试的内容用到该页面哪些元素就定义哪些元素
# 定义页面业务方法:组装该页面上的一些操作形成操作步骤
def tp_login(self, user, pwd, code):
"""
:param user: 用户名
:param pwd: 密码
:param code: 验证码
:return:
"""
# 方法2:元祖拆包
# ①清空用户名输入框(为了加强测试代码的健壮性)
self.driver.find_element(*self.username).clear()
# 输入用户名
self.driver.find_element(*self.username).send_keys(user)
# 清空密码输入框
self.driver.find_element(*self.password).clear()
# 输入密码
self.driver.find_element(*self.password).send_keys(pwd)
# 清空验证码输入框
self.driver.find_element(*self.verify_code).clear()
# 输入验证码
self.driver.find_element(*self.verify_code).send_keys(code)
# 点击登录
self.driver.find_element(*self.login_btn).click()
"""原始方法
# 输入用户名
self.driver.find_element(By.XPATH,"//*[@id='username']").send_keys(user)
# 输入密码
self.driver.find_element(By.XPATH,"//*[contains(@id,'pass')]").send_keys(pwd)
# 输入验证码
self.driver.find_element(By.XPATH,'//*[@placeholder="验证码" and @id="verify_code"]').send_keys(code)
# 点击登录
self.driver.find_element(By.XPATH,"//*[@class='login_bnt']/a").click()
"""
"""方法1
# 输入用户名
self.driver.find_element(self.username[0], self.username[1]).send_keys(user)
# 输入密码
self.driver.find_element(self.password[0], self.password[1]).send_keys(pwd)
# 输入验证码
self.driver.find_element(self.verify_code[0], self.verify_code[1]).send_keys(code)
# 点击登录
self.driver.find_element(self.login_btn[0], self.login_btn[1]).click()
"""
2.2 script
2.2.1 test_login.py
python
# 导包
from time import sleep
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from version_04.utils import DriverUtils, get_el_text
from version_04.page.login_page import LoginPage
# 1、定义测试类 --->模块(登录模块)
class TestLogin():
# 开始执行测试之前只会打开一次浏览器
def setup_class(self):
# 类方法的调用:创建对象.方法()
self.driver = DriverUtils.get_driver()
# 所有的测试用例都运行完毕才会关闭浏览器
def teardown_class(self):
DriverUtils.quit_driver()
# 每个测试方法的起点一致;那么证明每个测试方法运行之前都会回到首页
def setup(self):
self.driver.get("https://hmshop-test.itheima.net/")
# 2、定义测试方法 ---> 标题
# 登录失败-(账户不存在)
def test_login_account_not_exist(self):
# 4、暂停3s ->代替测试步骤
# a。使用Xpath 文本定位策略定位登录超链接,并点击
self.driver.find_element_by_xpath("//*[text()='登录']").click()
# 通过调用LoginPage登录页面的登录方法完成登录操作
LoginPage().tp_login("13611111111", "123456", "8888")
"""
# b。使用Xpath 属性定位策略定位用户名输入框,并输入13600001111
self.driver.find_element_by_xpath("//*[@id='username']").send_keys("13611111111")
# c。使用Xpath 属性包含定位策略定位密码输入框,并输入123456
self.driver.find_element_by_xpath("//*[contains(@id,'pass')]").send_keys("123456")
# d。使用Xpath 属性与逻辑结合策略定位验证码输入框,并输入8888
self.driver.find_element_by_xpath('//*[@placeholder="验证码" and @id="verify_code"]').send_keys("8888")
# e。使用Xpath 层级与属性结合策略定位登录按钮,并点击;
self.driver.find_element_by_xpath("//*[@class='login_bnt']/a").click()
"""
sleep(2)
# 获取实际结果:
msg = get_el_text("//*[@class='layui-layer-content layui-layer-padding']")
# 判断实际结果和预期结果是否一致
assert msg == "账号不存在!"
# 登录失败-(密码错误)
def test_login_password_error(self):
# 4、暂停3s ->代替测试步骤
# a。使用Xpath 文本定位策略定位登录超链接,并点击
self.driver.find_element_by_xpath("//*[text()='登录']").click()
# 通过调用LoginPage登录页面的登录方法完成登录操作
LoginPage().tp_login("13600001111", "error", "8888")
"""
# b。使用Xpath 属性定位策略定位用户名输入框,并输入13600001111
self.driver.find_element_by_xpath("//*[@id='username']").send_keys("13600001111")
# c。使用Xpath 属性包含定位策略定位密码输入框,并输入123456
self.driver.find_element_by_xpath("//*[contains(@id,'pass')]").send_keys("error")
# d。使用Xpath 属性与逻辑结合策略定位验证码输入框,并输入8888
self.driver.find_element_by_xpath('//*[@placeholder="验证码" and @id="verify_code"]').send_keys("8888")
# e。使用Xpath 层级与属性结合策略定位登录按钮,并点击;
self.driver.find_element_by_xpath("//*[@class='login_bnt']/a").click()
"""
sleep(2)
# 获取实际结果
msg = get_el_text("//*[@class='layui-layer-content layui-layer-padding']")
# 断言,期望的提示信息包含在实际结果中
assert "密码错误" in msg

2.2.2 test_address.py
python
# 导包
import time
from time import sleep
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.select import Select
from selenium.webdriver.support.wait import WebDriverWait
from version_04.utils import el_is_exist_by_text, DriverUtils
from version_04.page.login_page import LoginPage
class TestAddress:
# 开始执行测试之前只会打开一次浏览器
def setup_class(self):
self.driver = DriverUtils.get_driver()
self.driver.get("https://hmshop-test.itheima.net/")
# 所有的测试用例都运行完毕才会关闭浏览器
def teardown_class(self):
DriverUtils.quit_driver()
def test_01_login_suc(self):
# a。使用Xpath 文本定位策略定位登录超链接,并点击
self.driver.find_element_by_xpath("//*[text()='登录']").click()
# 通过调用LoginPage登录页面的登录方法完成登录操作
LoginPage().tp_login("13600001111", "123456", "8888")
"""
# b。使用Xpath 属性定位策略定位用户名输入框,并输入13600001111
self.driver.find_element_by_xpath("//*[@id='username']").send_keys("13600001111")
# c。使用Xpath 属性包含定位策略定位密码输入框,并输入123456
self.driver.find_element_by_xpath("//*[contains(@id,'pass')]").send_keys("123456")
# d。使用Xpath 属性与逻辑结合策略定位验证码输入框,并输入8888
self.driver.find_element_by_xpath('//*[@placeholder="验证码" and @id="verify_code"]').send_keys("8888")
# e。使用Xpath 层级与属性结合策略定位登录按钮,并点击;
self.driver.find_element_by_xpath("//*[@class='login_bnt']/a").click()
"""
sleep(2)
def test_02_add_address(self):
# 2、在个人中心页面点击【账户设置】下【收货地址】
ActionChains(self.driver).move_to_element(self.driver.find_element(By.XPATH, "//*[text()='账户设置']")).perform()
self.driver.find_element(By.XPATH, "//*[text()='收货地址']").click()
"""
# (*)获取当前已经保存地址条数
old_num = self.driver.find_elements(By.CSS_SELECTOR, "em.red")[0].text
print(f"新增地址前,已保存的地址条数为{old_num}")
"""
# 3、点击【新增地址】:收货人信息 = cus{当前时间}
self.driver.find_element(By.XPATH, "//*[text()='增加新地址']").click()
customer_name = f"cus{time.strftime('%H_%M_%S')}"
# 4、完成新增地址操作
self.driver.find_element(By.CSS_SELECTOR, '[name="consignee"]').send_keys(customer_name)
self.driver.find_element(By.CSS_SELECTOR, '[name="mobile"]').send_keys("13600001112")
Select(self.driver.find_element(By.ID, "province")).select_by_value("1") # value属性值为1,代表北京
Select(self.driver.find_element(By.ID, "city")).select_by_value("2") # 市辖区2
Select(self.driver.find_element(By.ID, "district")).select_by_value("39") # 朝阳区39
Select(self.driver.find_element(By.ID, "twon")).select_by_value("40") # 建外街道40
self.driver.find_element(By.CSS_SELECTOR, '[name="address"]').send_keys("幸福门街道26栋")
self.driver.find_element(By.CSS_SELECTOR, '[name="zipcode"]').send_keys("100000")
self.driver.find_element(By.ID, "address_submit").click()
# 当UI自动化脚本操作功能时,如该步骤会自动触发发送请求,最好在触发之后跟上强制等待1秒,防止发送请求失败
sleep(1)
# 根据本次新增的【收货人】信息的文本,到界面上找元素,如果能找到则代表信息成功,找不到则失败截图
assert el_is_exist_by_text(customer_name)
2.3 utils.py
python
import logging
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
# 驱动工具类
class DriverUtils:
# 初始化私有属性(不希望外部修改值)
__driver = None
# 获取浏览器驱动对象
# 整个测试用例运行过程中会多次调用获取驱动对象的方法,按照实例方法调用的话每次都要创建对象,调用就会出现多个浏览器
# 整个测试用例运行时,第一次打开浏览器驱动对象,则把浏览器驱动对象存储起来
# 下次调用获取驱动对象时,判断当前是否有存储的浏览器驱动对象,如有则直接返回,如没有则创建
@classmethod
def get_driver(cls):
"""
1、拷贝一份共性的代码
2、修改代码的错误
3、分析封装的代码中是否要参数化数据
4、分析封装代码的运行结果是否需要返回
5、优化代码
:return:驱动对象
"""
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:
sleep(2)
cls.__driver.quit()
# 将__driver值恢复为None
cls.__driver = None
# 函数:公用的获取任意元素文本
def get_el_text(xpath_str):
# 获取元素文本
# msg = DriverUtils.get_driver().find_element(By.XPATH, xpath_str).text
# 重要的信息,最好是显示等待
try:
msg = WebDriverWait(DriverUtils.get_driver(), 10, 1).until(lambda x:x.find_element(By.XPATH, xpath_str)).text
print(msg)
except Exception as e:
logging.error("没有获取到{xpath_str}的元素对象文本!")
msg = None
# 返回获取的文本
return msg
# 函数:根据文本判断当前页面是否有对应的元素对象
def el_is_exist_by_text(key_text):
# 根据本次新增的【收货人】信息的文本,到界面上找元素,如果能找到则代表信息成功,找不到则失败截图
try:
# 显示等待
# 如果找到元素对象则把元素对象赋值给is_suc变量
is_suc = WebDriverWait(DriverUtils.get_driver(), 10, 1).until(lambda x: x.find_element(By.XPATH, f"//*[text()='{key_text}']"))
except Exception as e:
# 找不到则给is_suc变量赋值为False
is_suc = False
# 截图
DriverUtils.get_driver().get_screenshot_as_file(f"{key_text}未找到.png")
logging.error(f"未找到文本为{key_text}的元素对象!")
# 返回是否找到结果
return is_suc