PO模式
PO模式
PO是什么?
PO:Page Object(⻚⾯对象),将⾃动化涉及的⻚⾯或模块封装成对象。
PO能解决什么问题?
1、代码复⽤性
2、便于维护(脚本层与业务分离)-- 如果元素信息发⽣变化了,也不⽤去修改脚本。
PO如何做?
-
Base层
存放所有⻚⾯公共⽅法。
-
Page层
基于⻚⾯或模块单独封装当前⻚⾯要操作的对象
- Script层
1 脚本层+unittest
面试题: 什么是PO模式,为什么要使用它
PO是Page Object 模式的简称,它是一种设计思想,意思是,把一个页面,当做一个对象,页面的元素
和元素之间操作方法就是页面对象的属性和行为,PO模式一般使用三层架构,分别为:基础封装层
BasePage,PO页面对象层,TestCase测试用例层。
对于简单的Selenium自动化测试,我们要做的不过是找到页面元素,并且值传递给这些元素。但是假如
有10个脚本同时调用了一个相同的页面元素,当这个元素发生改变,我们需要修改10个脚本。随着脚本 码同学 码同学 码同学 码同学 码同码同学
数的增加,时间工作复杂度也飞速增长。这个时候我们就可以考虑设计一个类,专门用来页面元素的查
找、传递值和修正。这样,当一个页面元素发生改变的时候,只用修改一个类,而不用同时修改10个脚
本。
Page Object是一种程序设计模式,将面向过程转变为面向对象(页面对象),将测试对象(按钮、输
入框、标题等)及单个的测试步骤封装在每个Page对象中,以page为单位进行管理。
这样,在Selenium测试页面中可以通过调用页面类来获取页面元素,从而巧妙的避免了当页面元素
id或者位置变化时,需要改测试页面代码的情况。当页面元素id变化时,只需要更改测试页Class中页面
的属性即可。可以使代码复用,降低维护成本,提高程序可读性和编写效率。
POM解决的问题:
以页面为单位,集中管理元素对象和方法。当页面元素或流程变动时只需要修改相关页面方法即
可,不需要修改相应脚本
编写脚本简单,顺着业务逻辑写脚本。page object模式以业务逻辑上的每一步操作作为区分点,
页面方法代表了此页面的一个业务操作并严格控制此操作的后续流程
后期维护方便
在编写PO前,建议先掌握以下几个知识点:
selenium库的基础运用
xpath语法
pytest 或者 unittest
面向对象中的类 和 继承
说在前边:PO模式是一种设计思想,在实际编码的时候可以有若干种实现方式。实际上,也建议
大家根据自己项目的情况来动态的编码。具体来说,常见的PO模式有:
1)三层:对象库层+case层+page层
2)四层:对象库层+case层+page层+公共类
编写步骤
定义页面元素实例属性
- 根据测试用例场景定义好所要用到的元素的实例属性
定义页面业务实例方法
- 一个页面可能存在多个业务方法,如登陆页面:登陆、找回密码等
抽取定位信息到实例属性
- 将元素定位的信息抽离到实例属性中进行统一管理,方便维护
非PO模式实现登录
class TestLogin(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get("http://hmshop-test.itheima.net/Home/user/login.html")
self.driver.implicitly_wait(10)
def tearDown(self) -> None:
sleep(5)
self.driver.quit()
def test01_login(self):
# 输⼊⽤户名
self.driver.find_element_by_css_selector("#username").send_keys("13600001111")
# 输⼊密码
self.driver.find_element_by_css_selector("#password").send_keys("123456")
# 输⼊验证码
self.driver.find_element_by_css_selector("#verify_code").send_keys("8888")
# 点击登录
self.driver.find_element_by_css_selector(".J-login-submit").click()
PO模式实现登录
-
结构
-
base: 存放所有page页面公共方法
-
script: 测试脚本
-
page: 将页面封装为对象
base层实现页面的公共方法
Base类:存放所有Page页面公共操作方法!
1 查找元素
2 输入方法
3 点击方法
4 获取文本值方法
base层实现
from selenium.webdriver.support.wait import WebDriverWait
'''
存放所有Page页面公共操作方法!
'''
# tpshop 登录
class Base:
def __init__(self, driver):
self.driver = driver
# 查找元素
def base_find(self, loc, timeout=3, frequency=1):
# 显示等待 loc -> [(By.Id,"userA")] 可以查找元素当超过时间后会报错
return WebDriverWait(self.driver, timeout=timeout, poll_frequency=frequency).until(
# loc[0], loc[1] ==*loc 解包
lambda x: x.find_element(*loc))
# 输入方法
def base_input(self, loc, value):
# 1 获取元素
el = self.base_find(loc=loc)
# 2 清空
el.clear()
# 3 输入
el.send_keys(value)
# 点击方法
def base_click(self, loc):
self.base_find(loc=loc).click()
# 获取文本值方法
def base_get_text(self, loc):
self.base_find(loc).text
page层实现
page层将页面的操作封装成对象 并调用base层的通用方法
page结构搭建
"""
模块名:page_模块单词
类名:大驼峰将模块移植进来,去掉下划线和数字。
方法:自动化测试当前页面要操作那些元素,就封装那些方法
"""
class PageLogin:
# 输入用户名
def __page_username(self):
pass
# 输入密码
def __page_pwd(self):
pass
# 输入验证码
def __page_verify_code(self):
pass
# 点击登录按钮
def __page_click_login_btn(self):
pass
# 获取昵称
def page_get_nickname(self):
pass
# 组合业务方法 (强调:测试业务成调用此方法,便捷。)
def page_login(self):
pass
page层完整代码
'''
将要操作的元素和操作的动作进行封装
'''
from selenium import webdriver
from selenium.webdriver.common.by import By
# *变量名 为解包给元素或者列表解包
from 黑马测试.po模式.po_01.Base.Base import Base
# 配置信息整理
username = By.CSS_SELECTOR, "#username" # 等价于(By.CSS_SELECTOR,"#username")
password = By.CSS_SELECTOR, "#password"
verfiy_code = By.CSS_SELECTOR, "#verify_code"
nick_name = By.CSS_SELECTOR, ".mu-m-phone"
login_click_btn = By.CSS_SELECTOR, ".J-login-submit"
class PageLogin(Base):
# 输入用户名
def __page_username(self, value):
self.base_input(username, value)
# 输入密码
def __page_pwd(self, value):
self.base_input(password, value)
# 输入验证码
def __page_verfiy_code(self, value):
self.base_input(verfiy_code, value)
# 点击登录
def __page_click_login_btn(self):
self.base_click(login_click_btn)
# 获取昵称 查看是否以登录
def get_nickname(self):
return self.base_get_text(nick_name)
# 组合业务方法 调试业务使用此方法
def page_login(self, name, pwd, code):
self.__page_username(name)
self.__page_pwd(pwd)
self.__page_verfiy_code(code)
self.__page_click_login_btn()
if __name__ == '__main__':
driver = webdriver.Chrome()
driver.maximize_window()
driver.get("http://tpshop-test.itheima.net/index.php/Home/user/login.html")
page = PageLogin(driver)
# driver.find_element()
page.page_login("15352932935", "123456", "8888")
print(page.get_nickname())
scirpt层实现
调用page层的方法 ,填充上数据,实现对用例的测试。
已经实现参数化
import unittest
from time import sleep
from selenium import webdriver
from 黑马测试.po模式.po_01.Page.page_login import PageLogin
from parameterized import parameterized
from 黑马测试.po模式.po_01.utils import read_json
class TestLogin(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.maximize_window()
self.driver.get("http://tpshop-test.itheima.net/index.php/Home/user/login.html")
self.login = PageLogin(self.driver)
def tearDown(self) -> None:
self.driver.quit()
@parameterized.expand(read_json("login.json", "login"))
def test01_login(self, username, password, code, expect):
try:
print(username,password,code,expect)
self.login.page_login(username, password, code)
sleep(3)
# html=self.driver.execute_script("return document.documentElement.outerHTML")
# print(html)
ncikname = self.login.get_nickname()
print(self.login.get_nickname())
self.assertEqual(ncikname, expect)
except Exception as e:
print("错误信息:",e)
login.json
{
"login": [
{"desc": "登录成功",
"username": "15352932935",
"password": "123456",
"code": "8888",
"expect":"15352932935"
}
]
}
读取json方法
import json
import os.path
def read_json(filename, key):
path = os.path.dirname(__file__) + "/" + "data" + "/" + filename
print(path)
with open(path, encoding="utf-8") as f:
arr = []
for data in json.load(f).get(key):
# print(data.values())
tmp = tuple(data.values())[1:]
arr.append(tmp)
return arr
# read_json("login.json", "login")