PageObject模式
# PageObject设计模式
# 1.什么是设计模式
设计模式是在软件开发过程中面临的一系列问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
是为了重用代码、让代码更容易被他人理解、保证代码可靠性。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
# 2.什么是Page Object Model设计模式
对于简单的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层+公共类
这里先介绍下几个层级:
业务封装层:
对象库:将元素定位表达式储存在这个层,发昂变维护
page层:需要将前端操作,按照业务封装成不同方法。例如:下单操作→def order():
selenium查找元素、selenium操作,都写在这。但要注意,这里一般按照页面维度来进行编写。例如:
登录、获取优惠券的按钮均在“首页”,故:首页_page.py中应该有 func_登录() 和 func_领取优惠券()
case层:使用“业务封装层”中写好的方法,拼装出一个完整的测试场景 并且 添加检查点
公共层:如果框架中,需要进行日志记录、数据库连接、加解密、复杂计算等功能,均在这个层来编写
# 3.怎么实现一个PO项目
# 1.先调通目标业务的selenium流程脚本,如下:
from selenium import webdriver
import time
def login(name,pw=123456):
driver = webdriver.Chrome()
driver.maximize_window()
driver.get('填写本地url')
driver.find_element_by_xpath('//div[@class="member-login"]/a[text()="登录"]').click()
time.sleep(2)
#找到用户名\密码框,并且进行输入
driver.find_element_by_xpath('//label[text()="登录账号"]/following-sibling::input').send_keys(name)
time.sleep(1)
driver.find_element_by_xpath('//label[text()="登录密码"]/following-sibling::input').send_keys(pw)
time.sleep(1)
#点击登录按钮
driver.find_element_by_xpath('//button[text()="登录"]').click()
#要完善的第一点:需要检查登录成功
#要完善的第一点:需要保存,当前登陆成功的浏览器状态
return driver
var=login('Bull')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2.抽离所有定位表达式到元素层
定位表达式、url、账号、密码等,均可以抽离出来
url_online: http://121.42.15.146:9090/mtx/
url_offline: http://192.168.3.186/mtx/
url_now: http://121.42.15.146:9090/mtx/
LoginButton: //a[@class="am-btn-primary btn am-fl"]
LoginName: //label[text()="登录账号"]/following-sibling::input
LoginPw: //label[text()="登录密码"]/following-sibling::input
LoginButton2: //button[text()="登录"]
2
3
4
5
6
7
# 3.编写业务函数
# 这里经常使用base-基础类来提高复用性,例如以下
import time
from selenium.webdriver.support.wait import WebDriverWait
class BasePage:
def __init__(self, driver):
self.driver = driver
def base_find_element(self,xp,timeout=5,poll=0.5):#智能的等待元素30秒,如果找到了元素就把“元素”作为返回值return出来
return WebDriverWait(self.driver, timeout=timeout, poll_frequency=poll).until(lambda dev: dev.find_element_by_xpath(xp))
def base_click(self,xp):#包装了js点击,调用更简单
self.driver.execute_script("arguments[0].click();", self.base_find_element(xp))
def base_wait(self,s):
s=int(s)
time.sleep(s)
def base_input(self,text,xpath):#包装selenium的send_keys
# el = self.driver.find_element_by_xpath(xpath) #查找输入元素
el = self.base_find_element(xpath) #希望用智能等待的方法优化稳定
el.clear() #尝试对输入框进行“清空”
el.send_keys(text) #调用send_keys()进行输入
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 再编写具体页面类
import yaml
from selenium import webdriver
from day7.Page.BasePage import BasePage
from day7.data.Config import Config
class MtxIndex(BasePage):
elements = {}
with open("../data/data.yml", encoding='utf-8') as file:
elements = yaml.safe_load(file)
def IntoLogin(self):
# xp='//a[@class="am-btn-primary btn am-fl"]'
self.driver.find_element_by_xpath(self.elements['LoginButton']).click()#点击“登录”按钮
return self.driver#如果不写return,就会返回一个None
def input_search(self,text):
input=self.base_input(text,self.elements['input'])
self.base_wait(4)
if __name__ == '__main__':
dev = webdriver.Chrome()
dev.maximize_window()
dev.get('请填入你的url')
MtxIndex(dev).IntoLogin()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4.编写case
import pytest
# 编写case,用到了那个页面的动作,就导入那个页面
from day7.Page.MtxIndex import MtxIndex
from day7.Page.MtxDriver import MtxDriver
from day7.Page.LoginPage import Login
class TestLoginCase:#新建测试用例类(例如:下单case、登录case。在这里来写)
# def __init__(self):#执行用例首先需求一个“浏览器”也就是一个webdriver对象
# self.dev = MtxDriver().start()
def setup_class(self):
self.dev = MtxDriver().start()
def test_Into_LoginPage_case(self):
#调用写好的操作逻辑
into = MtxIndex(self.dev)#实例化一个MtxIndex页面对象
res = into.IntoLogin() #利用MtxIndex实例,调用已经写好的“IntoLogin”动作
# assert 表达式(表达式最终返回True/False)
print(res.title)#在运行的时候没有输出
assert res.title == "用户登录 - 码同学实战系统"
def test_Login_act(self):
login = Login(self.dev)
res = login.login()
xp="//em[text()='Bull']"#检查页面里有我的登录名出现
assert res.find_element_by_xpath(xp)
@pytest.mark.parametrize('text',['正确的搜索项','异常符号','乱码'])
# # @参数化装饰器
# # @报告标题装饰器(“case标题”)
def test_search(self,text):
search=MtxIndex(self.dev)
search.input_search(text)
# 原始的做法:
# 有新内容,就在原始方法里边加代码
# +下单
def teardown_class(self):
self.dev.quit()
if __name__ =='__main__':#判断是当前这个python文件(LoginPage)在进行调用
pytest.main()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43