PO模式

简介: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'])
        # 断言.....

你可能感兴趣的:(PO模式)