【日常学习】2025-8-27 测开框架设计模式探索04

一、ext

1. 类定义:继承 Selenium 原生的 webdriver.Remote

chromedriver是浏览器驱动程序,selenium代码发送的自动化指令通过它翻译成浏览器能识别的底层命令,浏览器执行。

反之,浏览器的执行结果也需要通过浏览器内核反馈给代码

这个原生的类的实例化就是driver驱动实例化:

1)发送请求:把调用的方法封装成符合Webdriver协议的请求发给浏览器内核

2)接受结果:反之也能反馈浏览器操作结果给代码,是浏览器内核和代码之间的桥梁

3)管理会话:从driver实例化(创建浏览器会话)到driver.quit()关闭浏览器结束会话,整个浏览器的生命周期由driver统一管理。

python 复制代码
class WebDriverExt(webdriver.Remote):

WebDriverExt 继承了 webdriver.Remote,说明框架:

让代码可以跨机器、跨浏览器地控制网页,能大规模自动化测试

2. __init__ 方法:初始化扩展类
python 复制代码
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
             desired_capabilities=None, browser_profile=None, proxy=None,
             keep_alive=True, file_detector=None):
    # 调用父类(webdriver.Remote)的初始化方法,保留原生功能
    super(WebDriverExt, self).__init__(command_executor, desired_capabilities, browser_profile, proxy, keep_alive,
                                       file_detector)
    self._driver = self  # 给自己起个别名,方便后续调用
    self.initConfig()  # 调用自定义的初始化配置方法
  • 核心作用:创建 WebDriverExt 实例时,先按照 Selenium 原生的方式初始化(保证基础功能可用),然后执行自定义的配置。
  • command_executor:默认指向本地的 Selenium Grid 服务地址(127.0.0.1:4444),框架用了 Grid 来管理浏览器。
python 复制代码
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',  # 远程服务器地址,默认本地Grid服务
             desired_capabilities=None,  # 浏览器能力配置(如指定Chrome/Firefox)
             browser_profile=None,  # 浏览器配置文件(如保存的书签、设置)
             proxy=None,  # 代理配置
             keep_alive=True,  # 是否保持长连接(优化性能)
             file_detector=None):  # 文件检测器(处理文件上传)
  • 均为父类 webdriver.Remote 要求的参数,这里保留默认值或设为 None,确保兼容性。
python 复制代码
    super(WebDriverExt, self).__init__(command_executor, desired_capabilities, browser_profile, proxy, keep_alive,
                                       file_detector)
  • super(子类名, self).init(参数):调用父类的构造方法,确保父类的初始化逻辑被执行(如连接远程服务器、初始化浏览器)。
Matlab 复制代码
    self._driver = self  # 给当前实例(self)起一个别名_driver
  • self._driver:定义实例属性 _driver,值为实例本身(self)。

  • 用途:后续在类内部可以通过 self._driver 调用实例的方法(和 self 效果一样,可能是为了代码可读性或兼容旧逻辑)。

    复制代码
      self.initConfig()  # 调用当前类的initConfig方法,执行自定义配置
  • self.initConfig():调用本类中定义的 initConfig 方法,触发额外的初始化逻辑(如设置等待时间)。

3. initConfig 方法:设置默认配置
python 复制代码
def initConfig(self):
    """初始化Driver的配置"""
    # 从框架配置中读取"隐式等待时间",设置给driver
    self.implicitly_wait(settings.DRIVER['implicitlyWait'])
  • implicitly_wait() 是 Selenium 的隐式等待方法(当查找元素时,如果没找到,会等待指定时间再报错)。
  • settings.DRIVER['implicitlyWait']:从框架的配置文件(settings)中读取预设的等待时间(比如 10 秒),避免每次使用都手动设置,实现 "全局统一配置"。
4. windowScrollTo 方法:自定义页面滚动功能
python 复制代码
def windowScrollTo(self, x, y):
    # 确保滚动坐标不小于0(避免无效值)
    x = 0 if x < 0 else x
    y = 0 if y < 0 else y
    # 构造JavaScript代码:滚动页面到指定坐标(x,y)
    js = 'window.scrollTo(%s,%s)' % (x, y)
    self._driver.execute_script(js)  # 执行JS代码
    sleep(2)  # 等待2秒,让滚动完成
  • 这是在原生 webdriver 基础上新增的功能:封装了 "页面滚动" 操作。
  • 为什么要封装?因为 Selenium 原生没有专门的滚动方法,需要通过执行 JavaScript 实现(window.scrollTo(x,y) 是 JS 滚动页面的语法)。框架把这个操作封装成方法后,用例中可以直接调用 driver.windowScrollTo(0, 500),不用每次写 JS 代码。
  • sleep(2):滚动后等待 2 秒,确保页面元素加载完成(避免后续操作太快导致失败)。

⭐ 作用

简单说,WebDriverExt"增强版的浏览器驱动"

  1. 保留了 Selenium 原生的所有功能(继承自 webdriver.Remote);
  2. 自动应用框架的全局配置(如隐式等待时间,通过 initConfig 实现);
  3. 新增了常用的自定义操作(如 windowScrollTo 封装页面滚动)。

二、类

1. 类定义:继承 WebDriverExt
python 复制代码
class WebDriver(WebDriverExt):  # 定义WebDriver类,继承自WebDriverExt
2. __init__ 方法:初始化并新增基础属性
python 复制代码
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
             desired_capabilities=None, browser_profile=None, proxy=None,
             keep_alive=True, file_detector=None):
    # 调用父类(WebDriverExt)的构造方法,保留所有基础功能
    super(WebDriver, self).__init__(command_executor, desired_capabilities, browser_profile, proxy, keep_alive,
                                    file_detector)

    # 1. 给实例自身起别名(和WebDriverExt一致,可能是为了兼容旧代码)
    self._driver = self

    # 2. 新增:初始化窗口/iframe切换工具(SwitchTo)
    self._switch_to = SwitchTo(self)
  • self._switch_to = SwitchTo(self) :这是 WebDriver 相比 WebDriverExt 的第一个新增核心功能!
    • SwitchTo 是 Selenium 提供的 窗口 /iframe/ 弹窗切换工具类 (比如切换到新窗口 driver._switch_to.window()、切换到 iframe driver._switch_to.frame())。
    • 这里在 WebDriver 中初始化 SwitchTo 并绑定当前实例,意味着用例中创建 WebDriver 对象后,能直接用 driver._switch_to 做切换操作,无需手动初始化。

⭐ 分开的作用:职责分离

  • WebDriverExt:负责 通用基础增强(如全局隐式等待配置、通用页面滚动),是所有项目都能复用的 "基础层"。
  • WebDriver:负责 专项功能扩展 (如窗口切换、CDP 命令、自定义 Cookie 操作),是当前项目特有的 "业务层"。

三、SwitchTo 类

对 Selenium 原生 SwitchTo 类的 定制化扩展 ,主要用于更便捷、更健壮地处理 窗口切换、表单切换、弹窗(Alert)操作。它在保留原生功能的基础上,增加了对 "窗口索引切换" 的支持和自定义异常处理,让测试用例中的切换操作更符合实际使用习惯。

python 复制代码
class SwitchTo(SeleniumSwitchTo):  # 自定义SwitchTo类,继承自Selenium原生SwitchTo
python 复制代码
def __init__(self, driver):
    super(SwitchTo, self).__init__(driver)  # 调用父类初始化方法,保留原生初始化逻辑
    self._driver = driver  # 保存驱动实例到当前类,后续切换操作可能需要用到driver

window 方法:窗口切换(核心扩展功能)

这是整个类最关键的增强点 ------ 原生 window() 方法只能通过 "窗口句柄(字符串)" 切换,而框架扩展后支持 "整数索引" 切换,更符合测试人员的使用习惯。

python 复制代码
#@driverAction_dec("切换到窗口", False)  # 同样是预留的增强装饰器
def window(self, window_name):
    # 若传入的是整数(表示窗口索引,如0=第一个窗口,1=第二个窗口)
    if isinstance(window_name, int):
        try:
            # 1. 获取当前所有窗口的句柄(列表形式,按打开顺序排列)
            handles = self._driver.window_handles
            # 2. 获取窗口总数
            count = len(handles)
            # 3. 检查索引是否有效(0 <= 索引 < 窗口总数)
            if window_name < count and window_name >= 0:
                # 有效则切换到对应索引的窗口句柄(调用父类方法)
                super(SwitchTo, self).window(handles[window_name])
            else:
                # 无效则抛出自定义异常(明确提示索引越界)
                raise SwitchToWindowException(f'窗口索引{window_name}超出当前窗口数量最大值{count}')
        except Exception as e:
            # 其他错误(如获取句柄失败)也抛出自定义异常
            raise SwitchToWindowException(e)
    else:
        # 若传入的不是整数(如窗口句柄字符串),直接调用父类方法(保留原生功能)
        super(SwitchTo, self).window(window_name)
  • 原生 window() 缺陷:必须传入窗口句柄(如 CDwindow-XXX 这样的字符串),而测试人员通常更习惯用 "索引"(比如 "切换到第 2 个打开的窗口")。
  • 框架扩展后:
    • 支持 window(0) 切换到第一个窗口、window(1) 切换到第二个窗口(按打开顺序);
    • 自动校验索引有效性,超出范围时抛出 SwitchToWindowException(自定义异常,错误信息更明确,方便定位问题);
    • 兼容原生用法:如果传入窗口句柄字符串,仍能正常切换(不破坏原有功能)。

alert 属性:弹窗处理(关联自定义 Alert 类)

python 复制代码
@property  # 装饰为属性,可通过"实例.alert"访问,无需加括号调用
def alert(self):
    return self.__alert()  # 调用内部方法__alert()

def __alert(self):
    return Alert(self._driver)  # 返回框架自定义的Alert实例,而非原生Alert
  • 作用:将原生的弹窗(Alert)处理逻辑,替换为框架自定义的 Alert 类(导入自 from .alert import Alert)。
  • 为什么要自定义?原生 Alert 类功能简单(只有 accept()dismiss() 等基础方法),框架的 Alert 类可能增加了更多功能(如 "获取弹窗文本并截图""等待弹窗出现" 等),更适配项目需求。

四、alert

python 复制代码
# coding=utf-8  # 声明编码格式,支持中文注释(避免中文乱码)
from selenium.webdriver.common.alert import Alert as SeleniumAlert  # 导入Selenium原生Alert类,起别名"SeleniumAlert"
  • 关键:用 as SeleniumAlert 给原生类起别名,是为了 避免和当前自定义的 Alert 类重名 (如果直接 from ... import Alert,会覆盖自定义类,导致报错)。
python 复制代码
class Alert(SeleniumAlert):  # 自定义Alert类,继承自Selenium原生Alert
  • 语法:class 子类(父类),这是 Python 面向对象 "继承" 的基础写法,确保自定义类能复用原生类的所有方法(如 accept()dismiss() 等)。
python 复制代码
def __init__(self, driver):
    super(Alert, self).__init__(driver)  # 调用父类(原生Alert)的构造函数
    self._driver = driver  # 额外保存驱动实例到当前类
dismiss 方法:关闭弹窗(预留扩展位)
python 复制代码
#@driverAction_dec("取消警告框",False)  # 注释掉的装饰器(核心预留点)
def dismiss(self):
    super(Alert, self).dismiss()  # 直接调用原生Alert的dismiss()方法
  • 原生功能:dismiss() 用于 "关闭弹窗"(对应弹窗的 "取消" 按钮,比如浏览器提示 "是否离开此页面" 时,点击 "取消")。

  • 框架封装意图:目前完全复用原生逻辑,没有新增代码,但注释的 @driverAction_dec 装饰器是关键 ------ 这是 预留的扩展位
    比如未来想给 "关闭弹窗" 操作添加日志(记录 "何时关闭了弹窗")或截图(关闭前截图留存证据),只需启用装饰器。

    装饰器 driverAction_dec 是框架自定义的工具,能自动添加日志、截图等通用增强功能,无需修改 dismiss() 本身的代码。

accept 方法:确认弹窗(同 dismiss 逻辑)
python 复制代码
#@driverAction_dec("接受警告框",False)  # 同样是预留装饰器
def accept(self):
    super(Alert, self).accept()  # 调用原生Alert的accept()方法
send_keys 方法:向弹窗输入文本(同逻辑)
python 复制代码
#@driverAction_dec("发送文本到警告框",False)  # 预留装饰器
def send_keys(self, keysToSend):
    super(Alert, self).send_keys(keysToSend)  # 调用原生Alert的send_keys()方法

表面看,这个类只是 "重复调用原生方法",似乎没什么用,但实际是框架设计中 "预留扩展、统一管控" 思想的体现:

  1. 预留扩展空间:通过注释的装饰器,未来无需修改核心代码,就能快速给弹窗操作添加日志、截图、重试等增强功能(符合 "开闭原则":对扩展开放,对修改关闭);
  2. 统一 API 风格 :框架的 SwitchToWebDriverExt 等类都采用 "继承原生类 + 轻量封装" 的模式,Alert 类保持一致风格,方便测试人员记忆和使用(比如用 driver._switch_to.alert.accept() 确认弹窗,符合框架统一逻辑);
  3. 便于业务定制 :如果未来项目有特殊弹窗需求(比如 "弹窗文本必须包含 XX 关键词才确认"),可以直接在这些方法中添加业务逻辑。

五、By

这个 WebDriverBy 类是框架中的 "元素定位方式映射转换器" ,核心作用是:将框架 自定义的 By 类型 (来自 frameworkCore.driver.by),统一转换为 Selenium 原生的 By 类型(来自 selenium.webdriver.common.by),解决 "框架自定义定位标识" 与 "Selenium 原生定位标识" 的兼容问题。

python 复制代码
# coding:utf-8  # 支持中文注释
from frameworkCore.driver.by import By  # 导入框架自定义的By类(待转换的"源类型")
from selenium.webdriver.common.by import By as webDriverBy  # 导入Selenium原生By,起别名避免重名
  • 关键:用 as webDriverBy 给原生 By 起别名,是为了和框架自定义的 By 区分开(否则两个 By 重名,会导致代码混淆)。
python 复制代码
class WebDriverBy:  # 无父类,是一个"纯静态工具类"(只存映射表和转换方法)
  • 这个类不需要继承任何父类,因为它的核心功能是 "存储映射关系" 和 "提供转换方法",本质是一个 工具类(类似 "字典 + 静态方法" 的组合,但用类封装更规整)。
类属性 byMap:定位方式映射表
python 复制代码
byMap = {
    By.ID: webDriverBy.ID,                  # 框架By.ID → Selenium原生ID定位
    By.NAME: webDriverBy.NAME,              # 框架By.NAME → Selenium原生NAME定位
    By.CLASS_NAME: webDriverBy.CLASS_NAME,  # 框架By.CLASS_NAME → 原生CLASS_NAME定位
    By.LINK_TEXT: webDriverBy.LINK_TEXT,    # 框架By.LINK_TEXT → 原生LINK_TEXT定位
    By.PARTIAL_LINK_TEXT: webDriverBy.PARTIAL_LINK_TEXT,  # 部分链接文本定位
    By.TAG_NAME: webDriverBy.TAG_NAME,      # 标签名定位
    By.XPATH: webDriverBy.XPATH,            # XPATH定位
    By.CSS_SELECTOR: webDriverBy.CSS_SELECTOR  # CSS选择器定位
}
  • 本质:这是一个 字典(key-value 映射表) ,作用是建立 "框架自定义 By" 和 "Selenium 原生 By" 的一一对应关系。
类方法 convert_by:执行定位方式转换
python 复制代码
@classmethod  # 装饰为"类方法",无需创建实例,直接通过类调用
def convert_by(cls, by):
    return cls.byMap[by]  # 根据传入的框架By,返回对应的原生By

WebDriverBy 就是框架和 Selenium 之间的 "翻译官"------ 让框架的自定义定位标识,能被 Selenium 看懂并执行~

六、 exceptionUtil模块

单独创建 exceptionUtil 类(或工具模块),核心目的是 "统一异常判断逻辑,实现异常处理的复用与解耦"------ 把 "判断是否为超时异常" 这类通用逻辑抽成独立工具,避免在框架各个角落重复写相同代码,同时让异常判断更规范、更易维护。

模块直接定义了函数 isTimeOutException,本质是一个 "异常处理工具模块" (命名为 exceptionUtil.py),而非严格意义上的 "类"。这种设计更轻量,适合封装单一、通用的异常判断逻辑。

python 复制代码
from frameworkCore.driver.exception import TimeOutException, TimeOutException
python 复制代码
def isTimeOutException(err):
    return isinstance(err, TimeOutException)
  • 核心功能:判断传入的异常对象 err,是否属于 TimeOutException 类型(或其子类类型)。

  • 关键语法:isinstance(err, 异常类) 是 Python 中判断 "对象是否属于某个类(或其派生类)" 的标准方法,返回 True/False

    • 举例:如果 errTimeOutException("元素定位超时") 的实例,返回 True

⭐ 为什么要单独抽成工具?(核心价值)

如果不抽这个工具,每次判断 "是否为超时异常" 都要写 isinstance(err, TimeOutException)。

假设框架中有 10 个地方需要判断超时异常(比如 Page 类的元素定位、用例中的步骤重试、日志记录等):

  • 不抽工具 :每个地方都要写 if isinstance(err, TimeOutException): ...,如果未来框架要修改异常类名(比如把 TimeOutException 改成 ElementTimeOutException),需要手动修改 10 处代码,极易遗漏;
  • 抽成工具 :所有地方都调用 if exceptionUtil.isTimeOutException(err): ...,未来修改异常类时,只需改 exceptionUtil.py 中的 from ... import 新异常类 和函数内的判断,1 处修改即可覆盖所有调用场景。

对比两种写法:

  • 原始写法:if isinstance(err, TimeOutException): 处理超时逻辑
  • 工具写法:if exceptionUtil.isTimeOutException(err): 处理超时逻辑

工具写法的 可读性更高 ------ 即使是不熟悉框架的人,看到 isTimeOutException 这个函数名,也能立刻明白 "这是在判断是否为超时异常",无需理解 isinstance 的细节。

⭐ 实际使用场景示例

场景 1:元素定位超时重试

在框架的元素定位工具中,如果遇到超时异常,自动重试 2 次:

python 复制代码
import exceptionUtil  # 导入异常工具
from frameworkCore.driver.exception import TimeOutException

def find_element_with_retry(driver, by, value, retry=2):
    for _ in range(retry + 1):
        try:
            return driver.find_element(by, value)
        except Exception as err:
            # 用工具判断是否为超时异常,只有超时才重试
            if exceptionUtil.isTimeOutException(err) and _ < retry:
                print(f"定位超时,第{_+1}次重试...")
                continue
            # 非超时异常,直接抛出
            raise err
场景 2:日志记录异常类型

在框架的日志模块中,根据异常类型输出不同级别日志:

python 复制代码
import logging
import exceptionUtil

def log_exception(err):
    # 判断是否为超时异常,输出WARNING级别日志
    if exceptionUtil.isTimeOutException(err):
        logging.warning(f"超时异常:{err}")
    # 其他异常输出ERROR级别日志
    else:
        logging.error(f"错误异常:{err}", exc_info=True)

⭐ 总结

exceptionUtil 模块(或类)的核心是 "把通用的异常判断逻辑抽成工具",看似只封装了一行代码,却能显著提升框架的:

  • 可维护性:一处修改覆盖所有调用;
  • 一致性:统一异常判断标准;
  • 可读性:用函数名直观表达逻辑。

这是 Python 工程化开发中 "DRY 原则(Don't Repeat Yourself,不要重复造轮子)" 的典型体现~

相关推荐
wanzhong233323 分钟前
ArcGIS学习-12 实战-综合案例
学习·arcgis
diablobaal2 小时前
云计算学习100天-第32天
学习·云计算
练习时长两年半的Java练习生(升级中)3 小时前
从0开始学习Java+AI知识点总结-27.web实战(Maven高级)
java·学习·maven
艾莉丝努力练剑4 小时前
【C语言16天强化训练】从基础入门到进阶:Day 14
java·c语言·学习·算法
牛奶咖啡135 小时前
学习设计模式《二十四》——访问者模式
学习·设计模式·访问者模式·认识访问者模式·访问者模式的优缺点·何时选用访问者模式·访问者模式的使用示例
Brookty6 小时前
深入解析Java并发编程与单例模式
java·开发语言·学习·单例模式·java-ee
编码浪子7 小时前
趣味学习Rust基础篇(用Rust做一个猜数字游戏)
学习·rust
INS_KF7 小时前
【知识杂记】卡尔曼滤波及其变种,从理论精要到工程实践深入解析
经验分享·笔记·学习
竹杖芒鞋轻胜马,夏天喜欢吃西瓜20 小时前
二叉树学习笔记
数据结构·笔记·学习