UI自動化測試工具AirTest學(xué)習(xí)筆記

> 本篇更偏向于源碼解析笑撞,適用于對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吧

上手

網(wǎng)易官方的最快五分鐘上手教程

官方教程嚼黔,有演示視頻细层,有動圖,一目了然唬涧。環(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狰腌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子牧氮,更是在濱河造成了極大的恐慌琼腔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踱葛,死亡現(xiàn)場離奇詭異丹莲,居然都是意外死亡,警方通過查閱死者的電腦和手機尸诽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門甥材,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人性含,你說我怎么就攤上這事洲赵。” “怎么了商蕴?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵叠萍,是天一觀的道長。 經(jīng)常有香客問我绪商,道長苛谷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任格郁,我火速辦了婚禮抄腔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘理张。我一直安慰自己,他們只是感情好绵患,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布雾叭。 她就那樣靜靜地躺著,像睡著了一般落蝙。 火紅的嫁衣襯著肌膚如雪织狐。 梳的紋絲不亂的頭發(fā)上暂幼,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機與錄音移迫,去河邊找鬼旺嬉。 笑死,一個胖子當(dāng)著我的面吹牛厨埋,可吹牛的內(nèi)容都是我干的邪媳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼荡陷,長吁一口氣:“原來是場噩夢啊……” “哼雨效!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起废赞,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤徽龟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后唉地,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體据悔,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年耘沼,在試婚紗的時候發(fā)現(xiàn)自己被綠了极颓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡耕拷,死狀恐怖讼昆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骚烧,我是刑警寧澤浸赫,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站赃绊,受9級特大地震影響既峡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碧查,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一运敢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忠售,春花似錦传惠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泰佳,卻和暖如春盼砍,著一層夾襖步出監(jiān)牢的瞬間尘吗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工浇坐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留睬捶,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓近刘,卻偏偏與公主長得像擒贸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子跌宛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容