Selenium教程__POM架构(17)

news2024/11/25 14:51:59

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架构基本实现。


-事必有法,然后有成- 最后祝大家早日达到测试的天花板!



以下是我收集到的比较好的学习教程资源,虽然不是什么很值钱的东西,如果你刚好需要,可以留言【777】直接拿走就好了

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SkyWalking--traceId的作用

原文网址:SkyWalking--traceId的作用_IT利刃出鞘的博客-CSDN博客 简介 本文介绍SkyWalking中traceId的作用。 traceId是什么 SkyWalking的一个核心功能就是:链路追踪。链路追踪就是跟踪一个请求的所有链路,而这个链路都是通过一个id来串起…

数据结构--算法空间复杂度

数据结构–算法空间复杂度 只需关注存储空间大小与问题规模相关的变量 计算规则与算法的时间复杂度类似 eg: S ( n ) O ( n 2 ) O ( n ) O ( 1 ) O ( n 2 ) S(n) O(n^2)O(n)O(1) O(n^2) S(n)O(n2)O(n)O(1)O(n2) 知识点回顾 & 重要考点

java list集合数据去重方式

1.概述 最近又是一轮代码review , 发现了一些实现去重的代码,在使用 list.contain … 我沉思,是不是其实很多初学者也存在这种去重使用问题? 所以我选择把这个事情整出来,分享一下。 2.contain 去重 首先是造出一个 List 模拟…

[CKA]考试之七层负载均衡Ingress

由于最新的CKA考试改版,不允许存储书签,本博客致力怎么一步步从官网把答案找到,如何修改把题做对,下面开始我们的 CKA之旅 题目为: Task 如下创建一个新的nginx Ingress资源: 名称: pong Namespace: i…

掌握这些容易被忽略的Vue细节,轻松排查问题,省时省力!

v-bind 绑定的值是 null 或者 undefined v-bind 如果绑定的值是 null 或者 undefined&#xff0c;那么该 attribute 将会从渲染的元素上移除。 当attribute 为布尔型时&#xff0c; 行为略有不同。 <button :disabled"isButtonDisabled">Button</button&…

CSS之平面转换

简介 作用&#xff1a;为元素添加动态效果&#xff0c;一般与过渡配合使用 概念&#xff1a;改变盒子在平面内的形态&#xff08;位移、旋转、缩放、倾斜&#xff09; 平面转换也叫 2D 转换&#xff0c;属性是 transform 平移 transform: translate(X轴移动距离, Y轴移动距…

@Valid接口参数校验怎么做,详细教程

接口参数校验教程 一、在字段上可以使用这个注解来设置校验 Null&#xff1a;被注释的元素必须为null NotNull&#xff1a;被注释的元素不能为null AssertTrue&#xff1a;该字段只能为true AssertFalse&#xff1a;该字段的值只能为false Min("value","messa…

机器学习:监督学习

目前&#xff0c;在机器学习系统中&#xff0c;监督学习&#xff08;Supervised Learning&#xff09;占主导地位。由于监督学习的任务定义明确&#xff0c;例如识别垃圾邮件或预测降水&#xff0c;因此它比无监督学习具有更多潜在用例&#xff1b;而与强化学习相比&#xff0c…

剑指offer(C++)-JZ51:数组中的逆序对(算法-排序)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对…

Java IO 学习总结(四)BufferedReader 缓冲字符流

Java IO 学习总结&#xff08;一&#xff09;输入流/输出流 Java IO 学习总结&#xff08;二&#xff09;File 类 Java IO 学习总结&#xff08;三&#xff09;BufferedInputStream Java IO 学习总结&#xff08;四&#xff09;BufferedReader 缓冲字符流 前言&#xff1a; 学…

Kibana介绍安装

目录 Kibana入门配置和安装启动数据探索Metricbeat仪表盘Nginx指标仪表盘【Metricbeat】Nginx日志仪表盘查看集群数据 Kibana入门 Kibana 是一款开源的数据分析和可视化平台&#xff0c;它是 Elastic Stack 成员之一&#xff0c;设计用于和 Elasticsearch 协作。可以使用 Kiban…

2023上半年软考系统分析师科目一整理-03

2023上半年软考系统分析师科目一整理-03 1. 嵌入式 1. 嵌入式 嵌入式系统已被广泛应用到各行各业。嵌入式系统是一个内置于设备中&#xff0c;对设备的各种传感器进行管理与控制的系统。通常&#xff0c;根据系统对时间的敏感程度可将嵌入式系统划分为( A )两种&#xff0c;而…

netwox构建IP协议数据包【网络工程】(保姆级图文)

目录 netwox构建IP协议数据包1) 不指定选项&#xff0c;直接运行该模块。执行命令如下&#xff1a;2) 指定源 IP 地址为 192.168.43.95&#xff0c;目标 IP 地址为 192.168.43.97。执行命令如下&#xff1a;3) 通过抓包&#xff0c;验证构造的 IP 数据包。捕获到的数据包如图所…

管理类联考——英语——趣味篇——不择手段——b开头单词

第一部分 核心词汇趣讲 Unit 2 boom n./v.&#xff08;发出&#xff09;隆隆声&#xff1b;激增&#xff0c;繁荣 loom想象成&#xff1a;一百(100)米(m)外有个妹妹(m也可以想象成妹妹)&#xff0c;你能看得清她吗?→&#xff08;模糊之物)耸现。 boom&#xff1a;六百个妹…

低代码可视化拖拽编辑器实现方案

一、前言 随着业务不断发展&#xff0c;低代码、无代码平台越来越常见&#xff0c;它降低开发门槛、快速响应业务需求、提升开发效率。零开发经验的业务人员通过可视化拖拽等方式&#xff0c;即可快速搭建各种应用。本文主要是讲解低代码可视化拖拽平台前端展示层面的实现逻辑…

@Async使用什么线程池?

文章目录 前言一、前言1、ThreadPoolTaskExecutor2、SimpleAsyncTaskExecutor3、测试代码 二、各种情况模拟1、未配置线程池2、配置异步线程池3、配置1个或多个非异步线程池4、同时配置异步和非异步线程池 三、总结 前言 本文的目的&#xff0c;主要是看到网上各种说辞&#x…

JVM-类加载与运行区详细分析(一)

目录 一、为什么会有类加载机制 二、类加载机制原理是什么 1、什么是类加载器&#xff1a;宏观 2、类加载器工作原理 1、装载 2、链接 3、初始化 3、何为装载的机制&#xff1a;微观 4、上面既然我们已经知道了啥是双亲委派了&#xff0c;那么怎么去破坏呢&#xff1f;…

【设计模式】工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)详记

注&#xff1a;本文仅供学习参考&#xff0c;如有错漏还请指正&#xff01; 参考文献/文章地址&#xff1a; https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8F%AF%E5%A4%8D%E7%94%A8%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%BD%AF%E4%BB%B…

第20章:MySQL索引失效案例

1.全值匹配我最爱 当SQL查询 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age30 AND classId4 AND NAMEabcd; 创建3个索引 idx_age,idx_age_classid,idx_age_classid_name 当前优化器会选择跟where条件匹配最高的idx_age_classid_name索引&#xff0c;直接查询出对…

[CVPR 2023] Imagic:使用扩散模型进行基于文本的真实图像编辑

[CVPR 2023] Imagic:使用扩散模型进行基于文本的真实图像编辑 Paper Title: Imagic: Text-Based Real Image Editing with Diffusion Models The first author performed this work as an intern at Google Research. Project page: https://imagic-editing.github.io/. 原文…