简介:PO模式(Page Object), 是自动化测试中最为流行且最为熟悉和推崇的一种设计模式。PO模式把页面元素和元素的操作方法按照页面抽象出来,分离成一定的对象,然后再进行组织。
介绍
做web自动化最头疼的问题是页面变化了,如果没有使用PO设计模式,页面一变化就意味着之前写的元素定位不能用了,需要重新修改,这样就需要一个个从测试脚本中把需要修改的元素定位找出来,然后一一修改。自动化脚本不但繁琐,而且成本极高。
PO模式就可以很好地解决这个问题,那如何操作呢?
一般对脚本进行分层:
- 基础层:点击、输入等操作加入一些等待、日志输入、截图等操作,方便以后查看脚本的运行情况及问题排查
- 对象逻辑层:用于存放页面元素定位和存放一些封装好对功能用例模块
- 业务层:用于存放真正的测试用例的操作部分
优点:
- 减少代码冗余
- 业务和实现分离
- 降低维护成本
基础层
后续的对象层操作元素时都继承这个基础类。
'''
Author: daju.huang
Date: 2021-07-22 15:40:38
LastEditors: daju.huang
LastEditTime: 2021-07-25 12:59:58
FilePath: /webUIAutoTest/public/page_obj/base.py
'''
import os,sys
from time import sleep
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from config import setting
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchFrameException,NoSuchWindowException,NoAlertPresentException,NoSuchElementException
import configparser
from public.models.log import Log
log = Log()
class Page(object):
'''
@description: 基础类,用于页面对象类的继承
@param {*} self
@param {*} selenium_driver
@param {*} base_url
@param {*} parent
@return {*}
'''
def __init__(self,selenium_driver,base_url,parent=None) -> None:
self.driver = selenium_driver
self.base_url = base_url
self.parent = parent
self.timeout = 10
'''
@description: url地址判断是否是当前页面
@param {*} self
@return {*}
'''
def on_page(self):
return self.driver.current_url == (self.base_url + self.url)
'''
@description: 打开浏览器URL访问
@param {*} self
@param {*} url URL地址
@return {*}
'''
def _open(self,url):
url = self.base_url + url
self.driver.get(url)
assert self.on_page(),'Did not land on %s' % url
'''
@description: 内部调用_open私有函数
@param {*} self
@return {*}
'''
def open(self):
self._open(self.url)
'''
@description: 单个元素定位
@param {*} self
@param {array} loc 传入元素属性
@return {*} 定位到的元素
'''
def find_element(self, *loc):
try:
WebDriverWait(self.driver, self.timeout).until(EC.visibility_of_element_located(loc))
return self.driver.find_element(*loc)
except:
log.error("{0}页面中未能找到{1}元素".format(self,loc))
'''
@description: 多个元素定位
@param {*} self
@param {array} loc 传入元素属性
@return {*} 定位到的元素
'''
def find_elements(self, *loc):
try:
WebDriverWait(self.driver, self.timeout).until(EC.visibility_of_element_located(loc))
return self.driver.find_elements(*loc)
except:
log.error("{0}页面中未能找到{1}元素".format(self,loc))
'''
@description: 单个元素定位
@param {*} self
@param {array} loc 传入元素属性
@return {*} 定位到的元素
'''
def find_element_xpath(self, loc_str):
try:
return self.driver.find_element_by_xpath(loc_str)
except:
log.error("{0}页面中未能找到{1}元素".format(self,loc_str))
'''
@description: 提供调用JavaScript方法
@param {*} self
@param {*} src 脚本文件
@return {*} JavaScript脚本
'''
def script(self,src):
return self.driver.execute_script(src)
'''
@description:
@param {*} self
@param {*} loc
@param {*} value
@param {*} clear_first
@param {*} click_first
@return {*}
'''
def send_key(self, loc, value, clear_first=True, click_first=True):
try:
loc = getattr(self, "_%s" % loc) # getattr相当于实现self.loc
if click_first:
self.find_element(*loc).click()
if clear_first:
self.find_element(*loc).clear()
self.find_element(*loc).send_keys(value)
except AttributeError:
log.error("%s 页面中未能找到 %s 元素" % (self, loc))
'''
@description: 多表单嵌套切换
@param {*} self
@param {*} loc 传元素的属性值
@return {*} 定位到的元素
'''
def switch_frame(self, loc):
try:
return self.driver.switch_to_frame(loc)
except NoSuchFrameException as msg:
log.error("查找iframe异常->{0}".format(msg))
'''
@description: 多窗口切换
@param {*} self
@param {*} loc
@return {*}
'''
def switch_windows(self,loc):
try:
return self.driver.switch_to_window(loc)
except NoSuchWindowException as msg:
log.error("查找窗口句柄handle异常->{0}".format(msg))
'''
@description: 警告框处理
@param {*} self
@return {*}
'''
def switch_alert(self):
try:
return self.driver.switch_to_alert()
except NoAlertPresentException as msg:
log.error("查找alert弹出框异常->{0}".format(msg))
对象逻辑层
存放页面元素定位和元素操作方法,实现一个页面一个模块,后续页面元素发生变化,只需要修改这个模块中对应的定位表达式或者操作方法即可。
# baidu_page.py
from selenium.webdriver.common.by import By
from common.basepage import BasePage
class LoginPage(Base):
login_btn = (By.XPATH, '//div[@id="u1"]//a[@name="tj_login"]') # 登录按钮
username_login_btn = (By.ID, 'TANGRAM__PSP_11__footerULoginBtn') # 用户名登录按钮
user_input = (By.ID, 'TANGRAM__PSP_11__userName') # 用户信息输入框
pwd_input = (By.ID, 'TANGRAM__PSP_11__password') # 密码输入框
login_submit = (By.ID, 'TANGRAM__PSP_11__submit') # 登录提交按钮
"""
百度用户名登录
:param user: 手机/邮箱/用户名
:param pwd: 密码
:return:
"""
def login(self, user, pwd):
self.click_element(self.login_btn, '百度-登录')
self.click_element(self.username_login_btn, '百度登录-用户名登录')
self.input_text(self.user_input, user, '用户名登录-手机/邮箱/用户名')
self.input_text(self.pwd_input, pwd, '用户名登录-密码')
self.click_element(self.login_submit, '用户名登录-登录')
业务层
用于存放真正的测试用例操作,这里不会出现元素定位、页面功能,所有操作都是直接调用逻辑层的。
import unittest
import pytest
import ddt
from selenium import webdriver
from PageObjects.baidu_login_page import LoginPage
from testdatas import common_datas as com_d
from testdatas import login_data as lo_d
from common.logging import log
@ddt.ddt
class TestLogin(unittest.TestCase):
def setUp(self):
log.info("-------用例前置工作:打开浏览器--------")
self.driver = webdriver.Chrome()
self.driver.get(com_d.baidu_url)
self.driver.maximize_window()
def tearDown(self):
self.driver.quit()
log.info("-------用例后置工作:关闭浏览器--------")
@pytest.mark.smoke
def test_login_success(self):
# 用例:登录页的登录功能
# 步骤
LoginPage(self.driver).login(lo_d.success_data['user'], lo_d.success_data['pwd'])
# 断言.....
网友评论