python + uiautomator2 常用公共方法封装

前言

由于公司UI自动化框架底层用的是Uiautomator2,所以我就用Uiautomator2搭了一套UI自动化框架,思路其实和Appnium一样的。

uiautomator2是一个自动化测试开源工具,仅支持android平台的自动化测试,其封装了谷歌自带的uiautomator2测试框架;

u2 现在google 官方使用的是apk的形式来实现的,有大神封装了python来实现u2的功能的使用。

具体的了解相关的功能和实现的原理可以查看开源库:github 的地址:https://github.com/openatx/uiautomator2

 

ui2 的下载安装与环境配置等,见之前写的一篇帖子:https://www.cnblogs.com/gancuimian/p/16725664.html

ui2 的常用方法使用(未封装),见之前写的一篇帖子:https://www.cnblogs.com/gancuimian/p/16947337.html

 整体框架介绍:(非固定模式,每个人的习惯不同,框架会有些出入,有些包可以是非必要)

框架搭建

ps:这里主要讲 common 包下面的公共方法类(basepage.py模块)的封装,其它包/模块不做详细介绍

先创建一个BasePage.py

  • 为什么要单独封装一个BasePage呢? 如果说以后我们不用uiautomator2这个框架了,我们只需要更改BasePage即可,不会影响到其他类的代码。

  • 另外,这个类也可以封装自己写的公用的方法,例如:重复性很高的代码,这些方法不论在哪个app里都能用的话,我们就单独拧出来封装成一个方法。

模块创建完成后,先导入需要用到的内置库或需要提前安装的第三方库。

1 import os
2 import re
3 import time
4 import random
5 from typing import Union
6 from data.Swipe_Direction import SwipeDirection
# 第6行导入的是下方的一个类;在下面代码 207 行的方法中有引用。

下面代码为本人工作中会用到的一些操作 方法的封装。

  1 import os
  2 import re
  3 import time
  4 import random
  5 from typing import Union
  6 from data.Swipe_Direction import SwipeDirection
  7 
  8 
  9 class BasePage:  # 构造函数
 10     def __init__(self, driver):
 11         self.driver = driver
 12 
 13     # 点击
 14     def click(self, element, sleepTime=0):
 15         if str(element).startswith("com"):  # 若开头是com则使用ID定位
 16             self.driver(resourceId=element).click()  # 点击定位元素
 17         elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
 18             self.driver.xpath(element).click()  # 点击定位元素
 19         else:  # 若以上两种情况都不是,则使用描述定位
 20             self.driver(description=element).click()  # 点击定位元素
 21         time.sleep(sleepTime)
 22 
 23 
 24     # 点击直到元素消失
 25     def click_until_gone(self, element, kind):
 26         if kind == "id":
 27             self.driver(resourceId=element).click_gone()
 28         elif kind == "class":
 29             self.driver(className=element).click_gone()
 30         elif kind == "text":
 31             self.driver(text=element).click_gone()
 32         else:  # 若以上两种情况都不是,则使用描述定位
 33             self.driver(description=element).click_gone()  # 点击定位元素
 34 
 35     # 组合定位classname和text
 36     def click_by_classname_text(self, element1, element2):
 37         self.driver(className=element1, text=element2).click()
 38 
 39     # 组合定位classname和description
 40     def click_by_classname_description(self, element1, element2):
 41         self.driver(className=element1, description=element2).click()
 42 
 43     # 组合定位text和description
 44     def click_by_text_description(self, element1, element2):
 45         self.driver(text=element1, description=element2).click()
 46 
 47 
 48     # 根据id点击(包括非com开头的id点击定位元素)
 49     def click_by_id(self, element, sleepTime=0):
 50         self.driver(resourceId=element).click()
 51         time.sleep(sleepTime)
 52 
 53 
 54     # 根据文本点击
 55     def click_by_text(self, element, sleepTime=0):
 56         self.driver(text=element).click()  # 点击定位元素
 57         time.sleep(sleepTime)
 58 
 59 
 60     # 根据百分比坐标点击
 61     def click_by_zuobiao(self, x, y, sleepTime=0):
 62         size = self.driver.window_size()
 63         self.driver.click(int(size[0] * x), int(size[1] * y))
 64         time.sleep(sleepTime)
 65 
 66 
 67     # 根据坐标点击元素
 68     def click_coord(self, x, y):
 69         self.driver.click(x, y)
 70 
 71 
 72     # 根据坐标双击元素
 73     def double_click_by_zuobiao(self, x, y, sleepTime=0):
 74         size = self.driver.window_size()
 75         self.driver.double_click(int(size[0] * x), int(size[1] * y))
 76         time.sleep(sleepTime)
 77 
 78 
 79     # 超时时间设置点击,根据文本定位,针对点击屏幕元素反应慢的元素
 80     def click_by_text_time_out(self, element, timeout=30, sleepTime=0):
 81         self.driver(text=element).click(timeout=timeout)
 82         time.sleep(sleepTime)
 83 
 84 
 85     # 长按
 86     def long_click_extend(self, element, dur=1, sleepTime=0):
 87         zhmodel = re.compile(u'[\u4e00-\u9fa5]')
 88         if str(element).startswith("com"):  # 若开头是com则使用ID定位
 89             self.driver(resourceId=element).long_click(dur)  # 点击定位元素
 90         elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
 91             self.driver.xpath(element).long_click()  # 点击定位元素
 92         elif zhmodel.search(str(element)):
 93             self.driver(description=element).long_click(dur)
 94         else:
 95             self.driver(className=element).long_click(dur)
 96         time.sleep(sleepTime)
 97 
 98 
 99     # 通过文本长击
100     def long_click_by_text(self, element, dur=0.5, sleepTime=0):
101         self.driver(text=element).long_click(dur)
102         time.sleep(sleepTime)
103 
104 
105     #  通过坐标长击
106     def long_click_by_zuobiao(self, x, y, sleepTime=0,duration: float = 1):
107         self.driver.long_click(x, y,duration)
108         time.sleep(sleepTime)
109 
110 
111     # 清空输入框中的内容
112     def clear(self, element):
113         if str(element).startswith("com"):  # 若开头是com则使用ID定位
114             self.driver(resourceId=element).clear_text()  # 清除文本
115         elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
116             self.driver.xpath(element).clear_text()  # 清除文本
117         else:  # 若以上两种情况都不是,则使用描述定位
118             self.driver(description=element).clear_text()  # 清除文本
119 
120 
121     # 输入
122     def input(self, element, value, sleepTime=0):
123         if str(element).startswith("com"):  # 若开头是com则使用ID定位
124             self.driver(resourceId=element).set_text(value)  # send_keys
125         elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
126             self.driver.xpath(element).set_text(value)
127         else:  # 若以上两种情况都不是,则使用描述定位
128             self.driver(description=element).set_text(value)
129         time.sleep(sleepTime)
130 
131     # 不存在搜索按钮的搜索
132     def input_by_send_keys(self, element, value):
133         self.driver.set_fastinput_ime(True)  # 切换成FastInputIME输入法
134         if str(element).startswith("com"):  # 若开头是com则使用ID定位
135             self.driver(resourceId=element).send_keys(value)  # send_keys
136         elif re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
137             self.driver.xpath(element).send_text(value)
138         else:  # 若以上两种情况都不是,则使用描述定位
139             self.driver(description=element).send_keys(value)
140         self.driver.set_fastinput_ime(False)  # 切换成正常的输入法
141         self.driver.send_action("search")  # 模拟输入法的搜索
142 
143 
144     # 查找元素
145     def find_elements(self, element, timeout=5):  # 找元素
146         is_exited = False
147         try:
148             while timeout > 0:
149                 xml = self.driver.dump_hierarchy()  # 获取网页层次结构
150                 if re.findall(element, xml):
151                     is_exited = True
152                     break
153                 else:
154                     timeout -= 1
155         except:
156             print("元素未找到!")
157         finally:
158             return is_exited
159 
160 
161     # 断言元素是否存在, 不能判定xpath元素
162     def assert_existed(self, element):  #
163         # assert self.find_elements(element) == True, "断言失败,{}元素不存在!".format(element)
164         return self.find_elements(element) == True
165 
166     # 判断元素是否存在,如随机弹窗等
167     def judge_existed(self, element, type: str = "text", timeout=2):
168         if re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
169             return self.driver.xpath(element).exists == True
170         elif type == "text":
171             return self.driver(text=element).exists(timeout=timeout) == True
172         elif type == "dec":
173             return self.driver(description=element).exists(timeout=timeout) == True
174         else:
175             pass
176 
177 
178     #  截图
179     def screenshot(self, imageName):
180         if os.path.exists(r"./images"):
181             if os.path.exists(fr"./images/{imageName}.png"):
182                 image = self.driver.screenshot()
183                 image.save(fr"./images/{imageName}_bak.png")
184             else:
185                 image = self.driver.screenshot()
186                 image.save(fr"./images/{imageName}.png")
187         else:
188             os.mkdir(r"./images")
189             image = self.driver.screenshot()
190             image.save(fr"./images/{imageName}.png")
191 
192 
193     # 拿取文本
194     def get_text_extend(self, element=None, type: str = "id"):
195 
196         if re.findall("//", str(element)):  # 若//开头则使用正则表达式匹配后用xpath定位
197             return self.driver.xpath(element).get_text()
198         elif type == "id":
199             return self.driver(resourceId=element).get_text()
200         elif type == "selected":
201             return self.driver(selected=True).get_text()
202         else:
203             pass
204 
205 
206     # 滑动  (正常屏幕滑动,向上滑动解锁,返回主界面,解锁等通用)
207     # 坐标支持数据类型:Union[int, str]
208     def swipe_extend(self, x1=0.5, y1=0.99, x2=0.5, y2=0.3, dur: Union[int, str] = 0.2,
209                      sleepTime=0, type: str = "percent"):
210         if type == "percent":
211             size = self.driver.window_size()
212             x1 = int(size[0] * x1)
213             y1 = int(size[1] * y1)
214             x2 = int(size[0] * x2)
215             y2 = int(size[1] * y2)
216             self.driver.swipe(x1, y1, x2, y2, dur)
217             time.sleep(sleepTime)
218         else:
219             self.driver.swipe(x1, y1, x2, y2, dur)
220             time.sleep(sleepTime)
221 
222 
223     # 按下之后滑动,长按滑动
224     def long_click_swipe(self, x1, y1, x2, y2, dur=0.8, sleepTime=0):
225         self.driver.touch.down(x1, y1).sleep(dur).move(x1, y1).move(x2, y2).up(x2, y2)
226         time.sleep(sleepTime)
227 
228 
229     # 向上滑动解锁,返回主界面,解锁用
230     def swipe_up(self, time=0.2):
231         size = self.driver.window_size()
232         x1 = int(size[0] * 0.5)
233         y1 = int(size[1] * 1)
234         y2 = int(size[1] * 0.3)
235         self.driver.swipe(x1, y1, x1, y2, time)
236 
237 
238     # 滑动,根据方向滑动
239     def swipe_ext_extend(self, direction: Union[SwipeDirection, str] = "up", scale=0.9, sleepTime=0):
240         self.driver.swipe_ext(direction, scale=scale)
241         time.sleep(sleepTime)
242 
243 
244     # 缩放
245     def pinch_extend(self, element, kind: str = "out or in", percent=100, steps=50):
246         """ 在元素上面,做两个手指缩放的操作,kind in 或者out,放大或者缩小"""
247         zhmodel = re.compile(u'[\u4e00-\u9fa5]')
248         if str(element).startswith("com"):
249             selector = self.driver(resourceId=element)
250         elif not zhmodel.search(str(element)):
251             selector = self.driver(className=element)
252         elif zhmodel.search(str(element)):  # 若以上两种情况都不是,则使用描述定位
253             selector = self.driver(description=element)
254 
255         if kind == "in":
256             selector.pinch_in(percent, steps)
257         elif kind == "out":
258             selector.pinch_out(percent, steps)
259         else:
260             raise Exception("输入kind不能是非in/out")
261 
262 
263     # 关机
264     def reboot_physical_key(self):
265         self.driver.shell("sendevent /dev/input/event0 1 116 1")
266         self.driver.shell("sendevent /dev/input/event0 0 0 0")
267         time.sleep(3)
268         self.driver.shell("sendevent /dev/input/event0 1 116 0")
269         self.driver.shell("sendevent /dev/input/event0 0 0 0")
270         time.sleep(1)
271         self.click_by_text("关闭电源")
272 
273 
274     #   截图
275     def screenshot_physical_key(self):
276         self.driver.shell("sendevent /dev/input/event0 1 114 1")
277         self.driver.shell("sendevent /dev/input/event0 0 0 0")
278         self.driver.shell("sendevent /dev/input/event0 1 116 1")
279         self.driver.shell("sendevent /dev/input/event0 0 0 0")
280         self.driver.shell("sendevent /dev/input/event0 1 116 0")
281         self.driver.shell("sendevent /dev/input/event0 0 0 0")
282         self.driver.shell("sendevent /dev/input/event0 1 114 0")
283         self.driver.shell("sendevent /dev/input/event0 0 0 0")
284 
285 
286     # 推送文件到手机
287     def push_extend(self, root: Union[list, str], target, sleepTime=1):
288         peojectPath = "\\".join(os.path.abspath(os.path.dirname(__file__)).split("\\")[:-1])
289         if isinstance(root, list):
290             for i in root:
291                 self.driver.push(peojectPath+i, target)
292         elif isinstance(root, str):
293             self.driver.push(root, target)
294         time.sleep(sleepTime)
295 
296 
297     def randmon_phone(self):
298         """ 随机生成一个手机号,或者其他想生成的数据 """
299         while True:
300             phone = "AAAAA新建"
301             for i in range(8):
302                 num = random.randint(0, 9)
303                 phone += str(num)
304             return phone
305 
306 
307     def power(self, kind: str='power or kill'):
308         '''模拟power键'''
309         if kind == 'power':
310             self.driver.screen_on()  # 息屏
311         elif kind == 'kill':
312             self.driver.screen_off()  # 亮屏
313         else:
314             raise Exception("输入kind有误")
315 
316 
317     def virtual_key(self,kind: str = "home or delete or up or down or volume_up or volume_down or volume_mute or power or back"):
318         """ 常用虚拟按键 """
319         if kind == "home":
320             self.driver.press("home")           # 点击home键
321         elif kind == "delete":
322             self.driver.press("delete")         # 点击删除键
323         elif kind == "up":
324             self.driver.press("up")             # 点击上键
325         elif kind == "down":
326             self.driver.press("down")           # 点击下键
327         elif kind == "volume_up":
328             self.driver.press("volume_up")      # 点击音量+
329         elif kind == "volume_down":
330             self.driver.press("volume_down")    # 点击音量-
331         elif kind == "volume_mute":
332             self.driver.press("volume_mute")    # 点击静音
333         elif kind == "power":
334             self.driver.press("power")          # 点击电源键
335         elif kind == "back":
336             self.driver.press("back")           # 点击返回键
337         else:
338             raise Exception("输入kind有误")

 

 

以上为个人常用公共方法封装,但不是全部,有些场景可能未覆盖到。更多的 ui2 相关知识可自行网上学习。

随机推荐几个ui2相关的帖子,更多的ui2的相关知识自行网上搜索了解。

https://blog.csdn.net/Makasa/article/details/124358921

https://ceshiren.com/t/topic/5396

https://blog.csdn.net/weixin_43444734/article/details/124703281

 

更新几个uiautomaor2公共方法的封装

    def screen_off(self):
        '''虚拟按键 # 息屏'''
        self.driver.screen_off()

    def screen_on(self):
        """  虚拟按键  # 亮屏"""
        self.driver.screen_on()

    def press_key(self, key,sleepTime=2):
        """虚拟按键的公共方法"""
        self.driver.press(key)
        time.sleep(sleepTime)

    # 使用示例
    # 假设你有一个driver对象,可以是任何支持press方法的对象
    # driver = YourDriverObject()
    # press_key(driver, "recent")  # 模拟最近键
    # press_key(driver, "home")   # 模拟Home键
    # press_key(driver, "menu")   # 模拟菜单键


    def press_key_code(self,key_code,sleepTime=2):
        """  模拟虚拟按键,与上面的几个方法大同小异"""
        try:
            # 使用adb shell input keyevent命令模拟按键事件
            subprocess.check_call(['adb', 'shell', 'input', 'keyevent', str(key_code)])
            print(f"按键 {key_code} 被模拟点击")
        except subprocess.CalledProcessError as e:
            print("执行失败:", e)
        except Exception as e:
            print("发生错误:", e)
        time.sleep(sleepTime)

    # # 示例:模拟点击Home键,Home键的键值通常是3
    # press_key(3)
    # # 示例:模拟点击菜单键,菜单键的键值通常是82
    # press_key(82)
    # 更多按键对应数字请参考:https://www.cnblogs.com/gancuimian/p/17405701.html



    def run_adb_command(self,command,sleepTime=0):
        """
        执行 ADB 命令的封装方法
        :param command: 占位符,调用方法时输入adb命令
        :param sleepTime: 休眠时间
        """
        try:
            # 使用shell=True来直接执行字符串形式的命令
            subprocess.check_call(command, shell=True)
            log.info("adb命令执行成功")
        except subprocess.CalledProcessError as e:
            log.error(f"adb命令执行失败{e}")
        time.sleep(sleepTime)

        # 示例:
        # enable_driving_mode("adb shell xxxxx")



    def get_toast_text(self, timeout=2):
        """
        :param timeout:等待Toast弹窗显示的最大时间(秒)
        :return: Toast弹窗的文本,如果没有找到则返回default
        """
        try:
            start_time = time.time()
            while time.time() - start_time < timeout:
                # 尝试获取Toast弹窗的文本
                toast_msg = self.driver.toast.get_message()
                # 短暂等待后再次检查
                time.sleep(0.3)
                log.info(f"获取到toast弹窗内容:{toast_msg}")
                return toast_msg
        except Exception as e:
            print(f"获取Toast弹窗时发生错误:{e}")
        return None

    # 使用示例
    # toast_values = self.get_toast_text()


    # 截图的封装,
    def get_file_path(self,dir_name, filename):
        """Construct the full path for the screenshot file with .png extension."""
        # Ensure the file name has a .png extension
        if not filename.lower().endswith('.png'):
            filename += '.png'
        return os.path.join(dir_name, filename)

    # 截图的封装,保存在screenshots目录下,并在allure报告中展示截图
    def take_and_attach_screenshot(self, step_title, file_name, dir_name='screenshots'):
        """Take a screenshot and attach it to the Allure report."""
        # Ensure the screenshots directory exists
        if not os.path.exists(dir_name):
            os.makedirs(dir_name)

        file_path = self.get_file_path(dir_name, file_name)
        self.driver.screenshot(file_path)  # 使用uiautomator2的screenshot方法

        with allure.step(step_title):
            with open(file_path, 'rb') as file:  # 以二进制模式打开文件,读取内容
                file_content = file.read()
                allure.attach(file_content, name=step_title, attachment_type=allure.attachment_type.PNG)

 

posted @ 2022-12-05 14:28  干脆面666  Views(2094)  Comments(0Edit  收藏  举报
/* 看板娘 */ /* 点击鼠标烟花效果 */