python + uiautomator2 常用公共方法封裝
前言
由於公司UI自動化框架底層用的是Uiautomator2,所以我就用Uiautomator2搭了一套UI自動化框架,思路其實和Appnium一樣的。
uiautomator2是一個自動化測試開源工具,僅支援android平臺的自動化測試,其封裝了谷歌自帶的uiautomator2測試框架;
u2 現在google 官方使用的是apk的形式來實現的,有大神封裝了python來實現u2的功能的使用。
具體的瞭解相關的功能和實現的原理可以檢視開源庫:github 的地址:https://github.com/openatx/uiautomator2
ui2 的下載安裝與環境配置等,見之前寫的一篇帖子:
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 class BasePage(): 2 """ 建構函式 """ 3 def __init__(self, driver): 4 self.driver = driver 5 6 def click(self, element, sleepTime=0): 7 """ 點選 """ 8 if str(element).startswith("com"): # 若開頭是com則使用ID定位 9 self.driver(resourceId=element).click() # 點選定位元素 10 elif re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 11 self.driver.xpath(element).click() # 點選定位元素 12 else: # 若以上兩種情況都不是,則使用描述定位 13 self.driver(description=element).click() # 點選定位元素 14 time.sleep(sleepTime) 15 16 # 點選直到元素消失 17 def click_until_gone(self, element, kind): 18 if kind == "id": 19 self.driver(resourceId=element).click_gone() 20 elif kind == "class": 21 self.driver(className=element).click_gone() 22 elif kind == "text": 23 self.driver(text=element).click_gone() 24 else: # 若以上兩種情況都不是,則使用描述定位 25 self.driver(description=element).click_gone() # 點選定位元素 26 27 # 組合定位classname和text 28 def click_by_classname_text(self, element1, element2): 29 self.driver(className=element1, text=element2).click() 30 31 # 組合定位classname和description 32 def click_by_classname_description(self, element1, element2): 33 self.driver(className=element1, description=element2).click() 34 35 # 組合定位text和description 36 def click_by_text_description(self, element1, element2): 37 self.driver(text=element1, description=element2).click() 38 39 40 41 # 根據id點選(包括非com開頭的id點選定位元素) 42 def click_by_id(self, element, sleepTime=0): 43 self.driver(resourceId=element).click() 44 time.sleep(sleepTime) 45 46 # 根據文字點選 47 def click_by_text(self, element, sleepTime=0): 48 self.driver(text=element).click() # 點選定位元素 49 time.sleep(sleepTime) 50 51 # 根據百分比座標點選 52 def click_by_zuobiao(self, x, y, sleepTime=0): 53 size = self.driver.window_size() 54 self.driver.click(int(size[0] * x), int(size[1] * y)) 55 time.sleep(sleepTime) 56 57 # 根據座標點選元素 58 def click_coord(self, x, y): 59 self.driver.click(x, y) 60 61 # 根據座標雙擊元素 62 def double_click_by_zuobiao(self, x, y, sleepTime=0): 63 size = self.driver.window_size() 64 self.driver.double_click(int(size[0] * x), int(size[1] * y)) 65 time.sleep(sleepTime) 66 67 # 超時時間設定點選,根據文字定位,針對點選螢幕元素反應慢的元素 68 def click_by_text_time_out(self, element, timeout=30, sleepTime=0): 69 self.driver(text=element).click(timeout=timeout) 70 time.sleep(sleepTime) 71 72 # 長按 73 def long_click_extend(self, element, dur=1, sleepTime=0): 74 zhmodel = re.compile(u'[\u4e00-\u9fa5]') 75 if str(element).startswith("com"): # 若開頭是com則使用ID定位 76 self.driver(resourceId=element).long_click(dur) # 點選定位元素 77 elif re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 78 self.driver.xpath(element).long_click() # 點選定位元素 79 elif zhmodel.search(str(element)): 80 self.driver(description=element).long_click(dur) 81 else: 82 self.driver(className=element).long_click(dur) 83 time.sleep(sleepTime) 84 85 # 通過文字長按 86 def long_click_by_text(self, element, sleepTime=0): 87 self.driver(text=element).long_click() 88 time.sleep(sleepTime) 89 90 # 通過座標長按 91 def long_click_by_zuobiao(self, x, y, sleepTime=0,duration: float = .5): 92 self.driver.long_click(x, y,duration) 93 time.sleep(sleepTime) 94 95 # 清空輸入框中的內容 96 def clear(self, element): 97 if str(element).startswith("com"): # 若開頭是com則使用ID定位 98 self.driver(resourceId=element).clear_text() # 清除文字 99 elif re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 100 self.driver.xpath(element).clear_text() # 清除文字 101 else: # 若以上兩種情況都不是,則使用描述定位 102 self.driver(description=element).clear_text() # 清除文字 103 104 # 輸入 105 def input(self, element, value, sleepTime=0): 106 if str(element).startswith("com"): # 若開頭是com則使用ID定位 107 self.driver(resourceId=element).set_text(value) # send_keys 108 elif re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 109 self.driver.xpath(element).set_text(value) 110 else: # 若以上兩種情況都不是,則使用描述定位 111 self.driver(description=element).set_text(value) 112 time.sleep(sleepTime) 113 114 # 不存在搜尋按鈕的搜尋 115 def input_by_send_keys(self, element, value): 116 self.driver.set_fastinput_ime(True) # 切換成FastInputIME輸入法 117 if str(element).startswith("com"): # 若開頭是com則使用ID定位 118 self.driver(resourceId=element).send_keys(value) # send_keys 119 elif re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 120 self.driver.xpath(element).send_text(value) 121 else: # 若以上兩種情況都不是,則使用描述定位 122 self.driver(description=element).send_keys(value) 123 self.driver.set_fastinput_ime(False) # 切換成正常的輸入法 124 self.driver.send_action("search") # 模擬輸入法的搜尋 125 126 # 查詢元素 127 def find_elements(self, element, timeout=5): # 找元素 128 is_exited = False 129 try: 130 while timeout > 0: 131 xml = self.driver.dump_hierarchy() # 獲取網頁層次結構 132 if re.findall(element, xml): 133 is_exited = True 134 break 135 else: 136 timeout -= 1 137 except: 138 print("元素未找到!") 139 finally: 140 return is_exited 141 142 # 斷言元素是否存在, 不能判定xpath元素 143 def assert_existed(self, element): # 144 # assert self.find_elements(element) == True, "斷言失敗,{}元素不存在!".format(element) 145 return self.find_elements(element) == True 146 147 # 判斷元素是否存在(比如隨機彈窗等) 148 def judge_existed(self, element, type: str = "text", timeout=2): 149 if re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 150 return self.driver.xpath(element).exists == True 151 elif type == "text": 152 return self.driver(text=element).exists(timeout=timeout) == True 153 elif type == "dec": 154 return self.driver(description=element).exists(timeout=timeout) == True 155 else: 156 pass 157 158 # 截圖 159 def screenshot(self, imageName): 160 if os.path.exists(r"./images"): 161 if os.path.exists(fr"./images/{imageName}.png"): 162 image = self.driver.screenshot() 163 image.save(fr"./images/{imageName}_bak.png") 164 else: 165 image = self.driver.screenshot() 166 image.save(fr"./images/{imageName}.png") 167 else: 168 os.mkdir(r"./images") 169 image = self.driver.screenshot() 170 image.save(fr"./images/{imageName}.png") 171 172 173 # 拿取文字 174 def get_text_extend(self, element=None, type: str = "id"): 175 176 if re.findall("//", str(element)): # 若//開頭則使用正則表示式匹配後用xpath定位 177 return self.driver.xpath(element).get_text() 178 elif type == "id": 179 return self.driver(resourceId=element).get_text() 180 elif type == "selected": 181 return self.driver(selected=True).get_text() 182 else: 183 pass 184 185 186 # 滑動 (正常螢幕滑動,向上滑動解鎖,返回主介面,解鎖等通用) 187 # 座標支援資料型別:Union[int, str] 188 def swipe_extend(self, x1=0.5, y1=0.99, x2=0.5, y2=0.3, dur: Union[int, str] = 0.2, 189 sleepTime=0, type: str = "percent"): 190 if type == "percent": 191 size = self.driver.window_size() 192 x1 = int(size[0] * x1) 193 y1 = int(size[1] * y1) 194 x2 = int(size[0] * x2) 195 y2 = int(size[1] * y2) 196 self.driver.swipe(x1, y1, x2, y2, dur) 197 time.sleep(sleepTime) 198 else: 199 self.driver.swipe(x1, y1, x2, y2, dur) 200 time.sleep(sleepTime) 201 202 # 按下之後滑動,長按滑動 203 def long_click_swipe(self, x1, y1, x2, y2, dur=0.8, sleepTime=0): 204 self.driver.touch.down(x1, y1).sleep(dur).move(x1, y1).move(x2, y2).up(x2, y2) 205 time.sleep(sleepTime) 206 207 # 滑動,根據方向滑動 208 def swipe_ext_extend(self, direction: Union[SwipeDirection, str] = "up", scale=0.9, sleepTime=0): 209 self.driver.swipe_ext(direction, scale=scale) 210 time.sleep(sleepTime) 211 212 # 縮放 213 def pinch_extend(self, element, kind: str = "out or in", percent=100, steps=50): 214 """ 在元素上面,做兩個手指縮放的操作,kind in 或者out,放大或者縮小""" 215 zhmodel = re.compile(u'[\u4e00-\u9fa5]') 216 if str(element).startswith("com"): 217 selector = self.driver(resourceId=element) 218 elif not zhmodel.search(str(element)): 219 selector = self.driver(className=element) 220 elif zhmodel.search(str(element)): # 若以上兩種情況都不是,則使用描述定位 221 selector = self.driver(description=element) 222 223 if kind == "in": 224 selector.pinch_in(percent, steps) 225 elif kind == "out": 226 selector.pinch_out(percent, steps) 227 else: 228 raise Exception("輸入kind不能是非in/out") 229 230 # 關機 231 def reboot_physical_key(self): 232 self.driver.shell("sendevent /dev/input/event0 1 116 1") 233 self.driver.shell("sendevent /dev/input/event0 0 0 0") 234 time.sleep(3) 235 self.driver.shell("sendevent /dev/input/event0 1 116 0") 236 self.driver.shell("sendevent /dev/input/event0 0 0 0") 237 time.sleep(1) 238 self.click_by_text("關閉電源") 239 240 # 截圖。用命令 模擬安卓物理按鍵事件(需要手機有root許可權) 241 def screenshot_physical_key(self): 242 self.driver.shell("sendevent /dev/input/event0 1 114 1") 243 self.driver.shell("sendevent /dev/input/event0 0 0 0") 244 self.driver.shell("sendevent /dev/input/event0 1 116 1") 245 self.driver.shell("sendevent /dev/input/event0 0 0 0") 246 self.driver.shell("sendevent /dev/input/event0 1 116 0") 247 self.driver.shell("sendevent /dev/input/event0 0 0 0") 248 self.driver.shell("sendevent /dev/input/event0 1 114 0") 249 self.driver.shell("sendevent /dev/input/event0 0 0 0") 250 251 # 推本地檔案到手機 252 def push_extend(self, root: Union[list, str], target, sleepTime=1): 253 peojectPath = "\\".join(os.path.abspath(os.path.dirname(__file__)).split("\\")[:-1]) 254 if isinstance(root, list): 255 for i in root: 256 self.driver.push(peojectPath+i, target) 257 elif isinstance(root, str): 258 self.driver.push(root, target) 259 time.sleep(sleepTime) 260 261 262 def randmon_phone(self): 263 """ 隨機生成一個手機號,或者其他想生成的資料 """ 264 while True: 265 phone = "130" 266 for i in range(8): 267 num = random.randint(0, 9) 268 phone += str(num) 269 return phone
以上為個人常用公共方法封裝,但不是全部,有些場景可能未覆蓋到。更多的 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