selenium之PO设计模式

初识PO模式

PO(PageObject)是一种设计模式。简单来说就是把一些繁琐的定位方法、元素操作方式等封装到类中,通过类与类之间的调用完成特定操作。

PO被认为是自动化测试项目开发实践的最佳设计模式之一。

在学习PO模式前,可以先复习一下面向对象的编程思想。我觉得两者很像。

我之前写的一点笔记,可以参考。

测试菜鸟:Python中的面向对象与单例模式&反射(基础)1 赞同 · 0 评论文章​编辑

测试菜鸟:Python中的面向对象(高级)0 赞同 · 1 评论文章​编辑

优点

  • PO模式把页面元素定位和业务操作流程分开,界面元素的变化则不需要修改业务逻辑代码
  • PO能提高代码的可读性,高复用性,可维护性

设计准则

1.使用公共方法来代表页面提供的服务

2.不要暴露页面的内部细节(比如元素、元素的定位方法等),隔离测试用例和业务和页面对象

3.PO本身通常不应进行断言或判断。判断和断言是测试的一部分,而不是在PO中。

4.PO不一定需要代表整个界面,而是在测试中‘用到什么写什么’

5.相同的操作,但是数据不同,带来的不同结果可以封装成不同的方法。

6.方法可以返回其他的页面对象,进行页面的关联。

以上是比较官方的PO设计准则,我们需要根据具体业务的实际情况决定是完全遵循还是部分遵循。

selenium中的分层模型

  • 表现层:页面中可见的元素,都属于表现层。(元素定位器的编写)
  • 操作层:对页面可见元素的操作。(点击、输入文本等)
  • 业务层:上面2层的组合,联合到一起形成某个业务动作。
  • 测试用例:组合了一个或多个页面的方法,操作对应的元素,完成的测试。

PO模式实战

接下来就用PO模式完成一个简单的‘百度登录模块’的测试

思路:

1.创建一个elements.py存放登录界面所有的元素定位方式(用到哪个写哪个)

2.创建一个common_driver.py存放一些共用的浏览器相关方法

3.创建一个common_basepage.py存放共用的元素操作方法

4.创建一个test_cases.py文件存放测试用例

以下为部分代码:

  • 找到我们测试登录模块需要操作到的元素,将其定位方法写到elements.py中
#elements.py

class Elements():
    '''存放用到的所有元素定位器'''

    #登录前的界面元素
    LOGIN_BUTTON_OUT = ('id','s-top-loginbtn')#百度首页的‘登录’按钮
    LOGIN_WIN = ('id','TANGRAM__PSP_4__content')#登录窗口
    USERNAME_INPUT = ('id','TANGRAM__PSP_11__userName')#输入账号栏
    PASSWORD_INPUT = ('id','TANGRAM__PSP_11__password')#输入密码栏
    LOGIN_BUTTON_IN = ('id','TANGRAM__PSP_11__submit')#登录界面的‘登录’按钮

    #登录后的界面元素
    USER_INFO = ('css selector','#s-top-username > span.user-name.c-font-normal.c-color-t')#右上角的用户信息
    QUIT_BOTTON = ('css selector','#s-user-name-menu>.quit')#退出登录按钮
  • 浏览器相关操作放到common_driver.py中
# common_driver.py

from selenium import webdriver
from environment_config import Env

class Single(object):
    '''
    设计单例模式
    '''
    _instance = None #实例
    def  __new__(cls, *args, **kwargs):
        if cls._instance is None:     #此处是可以用__instance
            cls._instance = super().__new__(cls)
        return cls._instance
class Open_Driver(Single):
    '''
    打开一个浏览器
    '''
    driver = None
    def get_driver(self,browser_type=Env.BROWSER_TYPE,headless_flag=Env.HEADLESS_FLAG):
        '''
        根据参数打开想要的浏览器
        :param browser_type: 浏览器类型,读取Env文件中的值作为默认值
        :param headless_flag: 是否有头,读取Env文件中的值作为默认值,True/False
        :return: 返回一个浏览器对象
        '''
        if self.driver is None:
            if not headless_flag:#如果是有头模式
                if browser_type == 'chrome':
                    self.driver = webdriver.Chrome()
                elif headless_flag == 'firefox':
                    self.driver = webdriver.Firefox()
                else:
                    raise Exception(f'暂不支持{browser_type}浏览器')
            else:#如果是无头模式
                _option = webdriver.ChromeOptions()
                _option.add_argument('--headless')#添加无头模式参数'--headless'
                if browser_type == 'chrome':
                    self.driver = webdriver.Chrome(options=_option)
                elif headless_flag == 'firefox':
                    self.driver = webdriver.Firefox(options=_option)
                else:
                    raise Exception(f'暂不支持{browser_type}浏览器')
            self.driver.maximize_window()#窗口最大化
            self.driver.implicitly_wait(Env.IMPLICITLY_WAIT_TIME)#隐式等待,读取Env文件中IMPLICITLY_WAIT_TIME的值
        return self.driver #返回浏览器对象
  • 把要用到的元素操作方法写入到common_basepage.py中
# common_basepage.py
from common_driver import Open_Driver

class BasePage():
    '''
    存放所有界面元素操作方法
    '''
    def __init__(self):
        self.driver = Open_Driver().get_driver()

    def open_url(self,url):
        '''打开网址'''
        self.driver.get(url)
    def get_element(self,locator):
        '''
        定位元素
        :param locator:元素定位器,从elements中取
        :return: 元素对象
        '''
        return self.driver.find_element(*locator)

    def input_text(self,locator,text,append=False):
        '''
        在元素上输入文本
        :param locator: 元素定位器
        :param text: 要输入的文本
        :param append: 是否先清空,默认清空
        '''
        if append:#不需要清空内容,追加写入
            self.driver.find_element(*locator).send_keys(text)
        else:#先清空,再写入
            self.driver.find_element(*locator).clear()
            self.driver.find_element(*locator).send_keys(text)
    def click_element(self,locator):
        '''
        点击元素
        :param locator: 元素定位器
        '''
        self.driver.find_element(*locator).click()

    def ele_find_ele_input(self,locator1,locator2,text):
        '''
        在元素1上找元素2
        :param ele1: 元素1
        :param ele2: 元素2
        :return: 元素2
        '''
        return self.driver.find_element(*locator1).find_element(*locator2).send_keys(text)
    def get_element_text(self,locator):
        return self.driver.find_element(*locator).text
  • 页面对象loginpage.py
from common_basepage import BasePage
from datas import Datas
from elements import Elements
from logsuccesspage import LogSuccessPage

class LoginPage(BasePage):
    def open_loginpage(self,url):
        '''
        打开登录页
        :param url:登录页url
        :return: LoginPage实例对象
        '''
        self.open_url(url)
        return self
    def login_baidu(self,username,password):
        '''
        登录百度账号
        :param username: 用户名
        :param password: 密码
        :return: 登录成功后的页面对象
        '''
        self.click_element(Elements.LOGIN_BUTTON_OUT)#点击右上角登录
        self.ele_find_ele_input(Elements.LOGIN_WIN,Elements.USERNAME_INPUT,Datas.USERNAME)#输入账号
        self.ele_find_ele_input(Elements.LOGIN_WIN,Elements.PASSWORD_INPUT,Datas.PASSWORD)#输入密码
        self.click_element(Elements.LOGIN_BUTTON_IN)
        return LogSuccessPage()
  • 测试用例test_cases.py
from time import sleep
import pytest
from datas import Datas
from elements import Elements
from environment_config import Env
from loginpage import LoginPage

class Test_login():
    def test_login01(self):
        '''
        登录成功的测试
        :return:
        '''
        test_page = LoginPage()#创建实例
        test_page.open_loginpage(Env.TEST_URL)#打开测试url
        new_page=test_page.login_baidu(Datas.USERNAME,Datas.PASSWORD)#登录百度账号
        sleep(2)
        text = new_page.get_element_text(Elements.USER_INFO)#登录成功界面
        assert text == 'yvvgfffvbh'#断言用户名称是否正确

if __name__ == '__main__':
    pytest.main(['-vs'])
  • 运行结果

写完花了4个小时,感受就是:

1.要理解透彻Python中的面向对象思想。

2.写完整体结构后要继续优化。

可以看到,我们所有数据都放在配置文件中,代码中不会暴露任何的界面元素或账号数据。 最后用pytest执行测试用例即可。

以上只是一个最简版的PO模型项目。只是遵循了po设计准则,并不完整。

一个完整的selenium测试项目大体上应该包括:

1.tools 工具类,格式转换、路径操作等

2.commom 基类,一些公用的方法

3.pageobjects 页面对象类

4.testcases 测试用例

5.test_datas 测试数据,yaml/Excel文件等

6.outfiles 输出文件,log和截图等

7.testreport 测试报告

项目结构并没有具体标准,分类清晰即可。重要的是在设计过程中遵循上文说到的’设计准则‘。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/442354.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣日记3.8-【回溯算法篇】37. 解数独

力扣日记:【回溯算法篇】37. 解数独 日期:2023.3.8 参考:代码随想录、力扣 37. 解数独 题目描述 难度:困难 编写一个程序,通过填充空格来解决数独问题。 数独的解法需 遵循如下规则: 数字 1-9 在每一行只…

存货计价方式 比较-移动平均和批次计价

SAP常用的存货计价方式有 标准价格移动平均价格批次计价 标准价格常用于制造企业,今天的方案比较主要集中在销售型企业常用的移动平均价和批次计价 批次计价: 移动平均: 两种计价方式的Pros&Cons 比较 批次计价 移动平均优点 1…

基于单片机的水平角度仪系统设计

目 录 摘 要 I Abstract II 引 言 1 1控制系统设计 3 1.1系统方案设计 3 1.2系统工作原理 4 2硬件设计 6 2.1单片机 6 2.1.1单片机最小系统 6 2.1.2 STC89C52单片机的性能 7 2.2角度采集电路 8 2.2.1 ADXL345传感器的工作原理 9 2.2.2 ADXL345传感器倾角测量的原理 9 2.2.3 AD…

YOLOv8优化策略:特征融合篇 | GELAN(广义高效层聚合网络)结构来自YOLOv9

🚀🚀🚀本文改进:使用GELAN改进架构引入到YOLOv8 🚀🚀🚀YOLOv8改进专栏:http://t.csdnimg.cn/hGhVK 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1.YOLOv9介绍 论文: 2402.13616.pdf (arxiv.org) 摘要: 如今的深度学习方法重点关注如何设计最合适…

用 ChatGPT 搭配 STAR 原则,准备英文面试超轻松

用 ChatGPT 搭配 STAR 原则,准备英文面试超轻松 ChatGPT 除了可以帮忙改简历,在你的求职历程中,ChatGPT 也可以帮忙练英文面试。在我们实测之后,发现 ChatGPT 在练习英文面试上,不仅能针对你的回答给予回馈&#xff0…

Docker下Jenkins打包java项目并部署

docker 构建Jenkins sudo docker run --namezen_haslett --userjenkins --privilegedtrue --volume/home/cyf/server/jenkins/jenkins_home:/var/jenkins_home -v /usr/lib/jvm/java-17-openjdk-amd64:/usr/lib/jvm/java-17-openjdk-amd64 -v /usr/lib/maven/apache-mav…

FFmpeg--AAC音频解码流程

文章目录 AAC 组成函数分析读aac帧写aac帧aac的head参数设置 运行结果 AAC 组成 AAC音频格式:是⼀种由MPEG-4标准定义的有损⾳频压缩格式 ADTS:是AAC音频的传输流格式 AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成 每⼀帧的ADTS的头⽂件都包含了⾳频的采…

ArmSoM Rockchip系列产品 通用教程 之 GPIO 使用

1. GPIO简介​ GPIO,全称 General-Purpose Input/Output(通用输入输出),是一种在计算机和嵌入式系统中常见的数字输入输出接口。它允许软件控制硬件的数字输入和输出,例如开关、传感器、LED灯等。GPIO通常由一个芯片或…

C++矢量运算与java矢量运算

矢量运算 概述: 矢量运算是一种基于向量的数学运算,它遵循特定的法则。以下是矢量运算的一些基本原理: 矢量加法:可以使用平行四边形法则或三角形法则来执行。当两个矢量相加时,可以将它们的起点放在同一个点上&…

【硬件设计】(更新中)以 UCC27710 为例设计栅极驱动器元件选型(资料摘抄)

还没更新完。。。。。。。 【仅作自学记录,不出于任何商业目的。如有侵权,请联系删除,谢谢!】 本文摘抄翻译自: Bootstrap Network Analysis: Focusing on the Integrated Bootstrap Functionality (infineon.com)Boo…

[nlp入门论文精读] | Transformer

写在前面 最近工作从CV转向了NLP,于是空余时间便跟着哔哩哔哩李沐老师的视频学习。其实研一NLP课程讲论文的时候,我们小组就选择了经典的Attention和Bert,但还有很多细节并不完全理解,实际使用时也很困惑。 因此这个系列就来记…

【Android 内存优化】KOOM 快手开源框架线上内存监控方案-源码剖析

文章目录 前言OOMMonitorInitTask.INSTANCE.initOOMMonitor.INSTANCE.startLoopsuper.startLoopcall() LoopState.Terminate dumpAndAnalysisdumpstartAnalysisService回到startLoop方法总结 前言 这篇文章主要剖析KOOM的Java层源码设计逻辑。 使用篇请看上一篇: 【Android …

Kubesphere前端项目分析

1 KubeSphere console功能导图 模块: 命令行工具 (kubectl) 日志(Logging) 平台设置(Platform Settings) 服务组件(Service Components) 监控和警报(Monitoring & Alerting&…

手写分布式配置中心(六)整合springboot(自动刷新)

对于springboot配置自动刷新,原理也很简单,就是在启动过程中用一个BeanPostProcessor去收集需要自动刷新的字段,然后在springboot启动后开启轮询任务即可。 不过需要对之前的代码再次做修改,因为springboot的配置注入value("…

pytest教程-15-多个fixture以及重命名

领取资料,咨询答疑,请➕wei: June__Go 上一小节我们学习了fixture的yield关键字,本小节我们讲解一下使用多个fixture的方法。 使用多个fixture 如果用例需要用到多个fixture的返回数据,fixture也可以return一个元组、list或字…

[嵌入式系统-37]:龙芯1B 开发学习套件 -7-MIPS指令集

目录 一、MIPS指令分类 二、常用指令详解 三、常用MIPS指令集及格式: 四、CPU内部的32个寄存器: 一、MIPS指令分类 MIPS(Microprocessor without Interlocked Pipeline Stages)指令集是一种广泛用于教学和嵌入式系统的指令集…

在线部署ubuntu20.04服务器,安装jdk、mysql、redis、nginx、minio、开机自启微服务jar包

一、服务器 1、查看服务器版本 查看服务器版本为20.04 lsb_release -a2、服务器信息 服务器初始账号密码 sxd / 123456 首先,更改自身密码都输入123456 sudo passwd 创建最高权限root账号,密码为 123456 su root 3、更新服务器源 1、更新源列表 sudo apt-g…

【golang】Windows与Linux交叉编译保姆级教程

【golang】Windows与Linux交叉编译 大家好 我是寸铁👊 总结了一篇【golang】Windows与Linux交叉编译的文章✨ 喜欢的小伙伴可以点点关注 💝 问题背景 今天寸铁想将Windows中的程序部到Linux下跑,我们知道在从Windows与Linux下要进行交叉编译…

11、Linux-安装和配置Redis

目录 第一步,传输文件和解压 第二步,安装gcc编译器 第三步,编译Redis 第四步,安装Redis服务 第五步,配置Redis ①开启后台启动 ②关闭保护模式(关闭之后才可以远程连接Redis) ③设置远程…

Java基础 - 8 - 算法、正则表达式、异常

一. 算法 什么是算法? 解决某个实际问题的过程和方法 学习算法的技巧? 先搞清楚算法的流程,再直接去推敲如何写算法 1.1 排序算法 1.1.1 冒泡排序 每次从数组中找出最大值放在数组的后面去 public class demo {public static void main(S…