POM是Page Object Model的简称,它是一种设计思想,意思是,把每一个页面,当做一个对象,页面的元素和元素之间操作方法就是页面对象的属性和行为。

POM一般使用三层架构,分别为:基础封装层、页面对象层、测试用例层。

目录结构大致如下

下面简单介绍下我的POM架构实现方式。

基础封装层

基础封装层主要是封装一些常用的方法,提高代码的复用。

基础封装层当前只包含了3个文件:

  • base_page.py:将所有界面共用的方法进行封装
  • browser.py:继承了selenium常用的webdriver操作,并对部分操作进行了封装
  • log,py:封装日志功能

base_page.py文件代码如下:

class BasePage(object):

    def __init__(self, driver):
self.__driver = driver def find_element(self, by, value, times=10, wait_time=1) -> object:
return self.__driver.until_find_element(by, value, times=10, wait_time=1)

因为在页面对象层,我们会将每个界面定义成一个类对象,而每个类对象都需要传入一个webdriver的实例对象,为了减少这样的重复操作,我们在base_page.py定义一个页面基类BasePage,在页面对象层定义的类继承该基类就可以完成webdriver实例对象的传入。

browser.py文件代码如下:

import time
import logging from selenium.webdriver import Chrome, Firefox
from selenium.common.exceptions import NoSuchElementException class Browser(Chrome, Firefox): def __init__(self, browser_type="chrome", driver_path=None, *args, **kwargs):
"""
根据浏览器类型初始化浏览器
:param browser_type: 浏览器类型,只可传入chrome或firefox
:param driver_path:指定驱动存放的路径
"""
# 检查browser_type值是否合法
if browser_type not in ["chrome", "firefox"]:
# 不合法报错
logging.error("browser_type 输入值不为chrome,firefox")
raise ValueError("browser_type 输入值不为chrome,firefox") self.__browser_type = browser_type # 根据browser_type值选择对应的驱动
if self.__browser_type == "chrome":
if driver_path:
Chrome.__init__(self, executable_path=f"{driver_path}/chromedriver.exe", *args, **kwargs)
else:
Chrome.__init__(self, *args, **kwargs)
elif self.__browser_type == "firefox":
if driver_path:
Firefox.__init__(self, executable_path=f"{driver_path}/geckodriver.exe", *args, **kwargs)
else:
Firefox.__init__(self, *args, **kwargs) def open_browser(self, url):
self.get(url)
self.maximize_window() @property
def browser_name(self):
return self.capabilities["browserName"] @property
def browser_version(self):
return self.capabilities["browserVersion"] def until_find_element(self, by, value, times=10, wait_time=1):
"""
用于定位元素
:param by: 定位元素的方式
:param value: 定位元素的值
:param times: 定位元素的重试次数
:param wait_time: 定位元素失败的等待时间
:return: 返回定位的元素
"""
# 检查by的合法性
if by not in ["id", "xpath", "name", "class", "tag", "text", "partial_text", "css"]:
# 不合法报错
logging.error(f"无效定位方式:{by},请输入:id,xpath, name, class, tag, text, partial_text, css")
raise ValueError(f"无效定位方式:{by},请输入:id,xpath, name, class, tag, text, partial_text, css") # 定位元素,如果定位失败,增加重试机制
for i in range(times):
# 定位元素
el = None
try:
if by == "id":
el = super().find_element_by_id(value)
elif by == "xpath":
el = super().find_element_by_xpath(value)
elif by == "name":
el = super().find_element_by_name(value)
elif by == "class":
el = super().find_element_by_class_name(value)
elif by == "tag":
el = super().find_elements_by_tag_name(value)
elif by == "text":
el = super().find_element_by_link_text(value)
elif by == "partial_text":
el = super().find_element_by_partial_link_text(value)
elif by == "css":
el = super().find_element_by_css_selector(value)
except NoSuchElementException:
# 如果报错为未找到元素,则重试
logging.error(f"通过{by}未定位到元素【{value}】,正在进行第{i+1}次重试...")
time.sleep(wait_time)
else:
# 如果成功定位元素则返回元素
logging.info(""f"通过{by}成功定位元素【{value}】!")
return el # 如果循环完仍为定位到元素,则抛错
logging.error(f"通过{by}无法定位元素【{value}】,请检查...")
raise NoSuchElementException(f"通过{by}无法定位元素【{value}】,请检查...") def switch_to_new_page(self):
# 获取老窗口的handle
old_handle = self.current_window_handle handles = self.window_handles
for handle in handles:
if handle != old_handle:
self.switch_to.window(handle)
break

在browser.py文件中,我们主要定义一个 Browser类,该类继承了selenium的Chrome 和 Firefox,在实例化Browser类后,我们能使用selenium所有的方法,同时,我们在Browser类中还封装一些其它操作,比如将查找元素的8种方法进行封装并增加元素定位失败后重试次数,比如切换新界面的handle等

log.py文件代码如下:

import os
import logging
import time from logging.handlers import RotatingFileHandler def log(log_level="DEBUG"):
# 创建logger,如果参数为空则返回root logger
logger = logging.getLogger() # 设置logger日志等级
# logger.setLevel(logging.DEBUG)
logger.setLevel(log_level) # 创建handler
log_size = 1024 * 1024 * 20
# 将日志写入到文件中
dir_name = "./logs/"
if not os.path.exists(dir_name):
os.mkdir(dir_name)
time_str = time.strftime("%Y%m%d", time.localtime())
fh = RotatingFileHandler(dir_name + f"{time_str}.log", encoding="utf-8", maxBytes=log_size, backupCount=100)
# 将日志输出到控制台
ch = logging.StreamHandler() # 设置输出日志格式
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)s] %(filename)s line:%(lineno)s %(message)s",
# datefmt="%Y/%m/%d %X"
)
# 注意 logging.Formatter的大小写 # 为handler指定输出格式,注意大小写
fh.setFormatter(formatter)
ch.setFormatter(formatter) # 为logger添加的日志处理器
logger.addHandler(fh)
logger.addHandler(ch)

log.py文件主要定义了一个log函数,函数中定义了日志相关的操作,注意,定义的log函数需要在任意被执行文件中被调用,比如,在用例层我们调用了browser.py文件中的方法,那么在架构中必定执行utils文件中__init.py文件,所以我们在__init__.py文件中调用log函数。

__init__.py文件代码如下:

from .log import log

log("INFO")

到此,我们完成了日志的环境配置,当需要记录日志时,只需要在文件中导入logging包,使用logging.info()这种方式记录日志即可。点我查看更多日志操作

页面对象层

什么是页面对象?页面对象就是将每个界面当成一个对象,界面中的元素当成对象的属性。下面以百度首页和新闻页为例,介绍页面对象层。

在页面对象层,新增文件baidu.py,文件代码如下:

from utils.base_page import BasePage

class HomePage(BasePage):

    @property
def input_box(self):
return self.find_element("id", "kw") @property
def search_button(self):
return self.find_element("id", "su") @property
def news_link(self):
return self.find_element("xpath", '//*[@id="s-top-left"]/a[1]') class NewsPage(BasePage):
@property
def game_link(self):
return self.find_element("xpath", '//*[@id="channel-all"]/div/ul/li[10]/a')

类对象HomePage和NewsPage分别代表百度首页和百度新闻页,在类对象中定义了一些方法,每个方法表示页面中的一个元素,再使用装饰器@property将这些方法属性化。比如,input_box表示输入框,search_button表示搜索框。

测试用例层

在测试用例层,我们使用了uniittest框架来管理和执行用例,下面以两个简单的用例,来演示脚本的编写。

test_baidu.py文件代码如下:

import unittest
import time
import logging from utils.browser import Browser
from page_object.baidu import HomePage, NewsPage class Baidu(unittest.TestCase): def setUp(self) -> None:
self.driver = Browser("firefox")
self.driver.open_browser("http://www.baidu.com")
logging.info("打开浏览器")
logging.info(f"浏览器名称:{self.driver.browser_name},浏览器版本:{self.driver.browser_version}") self.homepage = HomePage(self.driver)
self.newspage = NewsPage(self.driver) def tearDown(self) -> None:
self.driver.quit()
logging.info("关闭浏览器") def test_search(self):
""" 用例1:测试百度搜索框输入selenium能搜索出包含selenium相关的信息 """
logging.info("用例1:测试百度搜索框输入selenium能搜索出包含selenium相关的信息") # 输入搜索信息
self.homepage.input_box.send_keys("selenium")
logging.info("输入搜索信息") # 点击按钮
self.homepage.search_button.click()
logging.info("点击搜索按钮")
time.sleep(2) # 校验搜索结果
els = self.driver.find_element_by_partial_link_text("selenium")
self.assertIsNotNone(els) def test_access_game_news(self):
""" 用例2:测试通过百度首页能进入新闻界面的游戏专题 """
logging.info("用例2:测试通过百度首页能进入新闻界面的游戏专题") # 点击新闻链接
self.homepage.news_link.click()
logging.info("点击新闻链接") # 切换窗口
self.driver.switch_to_new_page()
logging.info("切换窗口") # 点击游戏链接
self.newspage.game_link.click()
logging.info("点击游戏链接") # 校验url
current_url = self.driver.current_url
self.assertEqual(current_url, "http://news.baidu.com/game") if __name__ == '__main__':
unittest.main()

执行用例

到此,POM架构基本实现。

Selenium_POM架构(17)的更多相关文章

  1. 这是一套Java菜鸟到大牛的学习路线之高级教程,由工作了10年的资深Java架构师整理。

    这是一套Java菜鸟到大牛的学习路线之高级教程,由工作了10年的资深Java架构师整理.        01-java高级架构师设计-基础深入        J2SE深入讲解        Java多 ...

  2. 《iOS开发指南》正式出版-源码-样章-目录,欢迎大家提出宝贵意见

    智捷iOS课堂-关东升老师最新作品:<iOS开发指南-从0基础到AppStore上线>正式出版了 iOS架构设计.iOS性能优化.iOS测试驱动.iOS调试.iOS团队协作版本控制.... ...

  3. Web后台快速开发框架

    Web后台快速开发框架 Coldairarrow 目录 目录 第1章    目录    1 第2章    简介    3 第3章    基础准备    4 3.1    开发环境要求    4 3.2 ...

  4. docker“少折腾”

    1.docker镜像加速 新版的 Docker 使用 /etc/docker/daemon.json(Linux) 或者 %programdata%\docker\config\daemon.json ...

  5. 《关于长沙.NET技术社区未来发展规划》问卷调查结果公布

    那些开发者们对于社区的美好期待 2月,长沙.net 技术社区自从把群拉起来开始,做了一次比较正式.题目为<关于长沙.NET技术社区未来发展规划>的问卷调查,在问卷调查中,溪源写道: 随着互 ...

  6. 2017年--10年java大神告诉你开发最常用的百分之二十的技术有哪些?

    首先题主说的20%我不知道从哪方面去理解.接下来我会将自己多年来工作中会经常使用到的技术列出来. 1.html.css 2.java工作原理(jvm) 3.java语法.数据结构和算法 4.java语 ...

  7. 《JSP+Servlet+Tomcat应用开发从零開始学》

    当当网页面:  http://product.dangdang.com/23619990.html 内容简单介绍      本书全面介绍了 JSP开发中涉及的相关技术要点和实战技巧. 全书结构清晰,难 ...

  8. 新书上线:《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》,欢迎大家买回去垫椅子垫桌脚

    新书上线 大家好,笔者的新书<Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统>已上线,此书内容充实.材质优良,乃家中必备垫桌脚 ...

  9. Docker技术入门与实战第2版-高清文字版

      Docker技术入门与实战第2版-高清文字版 下载地址https://pan.baidu.com/s/1bAoRQQlvBa-PXy5lgIlxUg 扫码下面二维码关注公众号回复100011 获取 ...

随机推荐

  1. Redis,Memcache,MongoDb的特点与区别

    Redis Redis 简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下三个特点: Redis支 ...

  2. 【.NET6】gRPC服务端和客户端开发案例,以及minimal API服务、gRPC服务和传统webapi服务的访问效率大对决

    前言:随着.Net6的发布,Minimal API成了当下受人追捧的角儿.而这之前,程序之间通信效率的王者也许可以算得上是gRPC了.那么以下咱们先通过开发一个gRPC服务的教程,然后顺势而为,再接着 ...

  3. Spring框架源码干货分享之三级缓存和父子工厂

    记录并分享一下本人学习spring源码的过程,有什么问题或者补充会持续更新.欢迎大家指正! 环境: spring5.X + idea 建议:学习过程中要开着源码一步一步过 Spring中对象的创建宏观 ...

  4. [BUUCTF]REVERSE——helloword

    helloword 题目是安卓逆向.安卓逆向工具下载地址 用APKIDE打开附件,ctf+f调出检索版,检索main函数,能看到flag字符串 flag{7631a988259a00816deda84 ...

  5. java 多线程 发布订阅模式:发布者java.util.concurrent.SubmissionPublisher;订阅者java.util.concurrent.Flow.Subscriber

    1,什么是发布订阅模式? 在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者).而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话 ...

  6. winpcap 静默安装

    前几天做一个小工具用到winpcap,由于有些用户系统未必安装过这个而领导要求尽量减少用户点击,于是只好想办法静默安装了,csdn搜了,貌似没有好用的,求助stackoverflow,还好,在某篇解答 ...

  7. ACwing1211. 蚂蚁感冒

    题目: 长 100 厘米的细长直杆子上有 n 只蚂蚁. 它们的头有的朝左,有的朝右. 每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒. 当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行. 这些蚂蚁 ...

  8. JAVA获取昨天、今天、明天等日期

    /** * 获取明天的日期字符串 * @return */ public static String tomorrowDateStr(){ Date date=new Date();//取时间 Cal ...

  9. 【LeetCode】1114. Print in Order 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 mutex锁 promise/future 日期 题 ...

  10. 【LeetCode】Longest Word in Dictionary through Deleting 解题报告

    [LeetCode]Longest Word in Dictionary through Deleting 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode. ...