UI自動化測試工具AirTest學習筆記之從touch接口看圖像識別

本篇更偏向於源碼解析,適用於對airtest有一些瞭解,看過入門教程,寫過demo的童鞋,當然初學者也可以在本章的上手環節跳轉到網易官方最快5分鐘教程中學習,因爲我覺得那篇教程已經夠好了,就不多寫入門教程了。

目錄

簡介

上手

進階

總結


簡介

Airtest Project是最近非常火的一個ui自動化測試工具,由網易遊戲內部工具團隊開發並開源,獲得谷歌力挺。

AirtestIDE 是一個跨平臺、多端(Windows、web、android、ios、遊戲)的UI自動化測試編輯器。

  • 自動化腳本錄製、一鍵回放、報告查看,輕而易舉實現自動化測試流程,自有編輯器一站式解決
  • 支持基於圖像識別的 Airtest 框架,適用於所有Android和Windows遊戲,會截圖就能寫腳本
  • 支持基於UI控件搜索的 Poco 框架,適用於Unity3d,Cocos2d與Android、ios App、web
  • 能夠運行在Windows和MacOS上
  • 網易內部已成功應用在數十個項目上,利用 手機集羣 進行大規模自動化測試,手機集羣沒有開源,準備做收費模式吧
  • 使用python編寫,兼容2、3,儘量用3吧

上手

網易官方的最快五分鐘上手教程

官方教程,有演示視頻,有動圖,一目瞭然。環境搭建也相當簡單,基本上安裝好IDE就可以了。

AirTest IDE提供了一站式功能:腳本開發(錄製、編輯)、設備管理、運行、回放、結果查看

相信通過網易的這個上手教程,很多人都能很快就可以把airtest玩起來了。

進階

當我們跟隨着教程寫好一條腳本,運行起來以後,一起來看看AirTest的大致框架。

首先在AirTest的定義中腳本文件名的後綴是.air,當我們在IDE中新建一個腳本文件

再來到文件管理中我們可以看到這是一個文件夾。

這裏面有一個跟air腳本同名的py文件,其他的png圖片就是在IDE裏截圖,錄製,生成的圖像文件。

打開這個py文件來看看:

可以看出在IDE裏顯示的touch(圖片),就是在api裏的一個touch接口,裏面傳入一個Template,這個對象包含了圖片文件的名稱、錄製時的相對座標(record_pos),分辨率(resolution)等,當然還有其他參數:目標位置(target_pos)、rgb匹配(rgb),如果你在IDE裏雙擊圖片就會彈出窗口設置這些詳細參數。

我想圖像識別大概就是這樣了:寫腳本時截下目標圖片(你想要點擊的地方),這圖片就跟python腳本保存在一起,touch接口傳入這些目標圖片,進行匹配,成功後點擊目標圖片的位置,有興趣的話繼續來看看這個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

入參:

  1. v,可以是Template對象(目標截圖),或者是pos(座標)
  2. times,點擊次數,默認爲1
  3. kwargs,平臺的特殊參數

loop_find(v, timeout=ST.FIND_TIMEOUT)#通過名字大概知道,循環查找這個v,有個超時退出,返回座標點

G.DEVICE.touch(pos, **kwargs)#點擊設備的指定座標點

G.DEVICE應該就是一個當前的設備,兼容android、ios、windows

delay_after_operation#最後點擊完以後還等待一下,所以這裏可以配置每步點擊的等待時間

所以touch接口的邏輯是:

  1. 如傳入圖片信息,循環查找匹配出目標圖片所在屏幕的座標點;
  2. 傳入是座標,開始記錄log信息;
  3. 循環點擊指定的座標點;
  4. 等待,然後返回目標座標點。

再往下,看一下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,未超時則進行下次循環:
        if (time.time() - start_time) > timeout:
            try_log_screen(screen)
            raise TargetNotFoundError('Picture %s not found in screen' % query)
        else:
            time.sleep(interval)

入參:

  1. query:要在截圖中查找的圖片模板(也就是我們寫腳本截的圖咯)
  2. timeout:最大匹配時間
  3. threshold:默認是None,字面意思是閾值,也就是匹配時的相似度吧,調低點可以更容易匹配上,也更容易匹配錯
  4. interval:循環匹配的間隔時間,每次要對設備截圖傳入進來匹配,中間的等待時間
  5. intervalfunc:傳入一個方法,在匹配失敗時調用,也就是可以在接口的外部自定義匹配失敗後的動作

返參:pos:目標圖片在設備屏幕中的位置

screen = G.DEVICE.snapshot(filename=None)#設備截圖,所以運行完腳本以後工程路徑會有很多個截圖文件,就是這裏產生的。

match_pos = query.match_in(screen)#在設備截圖中匹配查找我們傳入的目標圖片

所以這loop_find的邏輯就是:一個循環,從設備中截取屏幕的圖片,在屏幕圖片上查找匹配我們的目標圖片,匹配成功則記錄日誌然後返回位置座標,失敗則判斷是否是否有intervalfunc方法需要執行,默認是沒有的,跳過,然後接着繼續循環截圖、匹配,直到超時報一個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調用cv_match進行匹配,然後TargetPos().getXY(match_result, self.target_pos)就是對匹配出來的結果進行處理,在前面講touch的時候有一個參數是target_pos,還有印象嗎?根據教程和文檔說明,target_pos是以123456789的數字按九宮格鍵盤排列,分別代表左上角,正上角,右上角,...,右下角。這個getXY就是對這個進行處理的,根據傳入的target_pos對匹配到的座標信息再做處理返回目標圖片中的不同位置上的座標,默認是返回中心點。

再看cv_match接口,

  1. imread()#根據圖片路徑,將圖片讀取爲cv2的圖片處理格式
  2. _resize_image(image, screen, ST.RESIZE_METHOD)#處理圖片尺寸,這裏可以在ST.RESIZE_METHOD自定義縮放規則,默認是用COCOS中的MIN策略
  3. 然後根據CVSTRATEGY(cv策略,應該不同匹配的算法),有tpl、sift,進行try_match。

其中sift策略中優先對預測的區域進行匹配,也就是用到了再touch接口中傳入的record_pos,終於知道爲啥要傳入寫腳本是截圖的位置了吧。

這個try_match是轉換接口,method,再調用method,也就是說匹配的算法有三個不同的,有興趣可以繼續去看看:

_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

總結

Airtest的優點

  1. 有個IDE,大大地減少了寫自動化腳本的難度,搭建環境、寫腳本,運行腳本,查看報告都一站式解決了;
  2. 圖像識別,對不能用ui控件定位的地方的,使用圖像識別來定位,對一些自定義控件、H5、小程序、遊戲,都可以支持;
  3. 支持多個終端,使用圖像識別的話可以一套代碼兼容android和ios哦,用ui控件定位的話需要兼容一下。

本篇通過touch接口對airtest的圖像識別的源碼進行了初步的分析,更多圖像匹配算法實現部分,下回分解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章