在使用 Python 进行编码的时候,会使用自身自带的编码设计格式,比如说最常见的单例模式,稍微抽象一些的抽象工厂模式等等… 在利用 Python 做自动化测试的时候,是不是也有自己的设计模式呢?所以在今天这个小章节里,需要续了解的就是 python 作为自动化测试里面的一种设计模式,尤其是 UI自动化 的专属模式 —> “PageObject” 自动化设计模式,简称 “PO模式” 。
了解并实现 “PageObject” 自动化设计模式
什么是PO模式
一种在测试自动化中变得流行的设计模式,使得自动化测试脚本的代码量减少,避免代码重复,更加易读,减少维护的成本。
其实简单来说就是将页面的操作、脚本的Case、通用的页面元素分开的这样一个模式。
一般 PO 设计模式多数分为三层
PO 三层模式
第一层:(核心、BasePage层)
- 对 Selenium 的底层进行二次封装,定义一个所有页面都继承的基础属性页面 —> BasePage 。
- 封装 Selenium 的基本方法,例如:元素定位、元素等待、导航页面、页面跳转等等...
- PS:其实在使用的过程中不需要全部封装,用到多少方法就封装多少方法即可。(之前接触过其他大佬的自动化框架,他把所有的 selenium 的底层的方法做了一层封装,这样做很好,能够做很多的事情,但是比较繁重。实际上在真实使用的时候用不到那么多,所以不建议全部封装)。
第二层:(页面层、也叫配置层)
- 页面元素进行分离,每个元素只定位一次,隔离定位。如果页面改变,只需要改变相应的元素定位。
- 如果存在一些业务的属性、方法,需要将其通过业务方法的方式将业务与操作元素的动作分离开来。
第三层:(封装测试层)
使用单元测试框架对业务逻辑进行封装测试
PO 设计模式的优点
UI 页面的频繁变化,导致页面 UI 元素频繁的变动,PO设计模式便于元素定位改变的维护。
传统线性自动化,多个用例脚本中需要反复的定位同一个元素,PO设计模式可以减少这部分频繁定位元素的代码量
小节:减少重复代码的冗余,便于UI页面频繁变更下的元素定位维护。
将改写的脚本转为PO设计模式
首先在项目里创建一个 python package 命名为 pages ,然后在 pages 创建一个模块 base_page.py 用来作为第一层的 base_page核心层 。
如下图:
构建基础的 BasePage 层
尝试构建最基础的 base_page 层,代码示例如下:
1 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 | # coding:utf-8 from selenium import webdriver class BasePage( object ): """ 1、第一层 - 核心层-BasePage层,定义一个所有页面都继承的page层 2、对将要使用的 selenium 的底层方法进行二次封装 """ def __init__( self , driver, path = None ): # 构造函数,类的初始化 """ 为了方便编写将 driver 初始化, 先使用 "self.driver = webdriver.Chrome()" 后续改为 self.driver = driver """ self .driver = webdriver.Chrome() # self.driver = driver self .driver.implicitly_wait( 5 ) # 定义全局的默认加载时间 self .load_page(path) # 访问并加载网页 def load_page( self , path = None ): # 访问并加载网页,如果 path 不为空的话,直接传给 driver.get() 访问 if path is not None : self .driver.get(path) def by_xpath( self , xpath): # 二次封装 selenium 的 xpath 元素定位 return self .driver.find_element_by_xpath(xpath) def js_click( self , xpath): # JavaScript 定位元素,并执行 click self .driver.execute_script( 'arguments[0].click()' , self .by_xpath(xpath)) |
到这里,base_page 层算是写完了,这就是一个最底层、最基础的类,这个类让我们实现了 selenium 底层的 Xpath 定位方法 与 JavaScript 定位元素方法,这些方法能够帮助我们更好的去完成后续的定位处理操作。
ok,接下我们再去编写各个页面层的东西。
构建首页的 Page 层(HomePage)
代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # coding:utf-8 from selenium import webdriver from pages.base_page import BasePage # 导入 base_page 层 class HomePage(BasePage): # 定义 FirstPage(继承 BasePage ) """ 1、第二层 - 各个页面单独封装成层,页面的元素、操作、流程 """ def direct_to_login( self ): # 首页跳转至登录页 return self .by_xpath( "//*[@id='app']/div[1]/div[5]/div[3]" ) def direct_to_product( self ): # 登陆成功后,跳转至首页 return self .by_xpath( "//*[@id='app']/div[1]/div[5]/div[1]" ) # 方法流程 def cross_to_login( self ): self .direct_to_login().click() # 点击 "登录" 按钮进行登录 def cross_to_product( self ): self .direct_to_product().click() # 点击 "首页" 跳转至首页 |
构建登录页的 Page 层(LoginPage)
代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # coding:utf-8 from selenium import webdriver from pages.base_page import BasePage # 导入 base_page 层 class LoginPage(BasePage): # 定义 FirstPage(继承 BasePage ) """ 1、页面层(登录页) - 各个页面单独封装成层,页面的元素、操作、流程 """ def login_username( self ): # 登录页 - 用户名输入框 return self .by_xpath( "//*[@id='app']/div[1]/form/div[1]/div[2]/div/input" ) def login_password( self ): # 登录页 - 密码输入框 return self .by_xpath( "//*[@id='app']/div[1]/form/div[2]/div[2]/div/input" ) def login_button( self ): # 登录页 - 登录按钮 return self .by_xpath( "//*[@id='app']/div[1]/form/div[3]/button" ) # 登录Case def login( self , username, password): # 登录方法,传入 username 与 password self .login_username().send_keys(username) self .login_password().send_keys(password) self .login_button().click() |
构建 首页 - 订单 - 支付 流程的 Page 层(OrderPage)
1 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 | # coding:utf-8 from time import sleep from pages.base_page import BasePage # 导入 base_page 层 class OrderPage(BasePage): # 定义 FirstPage(继承 BasePage ) """ 1、页面层(登录页) - 各个页面单独封装成层,页面的元素、操作、流程 """ def product( self ): # 下单 - 第一个产品 return self .by_xpath( "//*[@id='app']/div[1]/div[4]/div[2]/a[1]" ) def ticket_book( self ): # 门票 - 预定(按钮) return self .by_xpath( "//*[@id='app']/div[1]/div[5]/div[2]/div[2]/a" ) def book_date( self ): # 门票 - 选择日期 return self .by_xpath( "//*[@id='app']/div[1]/form/div[1]/div[1]/div[2]/div/input" ) def to_order( self ): # 门票下单 return self .by_xpath( "//*[@id='app']/div[1]/form/div[4]/div/button" ) def pay_off( self ): # 门票下单 - 支付 return self .by_xpath( "//*[@id='app']/div[1]/form/div/div/button" ) def confirm( self ): # 门票下单 - 确认支付 return self .by_xpath( "/html/body/div[5]/div[3]/button[2]" ) # 下单成功Case def place_order( self ): self .product().click() self .ticket_book().click() self .book_date().send_keys( "2022-06-16" ) self .to_order().click() sleep( 2 ) element = self .pay_off() self .driver.execute_script( 'arguments[0].click()' , element) sleep( 2 ) |
以上,我们准备的所有页面需要准备的元素定位、基线流程算是写完了,但是具体的用例,应该如何实现呢?继续往下看。
PO 设计模式下测试Case的改造
代码示例如下:
1 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 44 45 46 47 48 49 50 51 | # coding:utf-8 import unittest from time import sleep from selenium import webdriver from pages.home_page import HomePage from pages.login_page import LoginPage from pages.order_page import OrderPage ''' 1、初始化 - 打开浏览器,设置浏览器大小 2、最终操作 - 关闭浏览器 3、用例部分 - 登录 与 购买操作、下订单、支付 ''' class TestTravel(unittest.TestCase): @classmethod def setUpClass( cls ): # 每个测试类在加载之前执行一次 setUpClass ,初始化方法 cls .driver = webdriver.Chrome() cls .driver.maximize_window() def test_a_order( self ): #初始化参数 username = '13500000001' password = 'Success@2020' #初始化界面 home_page = HomePage(driver = self .driver, path = "http://django.t.mukewang.com/#/" ) login_page = LoginPage(driver = self .driver) order_page = OrderPage(driver = self .driver) #跳转登录 home_page.cross_to_login() #登录 login_page.login(username, password) # 跳转至订单页 home_page.cross_to_product() #下单 order_page.place_order() @classmethod def tearDownClass( cls ): cls .driver.quit() # 彻底退出浏览器 if __name__ = = '__main__' : unittest.main() |
这里改造完成之后,记得将 "BasePage 层" 的 '# self.driver = driver' 取消注释,并将 'self.driver = webdriver.Chrome()' 注释掉 。
以上就是一个比较完整的通过 PO 的方式来连接三个页面与基础的 base_page 来写出的更简洁一些的测试用例。
运行结果如下:(速度可能过快,担待一下,gif 只有15秒的时间)
现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】
最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】