PO:Page Object,po是一种设计模式,提供通用的方法,简单来说就是分层设计。
PO在Selenium中的应用,官方文档:https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/
场景:我们最初做UI自动化测试时,一个python文件可以实现整个流程的操作,那么会带来的问题有:
- 代码可读性差,一旦有一个元素的位置发生改动,改起来特别费劲;
- 代码维护性差,时间久了看不懂,别人也看不懂;
- 可扩展性弱,比如登录,这个页面需要登录,到了另外一个页面可能还需要登录,需要再写一遍。
要解决这种问题,我们需要把场景抽象化,通过PO思想进行封装。把操作细节封装成方法,对外只提供方法,不提供细节。当需要修改时只改操作细节,不改对外暴露的方法。但是,不是所有页面都封装,只封装主要模块,不封装次要模块。
PO原则
- 用一个方法代替页面的服务
- 对外提供的方法不暴露细节
- 对外提供的方法中不包含断言
- 如果页面a导航到页面b,page a应该return page b
- 不为页面的所有元素封装方法
- 当出现正确的和错误的结果时,我们封装时要分开写,不要揉到一起写
举个例子,以企业微信后台为例,先构思一下用例:
用PO思想,我需要创建几个文件:
- main_page.py:主页的po,提供主页的方法,不提供细节
- base_page.py:抽离通用方法的po
- add_member_page.py:添加成员页po,继承base_page,对应具体添加成员的操作细节
- contact_page.py:通讯录页的po,继承base_page,对应具体通讯录的操作细节
- test_add_member.py:添加成员的测试用例及断言
main_page.py
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
from test_selenium.work_weixin.api.base_page import BasePage
from test_selenium.work_weixin.api.contact_page import ContactPage
class MainPage(BasePage):
"""
首页
"""
_base_url = 'https://work.weixin.qq.com/wework_admin/frame#index'
def goto_contact(self):
"""
跳转通讯录页面
:return:
"""
return ContactPage()
def goto_add_member(self):
"""
跳转添加成员页面
:return:
"""
self.driver.find_element(By.XPATH, '//*[@id="_hmt_click"]/div[1]/div[4]/div[2]/a[1]/div/span[2]').click()
sleep(1)
return AddMemberPage(self.driver)
base_page.py
from time import sleep
import yaml
from selenium import webdriver
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.opera.options import Options
class BasePage:
"""
封装所有页面对象通用的操作,如driver的实例化
"""
_base_url = None
def __init__(self, base_driver=None):
"""
子类没有构造函数,在实例化过程中会自动调用父类的构造函数,所以每个PO在实例化过程中都会执行构造函数的逻辑,这样就会初始化多个driver
所以需要避免driver的重复实例化
"""
# 如果base_driver为真,就不需要重复driver实例化操作
if base_driver:
self.driver =base_driver
# 如果base_driver为None,就需要对driver实例化
else:
# 不加self是局部变量
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(5)
# url优化
if self._base_url != None:
self.driver.get(self._base_url)
cookie = yaml.safe_load(open('../data/login_cookie.yml'))
print(cookie)
# 种cookie
for c in cookie:
self.driver.add_cookie(c)
sleep(2)
self.driver.get(self._base_url)
def find(self, by, locaotor):
"""
封装find_element操作
:param by:
:param locaotor:
:return:
"""
res = self.driver.find_element(by, locaotor)
print(f"找到的元素为{res}")
return res
add_member_page.py
from time import sleep
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
from test_selenium.work_weixin.api.base_page import BasePage
from test_selenium.work_weixin.api.contact_page import ContactPage
class AddMemberPage(BasePage):
"""
添加成员页
"""
_base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'
def goto_contact(self):
"""
跳转通讯录页面
:return:
"""
return ContactPage()
def add_member(self, username, acctid, phonenum):
"""
添加成员操作
:return:
"""
self.find(By.ID, 'username').send_keys(username)
self.find(By.ID, 'memberAdd_acctid').send_keys(acctid)
self.find(By.ID, 'memberAdd_phone').send_keys(phonenum)
self.driver.find_element(By.CSS_SELECTOR, '.js_btn_save').click()
return ContactPage(self.driver)
def add_member_fail(self):
"""
添加成员操作
:return:
"""
self.find(By.ID, 'username').send_keys('')
self.find(By.ID, 'memberAdd_acctid').send_keys('111')
self.find(By.ID, 'memberAdd_phone').send_keys('13600001243')
error_list = self.driver.find_elements(By.CSS_SELECTOR, '.ww_inputWithTips_tips')
print(error_list)
# 寻找所有的错误信息,如果不为空,则返回
err_message = [ele.text for ele in error_list if ele.text != ""]
return err_message
return ContactPage(self.driver)
contact_page.py
from test_selenium.work_weixin.api.base_page import BasePage
class ContactPage(BasePage):
"""
通讯录列表页
"""
_base_url = 'https://work.weixin.qq.com/wework_admin/frame#contacts'
def goto_add_member(self):
"""
添加成员
:return:
"""
# 如果出现A到B,B到A循环导入的场景,需要把其中一个的导包放在方法里
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
return AddMemberPage()
def member_list(self, member_name):
"""
成员列表list
:return:
"""
return ['member_name']
test_add_member.py
from test_selenium.work_weixin.api.add_member_page import AddMemberPage
from test_selenium.work_weixin.api.contact_page import ContactPage
from test_selenium.work_weixin.api.main_page import MainPage
class TestAddMember:
"""
添加成员的测试用例
"""
def setup_class(self):
"""
页面实例化MainPage
:return:
"""
self.main = MainPage()
def test_add_member(self):
"""
1.在首页,点击添加成员按钮,跳转到添加成员页面
2.在添加成员页面,填写成员信息,点击保存
3.在通讯录页面查看信息是否添加成功
:return:
"""
# # 方法一
# # 页面实例化,调用过于复杂
# main = MainPage()
# add_member_page = AddMemberPage()
# contact_page = ContactPage()
#
# # 1.在首页,点击添加成员按钮,跳转到添加成员页面
# main.goto_add_member()
#
# # 2在添加成员页面,填写成员信息,点击保存
# add_member_page.add_member()
#
# # 3.在通讯录页面查看成员是否添加成功
# contact_page.member_list()
# 方法二
username, acctid, phonenum = "吱吱3", '3', '18700001234'
# 通过前面return实例对象,可以直接用方法.方法的方式,实现链式调用
res = self.main.goto_add_member().add_member(username, acctid, phonenum).member_list('member_name')
# 断言与手工测试一致,断言添加的成员是否在结果中,如果在表示用例通过,否则失败
assert 'member_name' in res
def test_add_member_fail(self):
"""
反向用例,成员名称为空
:return:
"""
error_list = self.main.goto_add_member().add_member_fail()
assert "请填写姓名" in error_list
def teardown_class(self):
self.main.driver.quit()
源码彩蛋》》》
链接: https://pan.baidu.com/s/14p0Q38whTbjlRwlMZA-iVg
提取码: 7wzh