当前位置: 首页 > news >正文

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

一、ext 

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

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

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

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

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

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

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

class WebDriverExt(webdriver.Remote):

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

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

2. __init__ 方法:初始化扩展类
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 来管理浏览器。
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,确保兼容性。
    super(WebDriverExt, self).__init__(command_executor, desired_capabilities, browser_profile, proxy, keep_alive,file_detector)
  • super(子类名, self).__init__(参数):调用父类的构造方法,确保父类的初始化逻辑被执行(如连接远程服务器、初始化浏览器)。

    self._driver = self  # 给当前实例(self)起一个别名_driver
  • self._driver:定义实例属性 _driver,值为实例本身(self)。
  • 用途:后续在类内部可以通过 self._driver 调用实例的方法(和 self 效果一样,可能是为了代码可读性或兼容旧逻辑)。
    self.initConfig()  # 调用当前类的initConfig方法,执行自定义配置
  • self.initConfig():调用本类中定义的 initConfig 方法,触发额外的初始化逻辑(如设置等待时间)。

    3. initConfig 方法:设置默认配置
    def initConfig(self):"""初始化Driver的配置"""# 从框架配置中读取"隐式等待时间",设置给driverself.implicitly_wait(settings.DRIVER['implicitlyWait'])
    

    • implicitly_wait() 是 Selenium 的隐式等待方法(当查找元素时,如果没找到,会等待指定时间再报错)。
    • settings.DRIVER['implicitlyWait']:从框架的配置文件(settings)中读取预设的等待时间(比如 10 秒),避免每次使用都手动设置,实现 “全局统一配置”。
    4. windowScrollTo 方法:自定义页面滚动功能
    def windowScrollTo(self, x, y):# 确保滚动坐标不小于0(避免无效值)x = 0 if x < 0 else xy = 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
    class WebDriver(WebDriverExt):  # 定义WebDriver类,继承自WebDriverExt
    

    2. __init__ 方法:初始化并新增基础属性
    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)操作。它在保留原生功能的基础上,增加了对 “窗口索引切换” 的支持和自定义异常处理,让测试用例中的切换操作更符合实际使用习惯。

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

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

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

    #@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 类)

    @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 

    # coding=utf-8  # 声明编码格式,支持中文注释(避免中文乱码)
    from selenium.webdriver.common.alert import Alert as SeleniumAlert  # 导入Selenium原生Alert类,起别名“SeleniumAlert”
    
    • 关键:用 as SeleniumAlert 给原生类起别名,是为了 避免和当前自定义的 Alert 类重名(如果直接 from ... import Alert,会覆盖自定义类,导致报错)。
    class Alert(SeleniumAlert):  # 自定义Alert类,继承自Selenium原生Alert
    
    • 语法:class 子类(父类),这是 Python 面向对象 “继承” 的基础写法,确保自定义类能复用原生类的所有方法(如 accept()dismiss() 等)。
    def __init__(self, driver):super(Alert, self).__init__(driver)  # 调用父类(原生Alert)的构造函数self._driver = driver  # 额外保存驱动实例到当前类
    

    dismiss 方法:关闭弹窗(预留扩展位)
    #@driverAction_dec("取消警告框",False)  # 注释掉的装饰器(核心预留点)
    def dismiss(self):super(Alert, self).dismiss()  # 直接调用原生Alert的dismiss()方法
    

    • 原生功能:dismiss() 用于 “关闭弹窗”(对应弹窗的 “取消” 按钮,比如浏览器提示 “是否离开此页面” 时,点击 “取消”)。
    • 框架封装意图:目前完全复用原生逻辑,没有新增代码,但注释的 @driverAction_dec 装饰器是关键 —— 这是 预留的扩展位
      比如未来想给 “关闭弹窗” 操作添加日志(记录 “何时关闭了弹窗”)或截图(关闭前截图留存证据),只需启用装饰器。

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

     accept 方法:确认弹窗(同 dismiss 逻辑)
    #@driverAction_dec("接受警告框",False)  # 同样是预留装饰器
    def accept(self):super(Alert, self).accept()  # 调用原生Alert的accept()方法
    

     send_keys 方法:向弹窗输入文本(同逻辑)
    #@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 原生定位标识” 的兼容问题。

    # 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 重名,会导致代码混淆)。
    class WebDriverBy:  # 无父类,是一个“纯静态工具类”(只存映射表和转换方法)
    
    • 这个类不需要继承任何父类,因为它的核心功能是 “存储映射关系” 和 “提供转换方法”,本质是一个 工具类(类似 “字典 + 静态方法” 的组合,但用类封装更规整)。

    类属性 byMap:定位方式映射表
    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:执行定位方式转换
    @classmethod  # 装饰为“类方法”,无需创建实例,直接通过类调用
    def convert_by(cls, by):return cls.byMap[by]  # 根据传入的框架By,返回对应的原生By
    

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

    六、 exceptionUtil模块

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

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

    from frameworkCore.driver.exception import TimeOutException, TimeOutException
    

    def isTimeOutException(err):return isinstance(err, TimeOutException)
    

    • 核心功能:判断传入的异常对象 err,是否属于 TimeOutException 类型(或其子类类型)。
    • 关键语法:isinstance(err, 异常类) 是 Python 中判断 “对象是否属于某个类(或其派生类)” 的标准方法,返回 True/False

      • 举例:如果 err 是 TimeOutException("元素定位超时") 的实例,返回 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 次:

    import exceptionUtil  # 导入异常工具
    from frameworkCore.driver.exception import TimeOutExceptiondef 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:日志记录异常类型

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

    import logging
    import exceptionUtildef 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,不要重复造轮子)” 的典型体现~

    http://www.dtcms.com/a/353891.html

    相关文章:

  • Element整体操作样式
  • 数据分析编程第五步:数据准备与整理
  • DDD之事件机制(9)
  • 沃丰科技出海客服系统对接沃尔玛全球电商平台,赋能中企出海
  • 升级DrRacket8.10到8.18版本@Ubuntu24.04
  • GitLab 导入/导出仓库
  • 金融 IT 运维痛点突围:用网络管理工具筑牢业务稳定防线(附 OpManager Plus 实践)
  • 【51单片机按键按下数码管秒增计时并LED亮释放停计时LED熄】2022-11-12
  • Android -第二十一次技术总结
  • 使用LLAMA_cpp_python进行qwen2.5-vl-7b-instruct进行推理
  • 【URP】Unity Shader Tags
  • IT66122替代IT66121-富利威
  • Day12 Gin框架学习
  • .NET周刊【8月第3期 2025-08-17】
  • 【C#】获取不重复的编码(递增,非GUID)
  • (LeetCode 面试经典 150 题) 102. 二叉树的层序遍历(广度优先搜索bfs)
  • Miniforge3替代Anaconda的一些经验总结
  • STL库——vector(类模拟实现)
  • 旧物二手回收小程序系统:让闲置旧物焕发新生,创造无限价值
  • Leetcode 深度优先搜索 (14)
  • 胶水研究记录学习1
  • 回顾websocket心跳机制以及断线重连(服务端为node)
  • 数据结构——抽象数据类型(ADT)
  • 浏览器渲染帧管线全景拆解:从像素到屏幕的 16.67 ms 之旅
  • Linux内核bitmap组件详解
  • 给Ubuntu添加新用户
  • MyBatis 之关联查询(一对一、一对多及多对多实现)
  • Ansible Playbook 概述与实践案例(下)
  • 基于muduo库的图床云共享存储项目(二)
  • STM32 之串口WIFI应用--基于RTOS的环境