鴻蒙自動(dòng)化常見(jiàn)踩坑點(diǎn)及解決方法
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
在做鴻蒙自動(dòng)化測(cè)試時(shí),經(jīng)常會(huì)碰到一些讓同學(xué)頭疼的問(wèn)題,有時(shí)會(huì)阻礙很久,影響編寫自動(dòng)化腳本進(jìn)度,那么碰到這些阻礙點(diǎn),我們?cè)撊绾蜗率帜兀旅媪_列幾個(gè)常見(jiàn)的坑點(diǎn)機(jī)解決方案
踩坑點(diǎn)一、在點(diǎn)擊方法使用的定位元素為非坐標(biāo)控件的情況時(shí),獲取圖片方法無(wú)法截取到toast!
如何獲取toast樣式進(jìn)行自動(dòng)化?
1.步驟:
- 截取toast樣式保存樣式圖片(點(diǎn)擊非元素坐標(biāo),無(wú)法獲取到toast圖片)
- 踩坑點(diǎn):使用gif錄制工具,計(jì)算點(diǎn)擊到彈出toast的時(shí)間,使用非元素坐標(biāo)點(diǎn)擊,無(wú)法獲取到toast截圖,正常邏輯思維,改變點(diǎn)擊到彈出toast的時(shí)間,始終無(wú)法獲取到toast圖片
- 截取整個(gè)屏幕的圖片
- 將樣式圖片與整體圖片進(jìn)行對(duì)比驗(yàn)證,樣式是否存在且一致
2.獲取圖片方法:
- def get_image(png_path, xy=None):
- """
- 截圖,當(dāng)xy入?yún)⒂行r(shí)則進(jìn)行裁剪
- :param png_path: 截圖的路徑
- :param xy: (x1,y1,x2,y2)
- :return:
- """
- conf.driver.get_screenshot_as_file(png_path)
- logger.debug("截屏成功")
- if xy is not None:
- if isinstance(xy, tuple):
- for i in xy:
- if isinstance(i, int):
- pass
- else:
- logger.error("xy的入?yún)⒈仨毝际钦麛?shù)")
- raise (Exception, "xy的入?yún)⒈仨毝际钦麛?shù)")
- try:
- Image.open(png_path).crop(xy).save(png_path)
- logger.debug("圖片[%s]裁剪成功,裁剪坐標(biāo)%s" % (png_path,xy))
- except Exception as e:
- logger.error("截圖失敗")
- raise e
- else:
- logger.error("xy的入?yún)⒏袷奖仨毷?nbsp;(x1,y1,x2,y2) ")
- raise (Exception, "xy的入?yún)⒏袷奖仨毷?nbsp;(x1,y1,x2,y2) ")
3.創(chuàng)建文件名稱:
- expect_image = os.path.join(self.expect_images, "expect_Toast.png")
- assert_image = os.path.join(self.assert_images, "assert_Toast_001.png")
- success_image = os.path.join(self.success_images, "success_Toast_001.png")
4.圖片對(duì)比方法:
- def image_assert(img1, img2, image_path=None, threshold=0.95,cvformat=1):
- """
- 斷言圖片img1是否在img2中,若斷言成功則會(huì)將圖片保存至本地
- !!! 斷言的圖片必須用appium截圖,可根據(jù)需求進(jìn)行裁剪(該類下的get_image方法截圖即可)
- !!! 直接對(duì)模擬器手動(dòng)截圖然后與appium的自動(dòng)截圖做對(duì)比是無(wú)法匹配的,因?yàn)榉直媛释耆煌。。?nbsp;
- :param img1: 預(yù)期的圖片
- :param img2: 用例執(zhí)行時(shí)的截圖
- :param image_path: 判斷后若斷言成功則會(huì)將對(duì)比后的圖片保存至本地,本地路徑,不入?yún)t不會(huì)生成對(duì)比圖
- :param threshold: 匹配度,建議大于0.9
- :param cvformat: 圖片轉(zhuǎn)換格式,入?yún)?-轉(zhuǎn)換為灰度圖片,入?yún)⒎?-轉(zhuǎn)換為RGB格式,對(duì)顏色有嚴(yán)格校驗(yàn)需求的要轉(zhuǎn)成RGB格式
- :return: True or False
- """
- if not os.path.exists(img1):
- raise (Exception,"[%s]圖片不存在!" % img1)
- if not os.path.exists(img2):
- raise (Exception, "[%s]圖片不存在!" % img2)
- scale = 1
- img = cv2.imread(img2) # 要找的大圖
- img = cv2.resize(img, (0, 0), fx=scale, fy=scale)
- template = cv2.imread(img1) # 圖中的小圖
- template = cv2.resize(template, (0, 0), fx=scale, fy=scale)
- template_size = template.shape[:2]
- if int(cvformat) == 1:
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
- template_ = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
- else:
- img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
- template_ = cv2.cvtColor(template, cv2.COLOR_BGR2RGB)
- result = cv2.matchTemplate(img_gray, template_, cv2.TM_CCOEFF_NORMED)
- loc = np.where(result >= threshold)
- # 使用灰度圖像中的坐標(biāo)對(duì)原始RGB圖像進(jìn)行標(biāo)記
- point = ()
- for pt in zip(*loc[::-1]):
- cv2.rectangle(img, pt, (pt[0] + template_size[1], pt[1] + template_size[0]), (0, 0, 255), 2)
- point = pt
- if point == ():
- logger.debug("圖片[%s]在圖片[%s]中沒(méi)有匹配到" % (img1, img2))
- return False
- else:
- if image_path is not None:
- cv2.imwrite(image_path, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 3]) # 將圖片保存到本地
- logger.debug("圖片[%s]在圖片[%s]中成功匹配到" % (img1, img2))
- return True
5.點(diǎn)擊方法:
- def ta_tap(self, selector, wait_presence=0, wait_visibility=0, timeout=20):
- """
- 模擬手指點(diǎn)擊一個(gè)元素或坐標(biāo)
- :param selector: 點(diǎn)擊的元素,定位器,要求格式為["元素名稱","定位方法","定位表達(dá)式"],例如["XXX按鈕","XPATH","//a[@text=‘測(cè)試定位’]"],["XXX按鈕","ID","username"]
- :param wait_presence: 是否需要等待元素加載,0-不需要,1-需要
- :param wait_visibility: 是否需要等待元素可見(jiàn),0-不需要,1-需要
- :param timeout: 等待的時(shí)間,默認(rèn)20秒
- :return:
- """
- if selector:
- try:
- locator_name = selector[0]
- locator_by = str(selector[1]).upper() # 定位方式
- locator_value = str(selector[2]) # locator的值
- except Exception as e:
- logger.error("ta_tap方法:selector入?yún)⒏袷奖仨毷莑ist<string>,[元素名稱,定位方法,定位表達(dá)式]")
- raise e
- if str(wait_presence) == "1" and str(wait_visibility) == "1":
- logger.warning("ta_tap方法:請(qǐng)不要同時(shí)使用等待元素加載和等待元素可見(jiàn)?。。?quot;)
- if locator_by == "XY":
- try:
- x, y = int(locator_value.split(",")[0]), int(locator_value.split(",")[1])
- except Exception as e:
- logger.error("XY坐標(biāo)值格式錯(cuò)誤,正確格式:x,y")
- raise e
- TouchAction(conf.driver).tap(x=x, y=y).release().perform()
- logger.debug("ta_tap模擬手指點(diǎn)擊元素:(%s,%s)" % (x, y))
- elif locator_by == "XY%":
- phonesize = self.get_phone_size()
- phonewidth = phonesize["width"]
- phoneheight = phonesize["height"]
- try:
- x, y = float(locator_value.split(",")[0]), float(locator_value.split(",")[1])
- except Exception as e:
- logger.error("XY坐標(biāo)值格式錯(cuò)誤,正確格式:x,y-x和y均是小數(shù)(不要填寫百分比),例如0.8,0.5")
- raise e
- TouchAction(conf.driver).tap(x=int(x * phonewidth), y=int(y * phoneheight)).release().perform()
- logger.debug("ta_tap模擬手指點(diǎn)擊元素:(%s,%s)" % (x * phonewidth, y * phoneheight))
- else:
- if str(wait_presence) == "1":
- self.wait_element_presence(selector, timeout)
- if str(wait_visibility) == "1":
- self.wait_element_visibility(selector, timeout)
- try:
- el = self.xlsxfind_element(selector)
- TouchAction(conf.driver).tap(el).release().perform()
- logger.debug("ta_tap模擬手指點(diǎn)擊%s" % locator_name)
- except Exception as e:
- logger.error("tap方法異常,元素名稱%s" % locator_name)
- raise e
- else:
- raise Exception("wait_element_presence方法:selector參數(shù)是必須的")
6.示例:

踩坑點(diǎn)二、appium版本在1.5以后就不再支持ByName的定位
appium版本在1.5以后就不再支持ByName的定位,在appium1.6.3/1.6.4/1.6.5版本以后如何支持ByName定位,適用于安卓,同樣適用于鴻蒙。在使用appium1.5之后的版本時(shí),當(dāng)我們直接適用ByName方式去查找控件時(shí),一定見(jiàn)過(guò)這個(gè)錯(cuò)誤:
- org.openqa.selenium.InvalidSelectorException: Locator Strategy 'name' is not supported for this session
發(fā)現(xiàn)曾經(jīng)的定位神器居然ByName居然不再支持了,那么怎么解決這個(gè)問(wèn)題呢?以下提供兩種解決方式:
- 換其他定位方式,比如用xpath代替
- 使用ByAByAccessibilityId代替,但實(shí)踐證明這個(gè)方法并沒(méi)有取代ByName
其中第一種是可取的,換其他定位方式,下面給大家一個(gè)不用換定位方式,可以無(wú)縫解決ByName在升級(jí)appium版本定位方法
一招修改源碼解決問(wèn)題根源,修改方法如下:
在本地找到Appium路徑下的driver.js文件
- Appium\resources\app\node_modules\appium\node_modules\appium-android-driver\build\lib\driver.js
只需要修改其中一行即可

打開(kāi)driver.js文件

在代碼行加上“name”屬性
- this.locatorStrategies = ['xpath', 'id', 'class name', 'accessibility id', '-android uiautomator','name'];
修改完成之后,保存,再次重啟appium服務(wù),就可以繼續(xù)使用ByName定位啦
- element = conf.driver.find_element_by_name("name").click()
如果不想用這種方式,也可以使用通用xpath
- element = conf.driver.find_element_by_xpath("//*[@text='name']").click()
踩坑點(diǎn)三、彈窗點(diǎn)擊確認(rèn)或選擇選項(xiàng)后,出現(xiàn)腳本無(wú)法往下執(zhí)行的問(wèn)題
遇到這種問(wèn)題時(shí),可以通過(guò)坐標(biāo)點(diǎn)擊頁(yè)面彈窗,再點(diǎn)擊空白處來(lái)釋放,就可以繼續(xù)往下執(zhí)行
如desCharts組件在彈窗切換圖表類型后,腳本會(huì)卡住,可以通過(guò)坐標(biāo)點(diǎn)擊一個(gè)不影響功能的彈窗,再點(diǎn)擊空白處讓彈窗消失,腳本就可以繼續(xù)往下執(zhí)行:
- def exchange(self, elements, chart):
- """
- 切換圖表選項(xiàng)(彈窗選擇后腳本會(huì)卡住,需要釋放)
- """
- self.ta_tap(elements["圖表切換選項(xiàng)"])
- if chart in ["堆疊折線圖", "柱狀圖", "堆疊柱狀圖"]:
- self.swipe_xy(452, 680, 450, 150)
- time.sleep(1)
- self.ta_tap(elements[chart])
- time.sleep(1)
- self.ta_tap(elements["釋放屏幕坐標(biāo)"])
- time.sleep(1)
- self.ta_tap(elements["釋放屏幕坐標(biāo)"])
- time.sleep(1)
踩坑點(diǎn)四、在滾動(dòng)功能無(wú)法使用時(shí),需要滑動(dòng)列表到特定位置。Appium的swipe此時(shí)不好用,drag_and_drop方法無(wú)法使用坐標(biāo)點(diǎn)。
解決方法:
1.重寫drag_and_drop方法,使它可以使用坐標(biāo)點(diǎn)
- def drag_and_drop(self, origin_el: Union["WebElement", "dict"], destination_el: Union["WebElement", "dict"]):
- action = TouchAction(self)
- if isinstance(origin_el, dict):
- action.long_press(**origin_el)
- else:
- action.long_press(origin_el)
- if isinstance(destination_el, dict):
- action.move_to(**destination_el)
- else:
- action.move_to(destination_el)
- action.release().perform()
- return self
2.示例:
- @allure.story("MusicBobber")
- @allure.title("MusicBobber_015")
- @allure.tag("L1")
- @allure.severity("normal") # blocker:阻塞缺陷 critical:嚴(yán)重缺陷 normal:一般缺陷 minor:次要缺陷 trivial:輕微缺陷
- @allure.description("刪除懸浮掛件")
- @pytest.mark.flaky(reruns=1, reruns_delay=5) # reruns:重試次數(shù) reruns_delay:重試的間隔時(shí)間
- def test_MusicBobber_015(self, allow):
- logger.info("MusicBobber_015")
- with allure.step("按返回鍵"):
- logger.info("按返回鍵")
- self.keyboard(4)
- time.sleep(1)
- with allure.step("刪除懸浮掛件"):
- logger.info("刪除懸浮掛件")
- self.drag_and_drop({x:74, y:176}, {x:540, y:2048})
- time.sleep(0.5)
- with allure.step("驗(yàn)證"):
- logger.info("驗(yàn)證")
- expect_image = os.path.join(self.expect_images, f"expect_MusicBobber_015.png")
- assert_image = os.path.join(self.assert_images, f"assert_MusicBobber_015.png")
- contrast_images = os.path.join(self.contrast_images, f"contrast_MusicBobber_015.png")
- self.get_image(contrast_images)
- flag = self.image_assert(expect_image, contrast_images, assert_image)
- self.image_in_report(flag, assert_image, expect_image)
- assert flag is False
結(jié)語(yǔ)
其實(shí),在UI自動(dòng)化實(shí)踐中我們還會(huì)遇到其他不同的阻礙點(diǎn),Appium本身就存在一些bug,鴻蒙應(yīng)用的自動(dòng)化與安卓原理基本一致,我們都是在
摸索中前行,所以,碰到坑點(diǎn)不要慌,在原生不支持的情況下,換一種解決方式,也會(huì)使你豁然開(kāi)朗,希望以上幾個(gè)問(wèn)題點(diǎn)及解決方案能對(duì)你有所幫助
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)