【日常学习8】2025-9-3 学习控件Day2
输入控件分为:输入控件基类,输入控件类,具体输入定位器,
像xxxLoc这个控件就是具体输入控件inputctrl,是定位器通过xpath得来的具体框元素。
而OnlyInput这个控件就是输入控件类,不需要传入具体inputctrl定位,由父类获取具体文本框/文本域,别的类型交给radio checkbox控件类。
radio里面有具体的定位器,类方法里根据不同的情况选择不同的定位器,比如:
if isinstance(value, str):radio_ctrl_obj = self.find_child(self.radio_ctrlLoc.format_key(value))radio_ctrl_obj.click()return radio_ctrl_obj
1.MessageItemCtrl(输入控件)
是一个处理 “消息项启停用状态” 的控件,主要用于管理系统中各类消息项(如通知、提醒等)的启用或停用状态,支持批量操作和状态校验。
系统设置,消息设置页面专属控件。
一、基础信息:定位规则与继承
# 导入输入控件基类:提供元素查找、基础操作等通用能力
from frameworkCore.business.InputCtrlBase import InputCtrlBase;
# 导入定位工具:定义如何找到消息项的状态按钮
from frameworkCore.driver.locator import Locator
from frameworkCore.driver.by import By
# 导入重试装饰器:处理操作可能出现的临时问题(如点击后状态未立即更新)
from frameworkCore.common.decorator import retry
# 导入运行上下文:(当前代码未直接使用,预留扩展)
from frameworkCore.business.runContext import RunContextclass MessageItemCtrl(InputCtrlBase):# 定位“消息项的启停用状态按钮”# 规则:在class为"message-item"的消息项中,找到标题为{key}的消息,再定位其状态切换按钮statusLoc = Locator(By.XPATH,'.//div[@class="message-item"]//div[ @title="{}"]/parent::div/parent::div/div[@class="message-content-switch"]',wait=2,desc="启停用状态按钮")
statusLoc
定位器:通过消息项的title
属性({}
占位符)精准定位对应消息项的状态切换按钮(比如 “订单通知”“库存预警” 等不同消息项的开关)。
二、核心方法input()
:批量设置消息项状态
@retry() # 操作失败时自动重试(如状态切换后未立即生效)
def input(self, value):# value是字典格式:{消息项名称: 目标状态},如{"订单通知": True, "库存预警": False}# True表示“启用”,False表示“停用”# 遍历字典中的每个消息项for key in value:# 1. 定位到当前消息项的状态按钮(用key替换占位符,如"订单通知")itemEle = self.find_child(self.statusLoc.format_key(key))# 2. 获取当前状态status = self.__getItemStatus(itemEle)# 3. 判断是否需要操作:如果当前状态与目标状态一致,跳过;否则点击切换if value[key] == status:continue # 状态一致,无需操作else:itemEle.mouse_click() # 点击状态按钮切换状态# 4. 再次获取状态并校验是否切换成功status = self.__getItemStatus(itemEle)assert status == value[key] # 校验失败则抛出错误
逻辑拆解:
这个方法支持批量处理多个消息项的状态,流程是 “定位→查状态→按需操作→校验”,确保每个消息项都能准确切换到目标状态。
三、辅助方法:获取状态
def __getItemStatus(self, ele):# 私有方法:根据状态按钮的文本判断当前状态status = ele.text() # 获取按钮上的文本(如"已启用"或"已停用")# 转换为布尔值:"已启用"→True,其他→FalsebStatus = True if status == '已启用' else Falsereturn bStatusdef getItemStatus(self, name):# 公开方法:获取指定消息项的当前状态# 1. 定位到目标消息项的状态按钮itemEle = self.find_child(self.statusLoc.format_key(name))# 2. 调用私有方法返回状态return self.__getItemStatus(itemEle)
在input方法里使用的是私有方法,每一步使用私有方法前都find_child了,但是外部要想调用公开方法,返回值是私有方法返回的,那么就应该在公开方法内部先加上find_child定位到目标消息项的当前状态。
__getItemStatus
(私有方法):将前端显示的文本(“已启用”/“已停用”)转换为程序易处理的布尔值(True/False),方便逻辑判断。getItemStatus
(公开方法):允许外部查询某个消息项的当前状态(比如测试用例需要先检查状态再执行操作)。
四、text()
方法:复用父类功能
def text(self):return super().text() # 调用父类的text()方法,获取控件文本(当前场景中用途较少)
2.OnlyInputComp
是一个专注于 “纯输入操作” 的组件,继承自InputComp
,专门处理各种表单输入场景(包括普通文本输入、单选框、复选框等),核心特点是 “简化输入逻辑,只关注输入操作本身”。
一、基础信息:继承与依赖
# 注释了注册装饰器,说明可能是内部使用或测试中的组件
# @register_comp
class OnlyInputComp(InputComp): # 继承自InputComp,复用基础输入逻辑# 直接输入的控件对象(不需要预先定义定位器,灵活接收外部传入的元素)inputCtrl = None # 不用定位,只用输入逻辑
- 继承
InputComp
:获得父类的基础输入能力,但弱化了 “定位元素” 的逻辑(inputCtrl
可以直接由外部传入)。 - 灵活性高:不强制依赖预定义的定位器,而是通过
inputCtrl
动态接收需要操作的输入元素,适配各种输入场景。
二、初始化方法__init__
:接收输入元素
def __init__(self, ctrl=None):self.inputCtrl = ctrl # 接收外部传入的输入元素(如文本框、单选框等),直接赋值给inputCtrl
作用:
允许在创建OnlyInputComp
实例时,直接传入一个已经定位好的输入元素(ctrl
),跳过 “通过定位器查找元素” 的步骤,简化流程。例如:
# 先定位到一个文本框元素
text_input_ele = driver.find_element(By.ID, "username")
# 直接将元素传入OnlyInputComp
input_comp = OnlyInputComp(ctrl=text_input_ele)
# 调用输入方法
input_comp._input("admin")
三、核心方法_input
:处理各种类型的输入
def _input(self, data):"""表单数据输入--直接输入and参照输入"""try:# 步骤1:如果inputCtrl未传入(外部没给元素),则自动查找输入框if self.inputCtrl is None:try:# 先尝试用父类的inputCtrlLoc定位普通输入框self.inputCtrl = self.find_child(self.inputCtrlLoc)except:# 如果失败,尝试定位文本域(textarea)self.inputCtrl = self.find_child(self.textareaInputCtrlLoc)# 步骤2:获取输入元素的类型(type属性),判断是哪种输入控件type = self.inputCtrl.getAttribute('type')# 步骤3:根据类型处理不同输入场景if type == 'radio': # 单选框(radio)# 复用RadioCtrl控件处理单选逻辑radio_ctrl = RadioCtrl.instance(self.get_parent())radio_ctrl.input(data) # 传入选项值(如"男"、"女")return False # 处理完成,返回if type == 'checkbox': # 复选框(checkbox)# 复用CheckBoxCtrl控件处理复选逻辑check_box_ctrl = CheckBoxCtrl().instance(self)check_box_ctrl.input(data) # 传入选项列表(如["选项1", "选项2"])return False # 处理完成,返回else: # 其他类型(文本框、密码框等普通输入)self.inputCtrl.clear() # 清空输入框self.inputCtrl.selectAll() # 全选(确保清空彻底)self.inputCtrl.sendKeys(data) # 输入数据(如文本、数字)time.sleep(0.1) # 短暂等待,确保输入生效except Exception as err:# 捕获异常并包装,方便调试raise Exception("OnlyInputComp异常" + repr(err))
逻辑拆解:
这个方法是输入操作的 “总控”,根据输入元素的类型(type
)自动适配不同的输入逻辑,兼容多种表单控件:
元素获取逻辑:
- 优先使用外部传入的
inputCtrl
(如果有); - 若未传入,则自动查找普通输入框或文本域(复用父类的定位器)。
- 优先使用外部传入的
分类型处理:
- 单选框(radio):交给
RadioCtrl
控件处理(比如选择 “男” 或 “女”); - 复选框(checkbox):交给
CheckBoxCtrl
控件处理(比如勾选 “选项 A” 和 “选项 B”); - 普通输入(文本框等):直接执行 “清空→全选→输入” 流程(比如输入用户名、邮箱)。
- 单选框(radio):交给
四、_after_handler
方法:输入后处理(预留)
def _after_handler(self, data):pass # 空实现,用于输入后需要额外处理的场景(如触发校验、关闭弹窗等)
作用:
这是一个 “钩子方法”,预留用于输入完成后的后续操作。例如:
- 输入后需要点击 “确认” 按钮;
- 输入后需要等待校验提示消失;
- 未来可以根据业务需求重写这个方法,扩展功能。
五、这个控件的设计目的和适用场景
OnlyInputComp
的核心设计目的是 **“简化输入操作,专注于输入本身”**,适用于以下场景:
- 快速输入:已经通过其他方式定位到输入元素,只想快速执行输入操作(无需重复定位);
- 多类型兼容:同一表单中既有文本框、又有单选 / 复选框,用一个控件统一处理;
- 轻量复用:不需要复杂的前置 / 后置逻辑,只需要基础的输入功能。
这段代码的核心是理解OnlyInputComp
如何继承并复用父类InputCompBase
的定位器,以及在inputCtrl
未传入时如何自动查找输入框元素。
InputCompBase
中定义了一个通用的输入框定位器:
class InputCompBase(InputCtrlBase):# 定位规则:匹配所有非隐藏的input,或textarea(文本域)inputCtrlLoc = Locator(By.XPATH, ".//input[not(@type) or @type != 'hidden']|.//textarea", # 关键定位规则wait=1)inputCtrl = None # 用于保存找到的输入框元素
这个inputCtrlLoc
是父类的 “通用输入框定位器”,作用是:
自动匹配两种常见输入元素:
- 所有非隐藏的
input
标签(@type != 'hidden'
)—— 包括文本框、密码框等; - 所有
textarea
标签(多行文本域)。
在OnlyInputComp
的_input
方法中,当外部没有传入inputCtrl
(即没有手动指定输入元素)时,会执行以下步骤:
if self.inputCtrl is None: # 外部没传入输入元素try:# 步骤1:尝试用父类的inputCtrlLoc定位普通输入框self.inputCtrl = self.find_child(self.inputCtrlLoc)except:# 步骤2:如果失败,尝试定位文本域(textarea)self.inputCtrl = self.find_child(self.textareaInputCtrlLoc)
拆解逻辑:
为什么先尝试
self.inputCtrlLoc
?self.inputCtrlLoc
继承自父类InputCompBase
,其定位规则是.//input[not(@type) or @type != 'hidden']|.//textarea
,已经包含了textarea
。
优先用这个定位器,是为了复用父类的通用规则,覆盖大多数输入场景(普通输入框 + 文本域)。为什么需要
except
里的textareaInputCtrlLoc
?
这是一种 “兼容处理”:- 某些特殊页面中,文本域(textarea)的结构可能比较特殊,父类的
inputCtrlLoc
可能定位不到(比如被特殊 class 包裹); - 因此
OnlyInputComp
可能在自己的类中单独定义了textareaInputCtrlLoc
(专门针对文本域的更精确的定位器,比如Locator(By.XPATH, './/textarea[@class="special-textarea"]')
); - 当父类的通用定位器失败时,就用子类专门定义的文本域定位器再试一次,提高定位成功率。
- 某些特殊页面中,文本域(textarea)的结构可能比较特殊,父类的
3.InputCompBase
是一个输入组件的基础类,为所有输入相关的控件(如文本框、单选框、文本域等)提供通用的输入逻辑、值校验和元素定位能力。它是框架中 “输入控件家族” 的父类,子类(如InputComp
、OnlyInputComp
)可以继承并扩展它的功能。
文本框和文本域:
<input type="text" value="默认文本">
<textarea>默认多行文本</textarea>
一、核心定位与属性
class InputCompBase(InputCtrlBase):# 通用输入框定位器:匹配所有可见输入元素inputCtrlLoc = Locator(By.XPATH, ".//input[not(@type) or @type != 'hidden']|.//textarea", # 核心定位规则wait=1)inputCtrl = None # 用于存储找到的输入元素(如input、textarea等)
关键设计:
inputCtrlLoc
:一个 “万能定位器”,能匹配两种常见输入元素:- 所有非隐藏的
input
标签(@type != 'hidden'
)—— 包括文本框、密码框、单选框等; - 所有
textarea
标签(多行文本域)。
这使得子类无需重复定义基础定位规则,直接复用即可。
- 所有非隐藏的
inputCtrl
:保存实际操作的输入元素(可以是外部传入,也可以通过inputCtrlLoc
自动查找),是后续输入、校验的核心对象。
二、核心方法:input()
—— 输入流程的总控
@retry() # 操作失败时自动重试(解决网络延迟、元素未就绪等问题)
def input(self, data):# 调用子类实现的_input方法(具体输入逻辑由子类决定)result = self._input(data)# 如果输入成功(result为None或True),执行后续校验和处理if result is None or result is True:# 特殊情况:如果输入空字符串,直接返回(无需校验)if isinstance(data, str) and data == "":return# 校验输入值是否正确(核心步骤)self._assert_input_value(data)# 业务特殊处理:某些字段输入后不执行_after_handler(避免触发不必要的交互)# 例如:出库数量输入后回车可能导致弹窗关闭,收入汇率输入后会自动四舍五入if hasattr(self, 'key') and (self.key in ['出库数量', '出库数量2', '收入_汇率']):return# 输入后的附加处理(由子类实现,如触发回车、关闭弹窗等)self._after_handler(data)
子类_input的返回值是True或者False,结合刚刚子类OnlyInputCtrl的代码,如果是直接输入,就是文本类输入,需要校验文本值。但要是单选框复选框就交给别的输入控件,返回false,无需校验。
在OnlyInputComp
的_input
方法中,普通输入框的处理逻辑确实没有显式返回True
:
# OnlyInputComp的_input方法
else: # 处理普通输入框(非radio/checkbox)self.inputCtrl.clear()self.inputCtrl.selectAll()self.inputCtrl.sendKeys(data)time.sleep(0.1)# 没有return语句
这是因为:
在 Python 中,函数如果没有显式 return,默认返回None
。
而父类input
方法的判断条件是if result is None or result is True
,None
会被视为 “输入成功”,因此会继续执行后续的校验(_assert_input_value
)和_after_handler
。
流程拆解:
这个方法定义了 “输入操作” 的标准流程 ——执行输入→校验结果→附加处理
,但把具体的输入逻辑(_input
)和附加处理(_after_handler
)交给子类实现,体现了 “模板方法模式” 的设计思想:
通用的写在父类,灵活的交给子类。
- 通用性:所有输入控件都需要 “校验结果”,因此
_assert_input_value
在父类中实现; - 灵活性:不同控件的输入方式不同(如文本框直接输入、单选框点击选项),因此
_input
由子类定制。
三、校验方法:_assert_input_value
—— 确保输入正确
@retry(1, 2) # 重试1次,间隔2秒(解决输入后值未立即更新的问题)
def _assert_input_value(self, data):data = str(data) # 统一转为字符串处理if data != '': # 非空值校验# 如果是单值,转为列表便于统一遍历(支持多值校验扩展)if isinstance(data, str):data = [data]for value in data:# 移除特殊标记(如"_notEnter"可能是业务中用于跳过回车的标记)value = value.replace("_notEnter", "")time.sleep(0.5) # 等待值更新# 核心断言:输入框的value属性中必须包含目标值assert (value in self.inputCtrl.getAttribute("value")), \f'元素html:{self.inputCtrl.get_inner_html()}期望值:{value},实际值:{self.inputCtrl.getAttribute("value")}'else: # 空值校验# 严格匹配空字符串assert (data == self.inputCtrl.getAttribute("value")), \f'元素html:{self.inputCtrl.get_inner_html()}期望值:{data},实际值:{self.inputCtrl.getAttribute("value")}'
比较输入的预期值和输入控件(inputCtrl:具体的输入控件,文本框单选框啥的)实际输入的值
设计目的:
输入操作后必须验证 “实际输入值” 与 “预期值” 一致,避免因页面延迟、输入失败等问题导致后续流程出错。
- 支持单值和多值校验(通过列表);
- 处理特殊业务标记(如
_notEnter
); - 包含重试和等待,应对页面渲染延迟。
四、获取值方法:text()
—— 统一获取输入值
def text(self):"""获取控件当前的值(适配不同类型的输入元素)"""# 如果inputCtrl未初始化,自动查找(优先用inputCtrlLoc,失败则找span标签)if self.inputCtrl is None:try:self.inputCtrl = self.find_child(self.inputCtrlLoc)except:self.inputCtrl = self.find_child(Locator(By.XPATH, './/span', wait=1))# 处理单选框(radio)的特殊情况:复用RadioCtrl获取选中值type = self.inputCtrl.getAttribute('type')if type == 'radio':radio_ctrl = RadioCtrl.instance(self.get_parent())return radio_ctrl.text()else:# 普通输入框:优先取value属性;如果没有value(如span文本),取text()if self.inputCtrl.getAttribute('value') is None:return self.inputCtrl.text()return self.inputCtrl.getAttribute("value")
核心能力:
统一不同输入元素的 “取值逻辑”,无论控件是input
、textarea
还是radio
,调用text()
都能返回正确的值,简化子类和外部调用。
五、辅助方法
is_this_ctrl
:判断元素是否为输入控件@classmethod def is_this_ctrl(cls, ctrl_html):# 如果元素的HTML中包含"input",则认为是输入控件return "input" in ctrl_html
enter
:模拟回车键def enter(self):self.inputCtrl.sendKeys(Keys.ENTER) # 向输入框发送回车键
输入完成后常用的操作(如提交表单、触发搜索),父类统一实现避免重复代码。
六、作为父类的设计价值
InputCompBase
的核心作用是 **“抽取输入控件的共性逻辑,统一标准”**,具体体现在:
- 减少重复代码:所有输入控件都需要定位元素、校验值,这些逻辑在父类中实现,子类只需关注自身特殊逻辑(如
_input
)。 - 统一操作标准:无论子类是文本框、单选框还是复合输入控件,都通过
input()
方法输入、text()
方法取值,外部调用方式一致。 - 增强可靠性:内置重试(
@retry
)、等待、断言等机制,解决页面不稳定问题,所有子类自动继承这些能力。