初识PO模式并在Selenium中简单实践

初识PO模式

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

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

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

优点

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

设计准则

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

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

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

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

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

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

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

​现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】

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 测试报告

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

 

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】

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

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

相关文章

太快了!文生图片只需1秒,开源SDXL Turbo来啦!

11月29日,著名开源生成式AI平台Stability.ai在官网发布了,开源文生图模型SDXL Turbo。 根据使用体验,SDXL Turbo的生成图像效率非常快,可以做到实时响应(可能小于1秒)。 在你输入完最后一个文本后&#x…

基于模块暴露和Hilt的Android模块化方案

ModuleExpose 项目地址:https://github.com/JailedBird/ModuleExpose 序言 Android模块化必须要解决的问题是 如何实现模块间通信 ?而模块之间通信往往需要获取相同的实体类和接口,造成部分涉及模块通信的接口和实体类被迫下沉到基础模块&…

Nginx性能调优策略

Nginx是一个高性能的Web服务器和反向代理服务器,常用于处理高并发的请求。以下是一些常见的Nginx性能调优策略: 一、调整worker_processes和worker_connections 在Nginx配置文件中,可以通过worker_processes和worker_connections参数来调整w…

vue2.6源码分析

vue相关文档 vue-cli官方文档 vuex官方文档 vue-router 官方文档 vue2.6源码地址 如何调试源码 package.json 添加了--sourcemap "scripts": {"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap" }新增…

InstructDiffusion-多种视觉任务统一框架

论文:《InstructDiffusion: A Generalist Modeling Interface for Vision Tasks》 github:https://github.com/cientgu/InstructDiffusion InstructPix2Pix:参考 文章目录 摘要引言算法视觉任务统一引导训练集重构统一框架 实验训练集关键点检测分割图像…

0x00000709一键修复的解决办法,0x00000709错误的原因

在使用打印机时,你可能会遇到一些错误代码,其中之一是0x00000709。这个错误代码表示无法设置默认打印机。如果你遇到这样的问题不用担心!这篇文章将为你提供0x00000709一键修复的解决办法,帮助你解决0x00000709错误,并…

对于 ` HttpServletResponse ` , ` HttpServletRequest `我们真的学透彻了吗

对于 **HttpServletResponse , HttpServletRequest**我们真的学透彻了吗 问题引入 PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) {ExcelUtil<SysUser> util new ExcelUtil<SysUser>(SysUser.class);uti…

J-Flash工具的使用---擦除、烧录及校验

文章目录 前言一、打开J-Flash工具二、使用步骤1.创建工程&#xff0c;选择MCU&#xff0c;配置端口2.打开要烧录的文件3.连接J-Link4.擦除Flash5. 烧录固件 总结 前言 不使用IDE&#xff08;如keil、Iar&#xff09;如何来烧录固件。当我们的程序需要保密&#xff0c;不需要被…

YOLOv8改进 | 2023 | DWRSeg扩张式残差助力小目标检测 (附修改后的C2f+Bottleneck)

论文地址&#xff1a;官方论文地址 代码地址&#xff1a;该代码目前还未开源&#xff0c;我根据论文内容进行了复现内容在文章末尾。 一、本文介绍 本文内容给大家带来的DWRSeg中的DWR模块来改进YOLOv8中的C2f和Bottleneck模块&#xff0c;主要针对的是小目标检测&#xff0c…

Vite 了解

1、vite 与 create-vite 的区别 2、vite 解决的部分问题 3、vite配置文件的细节 3.1、vite语法提示配置 3.2、环境的处理 3.3、环境变量 上图补充 使用 3.4、vite 识别&#xff0c;vue文件的原理 简单概括就是&#xff0c;我们在运行 npm润dev 的时候&#xff0c;vite 会搭起…

fastReID论文总结

fastReID论文总结 fastReIDReID所面临的挑战提出的背景概念&#xff1a;所谓ReID就是从视频中找出感兴趣的物体&#xff08;人脸、人体、车辆等&#xff09;应用场景&#xff1a;存在的问题&#xff1a;当前的很多ReID任务可复用性差&#xff0c;无法快速落地使用解决方式&…

EMA训练微调

就是取前几个epoch的weight的平均值&#xff0c;可以缓解微调时的灾难性遗忘&#xff08;因为新数据引导&#xff0c;模型权重逐渐&#xff0c;偏离训练时学到的数据分布&#xff0c;忘记之前学好的先验知识&#xff09; class EMA():def __init__(self, model, decay):self.…

RabbitMQ消息模型之Sample

Hello World Hello World是官网给出的第一个模型&#xff0c;使用的交换机类型是直连direct&#xff0c;也是默认的交换机类型。 在上图的模型中&#xff0c;有以下概念&#xff1a; P&#xff1a;生产者&#xff0c;也就是要发送消息的程序C&#xff1a;消费者&#xff1a;消…

机器学习:领域自适应学习

训练一个分类器是小问题 上难度 训练数据和测试数据不一致&#xff0c;比如训练数据是黑白的&#xff0c;测试时彩色的&#xff0c;结果准确率非常低。 训练数据和测试数据有点差距的时候&#xff0c;能不能效果也能好呢&#xff1f;这就用到了领域自使用domain adptation 用一…

Windows 11的新功能不适用于所有人,但对将要使用的人来说非常酷

正如一个新的预览版本所示&#xff0c;Windows 11即将为那些使用手写笔的人添加一些智能功能&#xff0c;以及其他改进。 这是预览版22635.2776&#xff08;也称为KB5032292&#xff09;&#xff0c;已推出Beta频道&#xff0c;这是发布预览版之前的最后一个测试方法&#xff…

速速报名!请查收 2023 龙蜥操作系统大会超全指南

亲爱的小伙伴们&#xff0c;大家好&#xff01;我是大家的老朋友小龙&#xff01;自 2023 龙蜥操作系统大会宣布启动以来&#xff0c;小龙收到了来自四面八方的诸多期待和小心心。首届龙蜥大会正如火如荼地进行中&#xff0c;为表示对关注社区的每一位小伙伴由衷的感谢&#xf…

Ubuntu安装ssh

Ubuntu安装ssh服务器 一、ssh ssh&#xff1a;安全外壳协议(secure shell)的缩写&#xff0c;安全外壳协议&#xff08;安全的shell&#xff09;&#xff0c;是一个计算机网络协议&#xff08;默认端口号为22&#xff09;。通过ssh协议可以在客户端安全&#xff08;提供身份认…

k8s中Pod控制器简介,ReplicaSet、Deployment、HPA三种处理无状态pod应用的控制器介绍

目录 一.Pod控制器简介 二.ReplicaSet&#xff08;简写rs&#xff09; 1.简介 &#xff08;1&#xff09;主要功能 &#xff08;2&#xff09;rs较完整参数解释 2.创建和删除 &#xff08;1&#xff09;创建 &#xff08;2&#xff09;删除 3.扩容和缩容 &#xff08…

【Java SE】带你在String类世界中遨游!!!

&#x1f339;&#x1f339;&#x1f339;我的主页&#x1f339;&#x1f339;&#x1f339; &#x1f339;&#x1f339;&#x1f339;【Java SE 专栏】&#x1f339;&#x1f339;&#x1f339; &#x1f339;&#x1f339;&#x1f339;上一篇文章&#xff1a;带你走近Java的…

【C 语言经典100例】C 练习实例9

题目&#xff1a;要求输出国际象棋棋盘。 程序分析&#xff1a;国际象棋棋盘由64个黑白相间的格子组成&#xff0c;分为8行*8列。用i控制行&#xff0c;j来控制列&#xff0c;根据ij的和的变化来控制输出黑方格&#xff0c;还是白方格。 #include<stdio.h>int main() {…