> 本篇更偏向于源碼解析笑撞,適用于對airtest有一些了解,看過入門教程藕畔,寫過demo的童鞋,當(dāng)然初學(xué)者也可以在本章的上手環(huán)節(jié)跳轉(zhuǎn)到網(wǎng)易官方最快5分鐘教程中學(xué)習(xí)录语,因為我覺得那篇教程已經(jīng)夠好了倍啥,就不多寫入門教程了。
簡介
Airtest Project是最近非撑觳海火的一個ui自動化測試工具虽缕,由網(wǎng)易游戲內(nèi)部工具團隊開發(fā)并開源,獲得谷歌力挺蒲稳。
AirtestIDE 是一個跨平臺氮趋、多端(Windows、web江耀、android剩胁、ios、游戲)的UI自動化測試編輯器祥国。
自動化腳本錄制昵观、一鍵回放、報告查看舌稀,輕而易舉實現(xiàn)自動化測試流程索昂,自有編輯器一站式解決
支持基于圖像識別的Airtest框架,適用于所有Android和Windows游戲扩借,會截圖就能寫腳本
支持基于UI控件搜索的Poco框架,適用于Unity3d缤至,Cocos2d與Android潮罪、ios App、web
能夠運行在Windows和MacOS上
網(wǎng)易內(nèi)部已成功應(yīng)用在數(shù)十個項目上领斥,利用手機集群進行大規(guī)模自動化測試嫉到,手機集群沒有開源,準(zhǔn)備做收費模式吧
使用python編寫月洛,兼容2何恶、3,盡量用3吧
上手
官方教程嚼黔,有演示視頻细层,有動圖,一目了然唬涧。環(huán)境搭建也相當(dāng)簡單疫赎,基本上安裝好IDE就可以了。
AirTest IDE提供了一站式功能:腳本開發(fā)(錄制碎节、編輯)捧搞、設(shè)備管理、運行、回放胎撇、結(jié)果查看
相信通過網(wǎng)易的這個上手教程介粘,很多人都能很快就可以把airtest玩起來了。
當(dāng)我們跟隨著教程寫好一條腳本晚树,運行起來以后姻采,一起來看看AirTest的大致框架。
首先在AirTest的定義中腳本文件名的后綴是.air题涨,當(dāng)我們在IDE中新建一個腳本文件
再來到文件管理中我們可以看到這是一個文件夾偎谁。
這里面有一個跟air腳本同名的py文件,其他的png圖片就是在IDE里截圖纲堵,錄制巡雨,生成的圖像文件。
打開這個py文件來看看:
可以看出在IDE里顯示的touch(圖片)席函,就是在api里的一個touch接口铐望,里面?zhèn)魅胍粋€Template,這個對象包含了圖片文件的名稱茂附、錄制時的相對坐標(biāo)(record_pos)正蛙,分辨率(resolution)等,當(dāng)然還有其他參數(shù):目標(biāo)位置(target_pos)营曼、rgb匹配(rgb)乒验,如果你在IDE里雙擊圖片就會彈出窗口設(shè)置這些詳細(xì)參數(shù)。
我想圖像識別大概就是這樣了:寫腳本時截下目標(biāo)圖片(你想要點擊的地方)蒂阱,這圖片就跟python腳本保存在一起锻全,touch接口傳入這些目標(biāo)圖片,進行匹配录煤,成功后點擊目標(biāo)圖片的位置鳄厌,有興趣的話繼續(xù)來看看這個touch接口的源碼。
```
@logwrap
def touch(v, times=1, **kwargs):
? ? """
? ? Perform the touch action on the device screen
? ? :param v: target to touch, either a Template instance or absolute coordinates (x, y)
? ? :param times: how many touches to be performed
? ? :param kwargs: platform specific `kwargs`, please refer to corresponding docs
? ? :return: finial position to be clicked
? ? :platforms: Android, Windows, iOS
? ? """
? ? if isinstance(v, Template):
? ? ? ? pos = loop_find(v, timeout=ST.FIND_TIMEOUT)
? ? else:
? ? ? ? try_log_screen()
? ? ? ? pos = v
? ? for _ in range(times):
? ? ? ? G.DEVICE.touch(pos, **kwargs)
? ? ? ? time.sleep(0.05)
? ? delay_after_operation()
? ? return pos
```
入?yún)ⅲ?/p>
v妈踊,可以是Template對象(目標(biāo)截圖)了嚎,或者是pos(坐標(biāo))
times,點擊次數(shù)廊营,默認(rèn)為1
kwargs歪泳,平臺的特殊參數(shù)
loop_find(v, timeout=ST.FIND_TIMEOUT)#通過名字大概知道,循環(huán)查找這個v赘风,有個超時退出午衰,返回坐標(biāo)點
G.DEVICE.touch(pos, **kwargs)#點擊設(shè)備的指定坐標(biāo)點
G.DEVICE應(yīng)該就是一個當(dāng)前的設(shè)備续语,兼容android土榴、ios锦积、windows
delay_after_operation#最后點擊完以后還等待一下假哎,所以這里可以配置每步點擊的等待時間
所以touch接口的邏輯是:
如傳入圖片信息,循環(huán)查找匹配出目標(biāo)圖片所在屏幕的坐標(biāo)點鞍历;
傳入是坐標(biāo)舵抹,開始記錄log信息;
循環(huán)點擊指定的坐標(biāo)點劣砍;
等待惧蛹,然后返回目標(biāo)坐標(biāo)點。
再往下刑枝,看一下loop_find這個接口香嗓,我想這就是“圖像識別”的“核心”部分了,哈哈
```
@logwrap
def loop_find(query, timeout=ST.FIND_TIMEOUT, threshold=None, interval=0.5, intervalfunc=None):
? ? """
? ? Search for image template in the screen until timeout
? ? Args:
? ? ? ? query: image template to be found in screenshot
? ? ? ? timeout: time interval how long to look for the image template
? ? ? ? threshold: default is None
? ? ? ? interval: sleep interval before next attempt to find the image template
? ? ? ? intervalfunc: function that is executed after unsuccessful attempt to find the image template
? ? Raises:
? ? ? ? TargetNotFoundError: when image template is not found in screenshot
? ? Returns:
? ? ? ? TargetNotFoundError if image template not found, otherwise returns the position where the image template has
? ? ? ? been found in screenshot
? ? """
? ? G.LOGGING.info("Try finding:\n%s", query)
? ? start_time = time.time()
? ? while True:
? ? ? ? screen = G.DEVICE.snapshot(filename=None)
? ? ? ? if screen is None:
? ? ? ? ? ? G.LOGGING.warning("Screen is None, may be locked")
? ? ? ? else:
? ? ? ? ? ? if threshold:
? ? ? ? ? ? ? ? query.threshold = threshold
? ? ? ? ? ? match_pos = query.match_in(screen)
? ? ? ? ? ? if match_pos:
? ? ? ? ? ? ? ? try_log_screen(screen)
? ? ? ? ? ? ? ? return match_pos
? ? ? ? if intervalfunc is not None:
? ? ? ? ? ? intervalfunc()
? ? ? ? # 超時則raise装畅,未超時則進行下次循環(huán):
? ? ? ? if (time.time() - start_time) > timeout:
? ? ? ? ? ? try_log_screen(screen)
? ? ? ? ? ? raise TargetNotFoundError('Picture %s not found in screen' % query)
? ? ? ? else:
? ? ? ? ? ? time.sleep(interval)
```
入?yún)ⅲ?/p>
query:要在截圖中查找的圖片模板(也就是我們寫腳本截的圖咯)
timeout:最大匹配時間
threshold:默認(rèn)是None靠娱,字面意思是閾值,也就是匹配時的相似度吧掠兄,調(diào)低點可以更容易匹配上像云,也更容易匹配錯
interval:循環(huán)匹配的間隔時間,每次要對設(shè)備截圖傳入進來匹配蚂夕,中間的等待時間
intervalfunc:傳入一個方法迅诬,在匹配失敗時調(diào)用,也就是可以在接口的外部自定義匹配失敗后的動作
返參:pos:目標(biāo)圖片在設(shè)備屏幕中的位置
screen = G.DEVICE.snapshot(filename=None)#設(shè)備截圖婿牍,所以運行完腳本以后工程路徑會有很多個截圖文件侈贷,就是這里產(chǎn)生的。
match_pos = query.match_in(screen)#在設(shè)備截圖中匹配查找我們傳入的目標(biāo)圖片
所以這loop_find的邏輯就是:一個循環(huán)等脂,從設(shè)備中截取屏幕的圖片铐维,在屏幕圖片上查找匹配我們的目標(biāo)圖片,匹配成功則記錄日志然后返回位置坐標(biāo)慎菲,失敗則判斷是否是否有intervalfunc方法需要執(zhí)行,默認(rèn)是沒有的锨并,跳過露该,然后接著繼續(xù)循環(huán)截圖、匹配第煮,直到超時報一個TargetNotFoundError異常出去解幼。
那么圖像的匹配算法大概就是在這個match_in接口里了,接著再看一點吧包警,哈哈
```
def match_in(self, screen):
? ? ? ? match_result = self._cv_match(screen)
? ? ? ? G.LOGGING.debug("match result: %s", match_result)
? ? ? ? if not match_result:
? ? ? ? ? ? return None
? ? ? ? focus_pos = TargetPos().getXY(match_result, self.target_pos)
? ? ? ? return focus_pos
? ? @logwrap
? ? def _cv_match(self, screen):
? ? ? ? # in case image file not exist in current directory:
? ? ? ? image = self._imread()
? ? ? ? image = self._resize_image(image, screen, ST.RESIZE_METHOD)
? ? ? ? ret = None
? ? ? ? for method in ST.CVSTRATEGY:
? ? ? ? ? ? if method == "tpl":
? ? ? ? ? ? ? ? ret = self._try_match(self._find_template, image, screen)
? ? ? ? ? ? elif method == "sift":
? ? ? ? ? ? ? ? ret = self._try_match(self._find_sift_in_predict_area, image, screen)
? ? ? ? ? ? ? ? if not ret:
? ? ? ? ? ? ? ? ? ? ret = self._try_match(self._find_sift, image, screen)
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? G.LOGGING.warning("Undefined method in CV_STRATEGY: %s", method)
? ? ? ? ? ? if ret:
? ? ? ? ? ? ? ? break
? ? ? ? return ret
```
match_in調(diào)用cv_match進行匹配撵摆,然后TargetPos().getXY(match_result, self.target_pos)就是對匹配出來的結(jié)果進行處理,在前面講touch的時候有一個參數(shù)是target_pos害晦,還有印象嗎特铝?根據(jù)教程和文檔說明暑中,target_pos是以123456789的數(shù)字按九宮格鍵盤排列,分別代表左上角鲫剿,正上角鳄逾,右上角,...灵莲,右下角雕凹。這個getXY就是對這個進行處理的,根據(jù)傳入的target_pos對匹配到的坐標(biāo)信息再做處理返回目標(biāo)圖片中的不同位置上的坐標(biāo)政冻,默認(rèn)是返回中心點枚抵。
再看cv_match接口,
imread()#根據(jù)圖片路徑明场,將圖片讀取為cv2的圖片處理格式
_resize_image(image, screen, ST.RESIZE_METHOD)#處理圖片尺寸汽摹,這里可以在ST.RESIZE_METHOD自定義縮放規(guī)則,默認(rèn)是用COCOS中的MIN策略
然后根據(jù)CVSTRATEGY(cv策略榕堰,應(yīng)該不同匹配的算法)竖慧,有tpl、sift逆屡,進行try_match圾旨。
其中sift策略中優(yōu)先對預(yù)測的區(qū)域進行匹配,也就是用到了再touch接口中傳入的record_pos魏蔗,終于知道為啥要傳入寫腳本是截圖的位置了吧砍的。
這個try_match是轉(zhuǎn)換接口,method莺治,再調(diào)用method廓鞠,也就是說匹配的算法有三個不同的,有興趣可以繼續(xù)去看看:
_find_template谣旁、_find_sift_in_predict_area床佳、_find_sift這三個接口。
```
@staticmethod
? ? def _try_match(method, *args, **kwargs):
? ? ? ? G.LOGGING.debug("try match with %s" % method.__name__)
? ? ? ? try:
? ? ? ? ? ? ret = method(*args, **kwargs)
? ? ? ? except aircv.BaseError as err:
? ? ? ? ? ? G.LOGGING.debug(repr(err))
? ? ? ? ? ? return None
? ? ? ? else:
? ? ? ? ? ? return ret
```
總結(jié)
Airtest的優(yōu)點
有個IDE榄审,大大地減少了寫自動化腳本的難度砌们,搭建環(huán)境、寫腳本搁进,運行腳本浪感,查看報告都一站式解決了;
圖像識別饼问,對不能用ui控件定位的地方的影兽,使用圖像識別來定位,對一些自定義控件莱革、H5峻堰、小程序讹开、游戲,都可以支持茧妒;
支持多個終端萧吠,使用圖像識別的話可以一套代碼兼容android和ios哦,用ui控件定位的話需要兼容一下桐筏。
本篇通過touch接口對airtest的圖像識別的源碼進行了初步的分析纸型,更多圖像匹配算法實現(xiàn)部分,下回分解梅忌。
本文同步自 港版國產(chǎn)機 的CSDN 博客:https://blog.csdn.net/u012897401/article/details/82927082