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

从企业实战中学习Appium自动化(二)

一.CommonMsgBox


通用消息对话框处理类

'''
消息对话框处理
'''
class CommonMsgBox(SRActivity):def __init__(self, sr_app: SR1620App):super().__init__(sr_app, wait_activity=False)self.update_locator({#  通用按钮'msgbox': {BY: SRBy.ID, VALUE: 'android:id/content'},'msgbox_title': {BY: SRBy.ID, VALUE: 'id/txt_dialog_title'},'msgbox内容': {BY: SRBy.ID, VALUE: 'id/txt_dialog_content'},'取消按钮': {BY: SRBy.ID, VALUE: 'id/txt_negative_btn'},'确定按钮': {BY: SRBy.ID, VALUE: 'id/txt_positive_btn'},#重命名输入框/修改刺激程序名字'重命名输入框': {BY: SRBy.ID, VALUE: 'id/et_rename'},#  定时刺激页面的“同步时间”弹窗'当前PAD时间': {BY: SRBy.ID, VALUE: 'id/txt_pad_time'},'当前IPG时间': {BY: SRBy.ID, VALUE: 'id/txt_ipg_time'},# 配对页面的“配对”弹窗'WLR信息': {BY: SRBy.ID, VALUE: 'id/txt_tlm_sn'},'IPG信息': {BY: SRBy.ID, VALUE: 'id/txt_ipg_sn'},#  IPG校验控件'code1': {BY: SRBy.ID, VALUE: 'id/tv_code1'},'code2': {BY: SRBy.ID, VALUE: 'id/tv_code2'},'code3': {BY: SRBy.ID, VALUE: 'id/tv_code3'},'code4': {BY: SRBy.ID, VALUE: 'id/tv_code4'},})def confirm_open_stim(self):if not self.controls['msgbox内容'].exist():returnassert self.controls['msgbox内容'].text.startswith('刺激输出已被关闭')self.controls['确定按钮'].click()def confirm_change_stim(self):if not self.controls['msgbox内容'].exist():returnassert '确定切换到' in self.controls['msgbox内容'].textself.controls['确定按钮'].click()def confirm_close_connection(self):assert self.controls['msgbox内容'].text.startswith('确定断开刺激连接')      self.controls['确定按钮'].click()def confirm_delete_program(self):assert self.controls['msgbox_title'].text == '删除程序'self.controls['确定按钮'].click()def confirm_change_stim_mode(self):if not self.controls['msgbox内容'].exist():returncontent = self.controls['msgbox内容'].textassert ('是否要切换刺激模式?\n 确定后,当前标准程序会被清空' == content)self.controls['确定按钮'].click()def confirm_sync_time(self):assert self.controls['msgbox_title'].text == '同步时间'self.controls['确定按钮'].click()def confirm_pair_ipg(self, ipg):assert self.controls['msgbox_title'].text == '配对提醒'assert self.controls['IPG信息'].text.startswith(f'患者IPG序列号:{ipg}')self.controls['确定按钮'].click()def confirm_dis_pair_ipg(self):assert self.controls['msgbox_title'].text == '解除配对'self.controls['确定按钮'].click()def confirm_conn_ipg(self, ipg):if self.controls['msgbox_title'].exist() and self.controls['msgbox_title'].text == '校验':self.controls['code1'].press_text(int(ipg[-4:-3]))self.controls['code2'].press_text(int(ipg[-3:-2]))self.controls['code3'].press_text(int(ipg[-2:-1]))self.controls['code4'].press_text(int(ipg[-1:]))self.controls['确定按钮'].click()@classmethoddef msg_box_exist(cls, app) -> bool:msg_box = CommonMsgBox(app)return msg_box.controls['msgbox'].exist() and msg_box.controls['msgbox_title'].exist()def confirm_over_charge_density_limit(self):assert self.controls['msgbox_title'].text == '温馨提示'assert self.controls['msgbox内容'].text == '刺激参数的电荷密度越限,是否继续程控?'self.controls['确定按钮'].click()def confirm_set_p1_zero(self):assert self.controls['msgbox_title'].text == '温馨提示'assert self.controls['msgbox内容'].text == '清空P1幅值将会使P2幅值归零'self.controls['确定按钮'].click()

(一)大概

1.通用元素/特定场景元素

2.

3.

为什么大部分方法是实例方法?

答:因为在消息对话框处理类中,每个消息对话框都是在不同业务场景下的,需要通过实例提前绑定具体的业务场景

二.CommonMsgTip

通用提示弹窗处理

class CommonMsgTip(SRActivity):def __init__(self, sr_app: SR1620App):super(CommonMsgTip, self).__init__(sr_app, wait_activity=False)self.update_locator({# 提示弹窗'提示弹窗': {BY: SRBy.ID, VALUE: 'id/ll_prompt'},'弹窗内容': {BY: SRBy.ID, VALUE: 'id/dialog_content'},'弹窗提示语': {BY: SRBy.ID, VALUE: 'id/tv_tip'},'弹窗关闭按钮': {BY: SRBy.ID, VALUE: 'id/img_close'},})@classmethoddef handle_line_busy(cls, app) -> bool:time.sleep(1)tip = CommonMsgTip(app)if not tip.controls['弹窗提示语'].exist():return Falsetry:if tip.controls['弹窗提示语'].exist() and tip.controls['弹窗提示语'].text == '通讯线路忙,请稍候再试':tip.controls['弹窗关闭按钮'].click()return Trueexcept Exception:return Falsereturn False@classmethoddef handle_offline(cls, app) -> bool:time.sleep(1)msg_tip = CommonMsgTip(app)if not msg_tip.controls['弹窗提示语'].exist():return Falsetry:if msg_tip.controls['弹窗提示语'].exist() and msg_tip.controls['弹窗提示语'].text == '连接信号已断开,请调整角度或移近距离后重试':msg_tip.controls['弹窗关闭按钮'].click()return Trueexcept Exception:return Falsereturn False@classmethoddef handle_exception(cls, app) -> bool:time.sleep(1)msg_tip = CommonMsgTip(app)if not msg_tip.controls['弹窗提示语'].exist():return Falsetry:if msg_tip.controls['弹窗提示语'].exist() and msg_tip.controls['弹窗提示语'].text == '':msg_tip.controls['弹窗关闭按钮'].click()return Trueexcept Exception:return Falsereturn False@classmethoddef handle_low_battery(cls, app) -> bool:time.sleep(1)msg_tip = CommonMsgTip(app)if not msg_tip.controls['弹窗提示语'].exist():return Falsetry:# print(msg_tip.controls['弹窗提示语'].text)if msg_tip.controls['弹窗提示语'].exist() and msg_tip.controls['弹窗提示语'].text == '刺激器电量不足,无法开启程控哦!':msg_tip.controls['弹窗关闭按钮'].click()return Trueexcept Exception:return Falsereturn False

(二)大概

1.po模式的“专项化延伸”:弹窗专属PO类

2.类方法(@classmethod)的妙用:无需实例化,直接调用

3.

4.

5.

三.CommonLoadingBar

转圈的小图标——“加载状态”

class CommonLoadingBar(SRActivity):def __init__(self, sr_app: SR1620App):super().__init__(sr_app, wait_activity=False) # 禁用“页面Activity等待”self.update_locator({# 提示弹窗'loading状态': {BY: SRBy.ID, VALUE: 'id/img_loading'},'loading状态2': {BY: SRBy.ID, VALUE: 'id/ivProgress'},})def wait_for_loading_disappear(self, timeout=5):self.wait_for_disappear('loading状态', wait_timeout=timeout)self.wait_for_disappear('loading状态2', wait_timeout=timeout)
class SRActivity(object):...............def wait_for_disappear(self, control_key: str, wait_timeout=30, wait_interval=0.5):'''等待控件消失,比如loading动画控件等:param control_key::param wait_timeout::param wait_interval::return:'''by, value = self.get_locator(control_key)time0 = time.time()while time.time() - time0 < wait_timeout:try:view = self._driver.find_element(by, value)if not view.is_enabled() or not view.is_displayed():returnexcept (NoSuchElementException, StaleElementReferenceException):returntime.sleep(wait_interval)raise TimeoutError(f'等待控件消失超时:控件[{control_key}]:[{value}]依然存在')

(一)知识点!!

1.为什么wait_activity为false?设为true可以吗

这个工具类的作用是:在当前已显示的页面中找加载状态控件

(1)每个页面的加载状态控件都不一样啊,怎么确定初始化的加载动态控件一定是Ipg连接界面的呢?

(2)CommonLoadingBar初始化谁?它怎么知道当前屏幕上显示的页面是IPG连接页面?

2.wait_for_loading_disappear 不使用 @classmethod 装饰器,不设计为类方法?而CommonMsgTip类里的方法却设计为类方法

而实例方法能通过 “创建实例” 提前绑定目标;类方法只能 “每次临时找目标”,很容易盯错或盯一半就断了 —— 所以必须用实例方法。?

3.怎么绑定到Ipg连接界面的, loading_bar = CommonLoadingBar(self.get_app())这个绑定的不是只是app吗?

——————————————————-——————————————————————

(二)总结!

1.在页面类中,你是怎么去判断一个方法是适合设计为类方法还是实例方法?

答:这个方法中所操作的元素是跨页面的公共元素(比如提示弹窗),还是和特定场景强关联的元素

元素的通用性和定位稳定性决定了方法类型

通用且定位稳定的元素适合类方法简化调用;与具体场景强关联的元素需要实例方法绑定上下文,确保操作准确。

2.什么样的类适合将wait_activity设定为false或ture?

答:看这个类是工具类(不对应独立页面,仅监控/操作当前屏幕的临时元素)还是页面业务类(对应app中一个独立的页面,有明确的activity标识)

四.IPGPage

class IPGConfirmPage(SRActivity):def __init__(self, sr_app: SR1620App):super().__init__(sr_app)self.update_locator({})class IPGPage(IPGConfirmPage):Activity = '.modules.connectipg.ActConnectIPG'  # 连接IPG界面def __init__(self, sr_app: SR1620App):super().__init__(sr_app)self.update_locator({'体外程控器编号': {'by': SRBy.ID, 'value': 'id/tv_tlm_info'},'程控记录按钮': {'by': SRBy.ID, 'value': 'id/frame_record'},'配对按钮': {'by': SRBy.ID, 'value': 'id/frame_pair'},'设置按钮': {'by': SRBy.ID, 'value': 'id/frame_setting'},'返回按钮': {'by': SRBy.ID, 'value': 'id/iv_back'},'ipg搜索输入框': {'by': SRBy.ID, 'value': 'id/et_search'},'ipg搜索按钮': {'by': SRBy.ID, 'value': 'id/iv_search'},'ipg搜索列表': {'by': SRBy.ID, 'value': 'id/rv_ipg_list'},'ipg序列号': {'by': SRBy.XPATH, 'value': '(.//android.widget.TextView)'},'查找按钮': {'by': SRBy.ID, 'value': 'id/tv_scan'},#  注意'注意提示语': {'by': SRBy.ANDROID_UIAUTOMATOR, 'value': 'new UiSelector().text("注意:请在患者前方1米范围内搜寻并连接")'},})@allure.step('点击查找按钮,进行查找IPG;')def begin_find_ipg(self):while self.controls['查找按钮'].text == '查找':self.controls['查找按钮'].click()time.sleep(1)if not CommonMsgTip.handle_line_busy(self.get_app()):breakloading_bar = CommonLoadingBar(self.get_app())loading_bar.wait_for_loading_disappear(10)def stop_find_ipg(self):while self.controls['查找按钮'].text == '停止查找':self.controls['查找按钮'].click()time.sleep(1)if not CommonMsgTip.handle_line_busy(self.get_app()):breakloading_bar = CommonLoadingBar(self.get_app())loading_bar.wait_for_loading_disappear(10)def _handle_process_bar(self, timeout=120) -> bool:time.sleep(1)time0 = time.time()while time.time() - time0 < timeout:if CommonMsgTip.handle_line_busy(self.get_app()):return Falseif CommonMsgTip.handle_offline(self.get_app()):return Falsemsg_box = CommonMsgBox(self.get_app())msg_box.confirm_conn_ipg(self.get_app().get_ipg())msg_tip = CommonMsgTip(self.get_app())if msg_tip.controls['弹窗提示语'].exist():time.sleep(1)continueelse:return Truereturn False# 搜索IPG@allure.step('点击搜索IPG按钮搜索IPG;')def search_ipg(self, ipg) -> bool:# allure.step(f"ipg编号: {ipg},连接该ipg")self.controls['ipg搜索输入框'].text = ipgself.controls['ipg搜索输入框'].hide_keyboard()timeout = 60 * 5time0 = time.time()while time.time() - time0 < timeout:self.stop_find_ipg()self.controls['ipg搜索按钮'].click()if CommonMsgTip.handle_line_busy(self.get_app()):continueif CommonMsgTip.handle_offline(self.get_app()):continueif CommonMsgTip.handle_exception(self.get_app()):continuetime.sleep(2)by, value = self.get_locator('ipg序列号')view_list = self.controls['ipg搜索列表'].children(by, value)view = Nonefor sr_view in view_list:if sr_view.text.startswith(ipg):view = sr_viewbreakif view:view.click()if self._handle_process_bar():return Truereturn False# raise ControlNotFoundError(f"连接体外程控器: {ipg}失败,未找到对应体外程控器")def find_ipg(self, ipg, name: str = ''):for i in range(0, 5):timeout = 60time0 = time.time()self.begin_find_ipg()while time.time() - time0 < timeout:by, value = self.get_locator('ipg序列号')view_list = self.controls['ipg搜索列表'].children(by, value)for sr_view in view_list:if name and sr_view.text == ipg + name:self.stop_find_ipg()return sr_viewif sr_view.text.startswith(ipg):self.stop_find_ipg()return sr_viewtime.sleep(2)self.controls['ipg搜索列表'].swipe_up()self.stop_find_ipg()return Nonedef connect_ipg(self, ipg_view: SRView, timeout=60) -> bool:if not ipg_view:return Falsetime0 = time.time()while time.time() - time0 < timeout:ipg_view.click()if CommonMsgTip.handle_line_busy(self.get_app()):continueif CommonMsgTip.handle_offline(self.get_app()):continueif CommonMsgTip.handle_exception(self.get_app()):continueif self._handle_process_bar():return Truereturn False

(一)代码讲解

1.自动化场景稳定性处理(关键保障)

常因为“弹窗干扰”“加载延迟失败”——超时控制/弹窗拦截/进度条等待等

2.@allure.step 装饰器

标记测试步骤,生成allure报告,便于问题追溯

3.

4.

5.设计亮点

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

相关文章:

  • Unity 使用ADB工具打包Apk 安装到Android手机或平板
  • 一、移动零,复写零,快乐数
  • React资源合集
  • sem是什么职业邢台做网站建设优化制作公司
  • 福建省建设执业资格注册中心网站企业建设网站注意点
  • 配置Modbus TCP转RS485模块读取温度数据
  • OSPF LSA/ 路由种类
  • 算法面试(5)------NMS(非极大值抑制)原理 Soft-NMS、DIoU-NMS 是什么?
  • 乐清网站制作推荐建湖人才网最新招聘信息
  • AWS下载sentinel-2原始影像
  • docker-容器网络类型
  • MySQL 中使用索引
  • 双功能分子:NOTA Octreotide Acetate,NOTA-奥曲肽具有放射性金属螯合能力
  • 帆软FCP开发认证模拟第二题
  • 做网站打印费复印费清单中方建设局网站
  • PyTorch DataLoader 接受的返回值类型
  • rust slint android 安卓
  • 网站后台建设怎么进入超级优化小说
  • 游戏对象AI类型释义
  • Harnessing Text Insights with Visual Alignment for Medical Image Segmentation
  • 网上做网站 干对缝儿生意外贸网站推广优化
  • 【Java后端】MyBatis 和 MyBatis-Plus (MP) 的区别
  • iOS PPBluetoothKit接入无法找到头文件问题
  • leetcode orb slam3 3/99--> leetcode49 Group Anagrams
  • c# 读取xml到datagridview
  • 开源的 CSS 动画库
  • (三)过滤器及组件化开发
  • [NewBeeBox] A JavaScript error occurred in the main process
  • 【LangGraph】ReAct构建-LangGraph简单实现
  • 做毕业设计哪个网站好网站怎样做百度推广