Page Object 设计思想
Page Object 就是在测试中将操作细节和验证分开的
设计原则 一
只为页面中重要的元素创建page类
设计原则 二
如果页面A 导航到页面B, Page A 应当return Page B
Pg object
PO 六大原则
1、一个 public 方法代表一个公共的服务。就是说一个方法代替页面上的某个操作
2、PageObject 中的方法细节不可暴露在外,通过提供公共服务接口的形式提供给外部
3、一般不需要在 PageObject 中断言
4、当有页面跳转的操作时候,执行这个方法时应该在方法结束返回时能够跳转到另一个页面中
5、我们只需要对页面中我们需要的重要的内容进行封装
6、页面中相同的组件,但是不同的操作应该要被拆成不同的方法进行封装
面试提问
1、Page Object 设计思想解决的是什么样的问题
1、代码冗余明显降低:二次封装Selenium方法和提取公共方法,提高代码复用性
2、代码的阅读性明显提升:因为三层分级,将不同内容进行不同的封装,整体代码阅读性提升
3、代码维护性明显提升:UI测试中,页面若经常变动,代码的维护量随之增多;因为三层分级,我们只需要修改页面对象的代码,如元素对象或者操作对象的方法,不用修改测试用例的代码,也不影响测试用例的正常执行
4、降低代码耦合性
2、Page Object 编写的思路
一个添加成员的步骤
1、登录_login页面
2、登录之后进入首页_main页面
3、点击添加成员_main页面
4、填写添加信息_add_member页面
5、点击保存_add_member页面
6、返回通讯录_add_membe页面
7、加断言做验证_contact页面
实战时序图建立
项目开始前,建立自己的一个时序图,便于思考
@startuml
autonumber
participant 企业微信主页面 as main
participant 通讯录页面 as contact
participant 添加成员页面 as add_member
main -> contact: 点击通讯录
main -> add_member: 点击添加成员
contact -> add_member: 点击添加成员
add_member -> contact: 填写成员资料, 点击保存
contact -> contact: 获取成员列表断言
@enduml
黄色长方形代表所在页面, 线代表在这个页面的操作,箭头代表跳转到哪个页面
实战练习
PO 构建图
1、返回值需要设置到箭头指向的地方
项目结构
注意:
在vscode中编辑,需要导入包的时候所有包结构下都要加入__init__.py
PO 文件夹为 构造PO模型
Po/base_function.py 基础的公用函数类,比如浏览器复用初始化
"""
公共函数类的继承
"""
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver import TouchActions
class PoBase:
# 添加baseurl, 可以支持测试用例的灵活配置起始页
# basepage 完全和项目解耦
_base_url = ""
def __init__(self, base_driver=None):
# base_driver, 解决链接不断初始化的过程
if base_driver is None:
self.chrome_options = Options()
self.chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222") # 指定配置好的 chrom
self.chrome_options.add_experimental_option("w3c", False)
self.chrome_driver = r"D:\my_project\git_deve\development\python_work\test_selenium\chromedriver.exe" # 驱动路径
self.driver = webdriver.Chrome(self.chrome_driver, chrome_options=self.chrome_options) # 加入驱动设置
self.driver.get(self._base_url) # 发起请求
# self.driver.maximize_window() # 设置为最大化
self.driver.implicitly_wait(5) # 添加一个隐式等待默认等待3秒
else:
self.driver = base_driver
# 封装driver,以便于添加Logger
def find_xpath(self, locator):
return self.driver.find_element_by_xpath(locator)
def finds_xpath(self, locator):
return self.driver.find_elements_by_xpath(locator)
testcase/test_add_member.py 为测试用例得一个编写文件是入口文件
"""
根据业务需要添加断言
通过链式调用的方式更加方便的描述业务逻辑
"""
from page_object_base_demo.po.main_page import MainPage
import pytest
class TestAddMember:
@pytest.mark.parametrize("name, id, phone", [('od', '123', '13111111111')]) # 参数化数据驱动
def test_add_member(self, name, id, phone):
main_page = MainPage()
# 1.跳转到add_member页面
# 2.做一个添加成员操作,然后点击保存 跳转到通讯录页面
# 3。通讯录页面获取成员信息。作为断言
assert "od" in main_page.goto_contact().goto_add_member().add_member(name, id, phone).get_member_list()
@ pytest.mark.parametrize("name, id, phone", [('keke', '1235', '13111111112')])
def test_index_member(self, name, id, phone):
main_page = MainPage()
# 1.直接从首页进入到添加成员
# 2.做一个添加成员操作,然后点击保存 跳转到通讯录页面
# 3。通讯录页面获取成员信息。作为断言
assert "keke" in main_page.goto_add_member().add_member(name, id, phone).get_member_list()
PO / main_page.py 根据上面的时序图编写,为入口文件, 里面的方法即为时序图下的两个箭头过程
两个函数返回的值为箭头指向的,另一个类里面的过程。
"""
编码第一步,构造PO模型,实现设置为空
构造页面相关类和方法
黄色的方块代表一个类
每条线代表这个页面提供的操作
箭头的始端为开始页面
箭头的末端为跳转页面, 即对应方法需要返回跳转页面的实例对象
实现暂时实际为空
这是个主页面
"""
import time
from page_object_base_demo.po.base_function import PoBase
class MainPage(PoBase):
_base_url = "https://work.weixin.qq.com/wework_admin/frame#index"
contact = "//span[@class='frame_nav_item_title'][contains(text(),'通讯录')]"
add_index = '//*[@id="_hmt_click"]/div[1]/div[4]/div[2]/a[1]/div/span[2]'
def goto_contact(self):
'''
跳转到通讯页
:return:ContactPage
'''
from page_object_base_demo.po.contact_page import ContactPage
self.find_xpath(self.contact).click()
return ContactPage(self.driver)
def goto_add_member(self):
'''
跳转到添加成员页
:return:
'''
from page_object_base_demo.po.add_member_page import AddMemberPage
self.find_xpath(self.add_index).click()
return AddMemberPage(self.driver)
PO / add_member_page.py 根据上面的时序图编写,为添加用户的两个过程办法:
'''
添加成员页面
'''
# from page_object_base_demo.po.contact_page import ContactPage
from page_object_base_demo.po.base_function import PoBase
class AddMemberPage(PoBase):
user_name = '//*[@id="username"]'
user_acctid = '//*[@id="memberAdd_acctid"]'
user_phone = '//*[@id="memberAdd_phone"]'
user_save = "//div[contains(@class,'member_edit')]//div[1]//a[2]"
def add_member(self, name, id, phone):
'''
添加成员操作
:return:
'''
from page_object_base_demo.po.contact_page import ContactPage
print('添加成员')
self.find_xpath(self.user_name).send_keys(name)
self.find_xpath(self.user_acctid).send_keys(id)
self.find_xpath(self.user_phone).send_keys(phone)
self.find_xpath(self.user_save).click()
return ContactPage(self.driver)
PO / contact_page.py 根据上面的时序图编写,跳转到通讯录页面的方法
'''
通讯录页面
'''
import time
from page_object_base_demo.po.base_function import PoBase
class ContactPage(PoBase):
add_people = "//div[contains(@class,'js_operationBar_footer ww_operationBar')]//a[contains(@class,'qui_btn ww_btn js_add_member')][contains(text(),'添加成员')]"
t_body = '//*[@id="member_list"]/tr'
def goto_add_member(self):
'''
点击跳转添加成员页
:return:
'''
time.sleep(1)
from page_object_base_demo.po.add_member_page import AddMemberPage
print('准备开始添加成员。。。')
self.find_xpath(self.add_people).click()
return AddMemberPage(self.driver)
def get_member_list(self):
'''
获取成员列表
:return: list
'''
time.sleep(1)
print('获取成员列表')
t_body = self.finds_xpath(self.t_body)
name_list = []
for i in t_body:
tdList = i.find_elements_by_tag_name("td")
name_list.append(tdList[1].text)
print(name_list)
return name_list
完。