基於uiautomator2的UI自動化實踐過程中,遇到過不少問題,后來看主要還是缺少對源碼的閱讀,導致理解不深,部分API不清楚如何調用,故在此記錄。
---->此處內容非原創,侵刪。
手勢交互
點擊屏幕(坐標,支持相對坐標)
d.click(x, y)
d.click(0.5, 0.5) #其中(0.235, 0.456) 代表 X(50%) Y(50%)
雙擊
d.double_click(x, y) # 默認兩次點擊相隔0.1秒
d.double_click(x, y, 1) # 兩次點擊相隔1秒
長按
d.long_click(x, y) # 默認按0.5秒
d.long_click(x, y, 5) # 長按5秒
滑動(滑動屏幕)
d.swipe(sx, sy, ex, ey) # 從坐標(sx,sy)滑動至坐標(ex, ey),支持相對坐標
d.swipe(sx, sy, ex, ey, 0.5) # 默認滑動時間0.5秒,可修改
SwipeExt 擴展功能
d.swipe_ext("right") # 屏幕右滑,4選1 - "left", "right", "up", "down"
d.swipe_ext("right", scale=0.9) # 默認0.9, 滑動距離為屏幕寬度的90%
d.swipe_ext("right", box=(0, 0, 100, 100)) # 在 (0,0) -> (100, 100) 這個區域做滑動
拖動(例:拖動某個APP)
d.drag(sx, sy, ex, ey) #支持相對坐標
d.drag(sx, sy, ex, ey, 0.5) # 默認滑動時間0.5秒,可修改
多個點連續滑動(典型案例:9宮格解鎖)
從point(x0, y0) 滑動到point(x1, y1)然后滑動到point(x2, y2)...等等
d.swipe_points([(x0, y0), (x1, y1), (x2, y2)], 0.2)) # 0.2為每次兩點之間滑動的時間,坐標可以是絕對坐標,也可以是相對坐標
d.swipe_points([(0.235, 0.456), (0.503, 0.449), (0.509, 0.601), (0.777, 0.603), (0.771, 0.763), (0.222, 0.75)], 0.2) # 其中(0.235, 0.456) 代表 X(23.5%) Y(45.6%)
模擬按下 - 等待 - 移動 - 松開
d.touch.down(10, 10) # 模擬按下
time.sleep(0.01) # 控制延遲
d.touch.move(15, 15) # 模擬移動
d.touch.up() # 模擬抬起
截圖
d.screenshot(path) # 傳入路徑和名稱
元素選擇器
# 定位text為'Clock'並且className為'android.widget.TextView'的元素
d(text='Clock', className='android.widget.TextView')
后代元素
#定位className為"android.widget.ListView"的元素下面text為"Bluetooth"的元素
d(className="android.widget.ListView").child(text="Bluetooth")
兄弟姐妹,同級元素
#定位text為"Google"的元素的同級元素中className="android.widget.ImageView"的元素
d(text="Google").sibling(className="android.widget.ImageView")
通過多個層級多個屬性定位
#className="android.widget.ListView"且resourceId="android:id/list"的元素下面text為"Bluetooth"且className="android.widget.LinearLayout"的元素
d(className="android.widget.ListView", resourceId="android:id/list").child_by_text("Bluetooth", className="android.widget.LinearLayout")
可以由多個層級往下定位,例:
d(className="###", resourceId="###").child_by_text("Wi-Fi", className="¥¥¥¥").child(className="****").click()
相對定位
d(A).left(B), 定位A左邊的B
d(A).right(B), 定位A右邊的B
d(A).up(B), 定位A上邊的B
d(A).down(B), 定位A下邊的B
多個同屬性元素索引
如果元素選擇器選擇到了多個同屬性的元素而無法進行更細致的區分,可以用索引選擇指定的元素
d(text="Add new", instance=0) #instance=0表示選擇第一個,依次類推
其他操作:
d(text="Add new").count #返回當前屏幕某個屬性元素的個數
len(d(text="Add new")) #返回當前屏幕某個屬性元素列表的長度
d(text="Add new")[0] #用下標選擇指定元素,同d(text="Add new", instance=0)
迭代:
for view in d(text="Add new"):
view.info
獲取所選ui對象的狀態及其信息
1. 檢查元素是否存在
d(text="Settings").exists # 返回布爾值,True if exists, else False,用戶if判斷時,不可使用exist()
d(text="Settings").exists(timeout=3) #增加等待時間為3秒
2. 輸出指定元素的信息
d(text="Settings").info
3. 獲取、輸入、清除輸入框文本
d(text="Settings").get_text() # 獲取文本內容
d(text="Settings").set_text("My text...") #輸入文本內容
d(text="Settings").clear_text() # 清除文本內容
4. 獲取指定元素中心點坐標
x, y = d(text="Settings").center()
x, y = d(text="Settings").center(offset=(0, 0)) # offset為指定元素的相對坐標,(0,0)表示元素左上角,(0.5,0.5)表示元素中心,(1,1)表示元素右下角
5. 指定元素點擊操作
d(text="Settings").click() #點擊指定元素中心位置
d(text="Settings").click(timeout=10) #等待元素出現(最多等待10秒)后點擊
d(text="Settings").click(offset=(0.5, 0.5)) # offset為指定元素的相對坐標,(0,0)表示元素左上角,(0.5,0.5)表示元素中心,(1,1)表示元素右下角
clicked = d(text='Skip').click_exists(timeout=10.0) #如果10秒內元素存在,則點擊,默認等待10秒
is_gone = d(text="Skip").click_gone(maxretry=10, interval=1.0) #等待元素消失后點擊,返回布爾值,默認輪詢次數10次,每次間隔時間1秒
d(text="Settings").long_click() #長按指定元素
特定元素的手勢操作
1. 將元素拖向另一個點或另一個元素
備注:安卓4.3以下不能用
d(text="Settings").drag_to(x, y, duration=0.5) # 將指定元素在0.5秒的時間內拖動至指定坐標
d(text="Settings").drag_to(text="Clock", duration=0.25) # 將指定元素在0.25秒的時間內拖動至指定元素的中心位置
2. 等待元素出現或消失
d(text="Settings").wait(timeout=3.0) # 等待元素出現,等待時間最長3秒,返回布爾值,默認等待時間20秒
d(text="Settings").wait_gone(timeout=1.0) # 等待元素消失,等待時間最長1秒,返回布爾值,默認等待時間20秒
3、滾動屏幕
scroll()里面的參數steps默認是滑動一個屏幕的距離
a.向上滾動:d(scrollable=True).scroll(steps=10)
b.向下滑動:d(scrollable=True).scroll.vert.backward()
c.水平向右滾動:d(scrollable=True).scroll.horiz.forward(steps=50)
d.水平向左滾動:d(scrollable=True).scroll.horiz.backward(steps=50)
e.水平滑動到最左邊:d(scrollable=True).scroll.horiz.toBeginning(steps=100, max_swipes=1000)
f.水平滑動到最右邊:d(scrollable=True).scroll.horiz.toEnd(steps=100, max_swipes=1000)
g.豎直滑動到結尾:d(scrollable=True).scroll.toEnd()
h.豎直滑動到開頭:d(scrollable=True).scroll.toBeginning(steps=50)
i.滑動到指定位置:d(scrollable=True).scroll.to(text="Security")
全局設置
# 設置每次UI點擊后1.5秒的延遲
d.click_post_delay = 1.5 # default no delay
# 設置默認元素等待超時(秒)
d.wait_timeout = 30.0 # default 20.0
設置元素查找等待時間(默認20s)
d.implicitly_wait(10.0)
輸入內容
d.send_keys('str') # 不好用,存在問題
Toast
1、顯示toast
d.toast.show("Hello world", 1.0) # show for 1.0s, default 1.0s
2、獲取toast
# 5.0: 最大等待時間
# 10.0: toast出現后的緩存時間. 默認 10.0
# "default message": 返回的toast值. Default None
d.toast.get_message(5.0, 10.0, "default message")
# 一般用法
assert "Short message" in d.toast.get_message(5.0, default="")
# 清除toast緩存
d.toast.reset()
xpath
description -> content-desc(xpath)
resourceId -> resource-id(xpath)
# 等待元素存在(等待10秒)
d.xpath("//android.widget.TextView").wait(10.0) # return bool
# 找到元素並點擊
d.xpath("//*[@content-desc='分享']").click()
# 檢查是否存在
if d.xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
print("exists")
# 獲取所有輸入框的text,屬性和中心點坐標
for elem in d.xpath("//android.widget.TextView").all():
print("Text:", elem.text)
# Dictionary eg
#{'index': '1', 'text': '999+', 'resource-id': 'com.netease.cloudmusic:id/qb', 'package': 'com.netease.cloudmusic', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'false','enabled': 'true', 'focusable': 'false', 'focused': 'false','scrollable': 'false','long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[661,1444][718,1478]'}'''
print("Attrib:", elem.attrib)
# Coordinate eg: (100, 200)
print("Position:", elem.center())
部分實例
def add_picture_to_report(filepath):
filebytes = ''
with open(filepath, 'rb') as f:
filebytes = f.read()
allure.attach(filebytes, '運行截圖', attachment_type=allure.attachment_type.PNG)
def d_screenshot(self, screenshot_name):
""" 截圖 """
screenshot_time = time.strftime(TIMEFORMAT, time.localtime())
filename = '%s_%s_%s_.png' % (screenshot_time, project_conf.PROJECT_SN, screenshot_name)
logging.info('current picture name is %s' % filename)
filepath = os.path.join(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'Screenshot'), 'AiHome')
if not os.path.exists(filepath):
os.makedirs(filepath)
picture_file = os.path.join(filepath, filename)
try:
self.d.screenshot(picture_file)
add_picture_to_report(picture_file)
logging.info('screenshot success')
except ScreenshotError:
logging.exception('screenshot failed')