HarmonyOS自動(dòng)化測(cè)試實(shí)踐
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
一、運(yùn)行環(huán)境
工具列表
- Appium-Desktop v1.8.2
- Android SDK
- Python
- Appium-Python-Client
- Pytest
- Pycharm
工具安裝:
Appium-Desktop v1.8.2
下載地址:Appium-Desktop v1.8.2, 對(duì)于鴻蒙系統(tǒng),最佳的Appium兼容版本為v1.8.2。其他版本在獲取text時(shí)的值為“0.0”
安裝過(guò)程:




Android SDK
1.下載地址:Android SDK
2.將Android Studio安裝在默認(rèn)路徑: C:\Program Files\Android\Android Studio
3.在Android Studio的SDK Manager中下載Android SDK
4.配置環(huán)境變量:
- 新建JAVA_HOME,值為C:\Program Files\Android\Android Studio\jre
- 新建ANDROID_HOME,值為C:\Users\Administrator\AppData\Local\Android\Sdk
- 編輯Path, 新建%JAVA_HOME%\bin, %JAVA_HOME%\jre\bin, %ANDROID_HOME%\tools, %ANDROID_HOME%\tools\bin, %ANDROID_HOME%\platform-tools
Python
下載地址:Python
將Python安裝在默認(rèn)位置
環(huán)境變量配置
打開(kāi)CMD,輸入 python –version 測(cè)試python命令,輸入 pip –V 測(cè)試pip命令
如果測(cè)試失敗,則需要配置python的環(huán)境變量
Appium-Python-Client
安裝命令:pip install Appium-Python-Client

Pytest
安裝命令:pip install pytest

Pycharm
1.下載地址:Pycharm
Pycharm是比較流行Python編輯器(IDE工具),選擇Community版本下載
2.將Pycharm安裝在默認(rèn)位置
3.選擇python編譯器

二、元素定位
元素定位工具常用的有:uiautomatorviewer.bat,weditor.exe,Appium-Desktop

以u(píng)iautomatorviewer為例看一下常見(jiàn)的查找控件方式
1.通過(guò)id定位,resrouce-id
- element = conf.driver.find_element_by_id("Id_myButton").click()
2.通過(guò)ClassName定位: classname
- element = conf.driver.find_elements_by_class_name("android.widget.Button").click()
3.通過(guò)Accessiblityld定位:content-desc
- element = driver.find_element_by_accessibility_id("content-desc-text").click()
4.通過(guò)AndroidUiAutomator
- ui_str = 'new UiScrollable(UiSelector().className("{}")).scrollIntoView(new UiSelector().textContains("{}"))'.format(list_id, text_contains)
- element = conf.driver.find_element_by_android_uiautomator(ui_str).click()
5.通過(guò)坐標(biāo)定位,XY
- TouchAction(conf.driver).tap(x=int(x * phonewidth), y=int(y * phoneheight)).release().perform()
6.通過(guò)xpath定位終極定位,方式有很多種,以下列幾種常見(jiàn):
- 如果元素text是唯一的,可以通過(guò)text文本定位:
- //*[@text=’text文本屬性’]
- element = conf.driver.find_element_by_xpath("//*[@text='Click me!']").click()
- 如果元素id是唯一的,也可以id屬性定位
- //*[@resource-id=’id屬性’]
- element = conf.driver.find_element_by_xpath("//*[@resource-id='org.ohosannotations.sample:id/Id_myButton']").click()
- 通過(guò)content-desc屬性定位
- //*[@content-desc=’desc的文本’]
- element = conf.driver.find_element_by_xpath("//*[@content-desc='desc的文本']").click()
- contains模糊定位
- //[contains(@content-desc, ‘desc的文本’)]
- element = conf.driver.find_element_by_xpath("//*[contains(@content-desc, 'desc的文本')]").click()
- 組合定位
如果一個(gè)元素有2個(gè)屬性,通過(guò)xpath也可以同時(shí)匹配2個(gè)屬性,text, resource-id,class ,index,content-desc 這些屬性都能任意組合定位
通過(guò)id和class屬性 定位搜索框
- id_class = '//android.widget.EditText[@resource-id="org.ohosannotations.sample:id/Id_myTextField"]'
- element = conf.driver.find_element_by_xpath(id_class).click()
通過(guò)text和index屬性 定位按鈕Start list ability !
- desc_class = '//*[@text="Start list ability !" and @index="3"]'
- element = conf.driver.find_element_by_xpath(desc_class).click()
通過(guò)class和text屬性 定位輸入框
- class_text = '//android.widget.EditText[@text="輸入框默認(rèn)值"]'
- element = conf.driver.find_element_by_xpath(class_text).send_keys("zdhtest")
通過(guò)class和desc 定位搜索框
- id_desc = '//*[contains(@resource-id, "Id_myTextField") and @content-desc="desc的文本"]'
- element = conf.driver.find_element_by_xpath(id_desc).click()
鴻蒙與安卓定位方式基本相同
三、模擬用戶事件
在自動(dòng)化用戶操作事件中,鴻蒙與安卓基本方式一致,以下列舉常見(jiàn)的幾種操作事件:
1.點(diǎn)擊(確認(rèn)點(diǎn)擊)、輸入和清空操作
- 點(diǎn)擊:能支持點(diǎn)擊跳轉(zhuǎn)的定位元素,通過(guò).click()執(zhí)行
- driver.find_element_by_id(‘org.ohosannotations.sample:id/Id_myTextField’).click()
輸入:有輸入框需要輸入的定位元素,通過(guò).send_keys()執(zhí)行
- driver.find_element_by_id(‘org.ohosannotations.sample:id/Id_myTextField’).send_keys(‘zdhtest’)
清空:對(duì)已輸入的輸入框清空內(nèi)容,通過(guò).clear()執(zhí)行
- driver.find_element_by_id(‘org.ohosannotations.sample:id/Id_myTextField’).clear()
2.元素等待作用
在自動(dòng)化過(guò)程中,元素出現(xiàn)受網(wǎng)絡(luò)環(huán)境,設(shè)備性能等多種因素影響。因此元素加載的時(shí)間可能不一致,從而會(huì)導(dǎo)致元素?zé)o法定位超時(shí)報(bào)錯(cuò),但是實(shí)際上元素是正常加載了的,只是出現(xiàn)時(shí)間晚一點(diǎn)而已, 故設(shè)置元素等待可以更加靈活的制定等待定位元素的時(shí)間,從而增強(qiáng)腳本的健壯性,提高執(zhí)行效率
- 強(qiáng)制等待: 設(shè)置固定的等待時(shí)間,使用sleep()方法即可實(shí)現(xiàn)
- from time import sleep
- #強(qiáng)制等待5秒
- sleep(5)
- 隱式等待: 隱式等待是針對(duì)全部元素設(shè)置的等待時(shí)間
- driver.implicitly_wait(20)
- 顯式等待: 顯式等待是針對(duì)某個(gè)元素來(lái)設(shè)置的等待時(shí)間。
方法WebDriverWait格式參數(shù)如下:
- from selenium.webdriver.support.ui import WebDriverWait
- WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
- driver : WebDriver
- #timeout : 最長(zhǎng)超時(shí)時(shí)間,默認(rèn)以秒為單位
- #poll_frequency : 休眠時(shí)間的間隔時(shí)間,默認(rèn)為0.5秒
- #ignored_exceptions : 超時(shí)后的異常信息,默認(rèn)情況下拋NoSuchElementException異常。
- WebDriverWait()一般和until()或until_not()方法配合使用
- from selenium.webdriver.support.ui import WebDriverWait
- WebDriverWait(conf.driver, 10).until(EC.visibility_of_element_located((By.ID, self.locator)))
3.Toast內(nèi)容獲取
Toast是一種簡(jiǎn)易的消息提示框。 當(dāng)視圖顯示給用戶,在應(yīng)用程序中顯示為浮動(dòng),和Dialog不一樣的是,它永遠(yuǎn)不會(huì)獲得焦點(diǎn),無(wú)法被點(diǎn)擊,而且Toast顯示的時(shí)間有限,一般3秒左右就消失了,在鴻蒙應(yīng)用中,Toast目前還捕捉不到,解決方法也是采用截圖比較
- #Android獲取Toast方式
- toast_message = "這是一個(gè)toast文本"
- message ='//*[@text=\'{}\']'.format(toast_message)
- #顯示等待檢測(cè)元素
- toast_element=WebDriverWait(driver, 5).until(EC.visibility_of_element_located((message, self.locator)))
- print(toast_element.text)
- #鴻蒙中Toast處理方式
- self.get_image(imagepath)
- flag = self.image_assert(assertimagepath, imagepath, imagesucess)
4.截圖操作
- save_screenshot() :方法直接保存當(dāng)前屏幕截圖到當(dāng)前腳本所在文件位置
- driver.save_screenshot(‘aa.png’)
- get_screenshot_as_file(self, filename) :將截圖保留到指定文件路徑
- driver.get_screenshot_as_file(’./images/aa.png’)
5.滑動(dòng)操作
- #獲得機(jī)器屏幕大小x,y
- def getSize():
- x = dr.get_window_size()['width']
- y = dr.get_window_size()['height']
- return (x, y)
- #屏幕向上滑動(dòng)
- def swipeUp(t):
- l = getSize()
- x1 = int(l[0] * 0.5) #x坐標(biāo)
- y1 = int(l[1] * 0.75) #起始y坐標(biāo)
- y2 = int(l[1] * 0.25) #終點(diǎn)y坐標(biāo)
- dr.swipe(x1, y1, x1, y2,t)
- #屏幕向下滑動(dòng)
- def swipeDown(t):
- l = getSize()
- x1 = int(l[0] * 0.5) #x坐標(biāo)
- y1 = int(l[1] * 0.25) #起始y坐標(biāo)
- y2 = int(l[1] * 0.75) #終點(diǎn)y坐標(biāo)
- dr.swipe(x1, y1, x1, y2,t)
- #屏幕向左滑動(dòng)
- def swipLeft(t):
- l=getSize()
- x1=int(l[0]*0.75)
- y1=int(l[1]*0.5)
- x2=int(l[0]*0.05)
- dr.swipe(x1,y1,x2,y1,t)
- #屏幕向右滑動(dòng)
- def swipRight(t):
- l=getSize()
- x1=int(l[0]*0.05)
- y1=int(l[1]*0.5)
- x2=int(l[0]*0.75)
- dr.swipe(x1,y1,x2,y1,t)
- #調(diào)用向左滑動(dòng)
- swipLeft(1000)
- #調(diào)用向右滑動(dòng)
- swipRight(1000)
- #調(diào)用向上滑動(dòng)
- swipeUp(1000)
- #調(diào)用向下滑動(dòng)
- swipeDown(1000)
6.按壓、長(zhǎng)按、點(diǎn)擊(單純點(diǎn)擊)、移動(dòng)、暫停、釋放、執(zhí)行等操作
- 強(qiáng)制等待: 設(shè)置固定的等待時(shí)間,使用sleep()方法即可實(shí)現(xiàn)
- 按壓:press() 開(kāi)始按壓一個(gè)元素或坐標(biāo)點(diǎn)(x,y)。通過(guò)手指按壓手機(jī)屏幕的某個(gè)位置。 press也可以接收屏幕的坐標(biāo)(x,y)。press(self, el=None, x=None, y=None)
- TouchAction(driver).press(x=0,y=308)
- 長(zhǎng)按:longPress() 開(kāi)始按壓一個(gè)元素或坐標(biāo)點(diǎn)(x,y)。 相比press()方法,longPress()多了一個(gè)入?yún)ⅲ热婚L(zhǎng)按,得有按的時(shí)間吧。duration以毫秒為單位。1000表示按一秒鐘。其用法與press()方法相同。
- long_press(self, el=None, x=None, y=None, duration=1000)
- 點(diǎn)擊:tap() 對(duì)一個(gè)元素或控件執(zhí)行點(diǎn)擊操作。用法參考press()。不能點(diǎn)擊跳轉(zhuǎn),單純的點(diǎn)擊(例如:點(diǎn)擊勾選、點(diǎn)擊點(diǎn)贊,點(diǎn)擊播放等)
- 移動(dòng):move_to() 將指針從上一個(gè)點(diǎn)移動(dòng)到指定的元素或點(diǎn)。(滑動(dòng)驗(yàn)證條)
- move_to(self, el=None, x=None, y=None)
- 暫停:Wait()暫停腳本的執(zhí)行,單位為毫秒
- wait(self, ms=0)
- 釋放:方法release() 結(jié)束的行動(dòng)取消屏幕上的指針。
- release(self)
- 執(zhí)行:perform() 執(zhí)行的操作發(fā)送到服務(wù)器的命令操作。
- perform(self)
7.獲取元素的屏幕尺寸和名稱(chēng)
- 獲取屏幕尺寸:
- 屏幕總尺寸:分辨率 phonesize = self.get_phone_size()
- 屏幕寬度:X值 phonewidth = phonesize["width"]
- 屏幕高度:Y值 phoneheight = phonesize["height"]
- 獲取元素的名稱(chēng):
- driver.find_element_by_xpath(xpath).text
- driver.find_element_by_id(“org.ohosannotations.sample:id/Id_myTextField”).text
四、簡(jiǎn)單示例
以下是兩個(gè)簡(jiǎn)單case
1.示例一
下面以ohosannotations組件為例,case中包含事件有:點(diǎn)擊、長(zhǎng)按、輸入、文本斷言和圖片對(duì)比斷言
- def test_ohosannotations(self, getlocator):
- annotations_locators = getlocator["ohosannotations"]
- with allure.step("點(diǎn)擊Click me_按鈕的點(diǎn)擊事件"):
- self.ta_tap(annotations_locators["Click me_按鈕"])
- with allure.step("向輸入框輸入內(nèi)容"):
- self.text_input(annotations_locators["myTextField_控件"], "這是一條有內(nèi)涵的內(nèi)容!")
- self.ta_tap(annotations_locators["Click me_按鈕"])
- time.sleep(2)
- assert "這是一條有內(nèi)涵的內(nèi)容!" == self.get_element_text(annotations_locators["myText_控件"]), "文本顯示不正確"
- with allure.step("長(zhǎng)按Start extra ability, long click !控件"):
- logger.info("長(zhǎng)按Start extra ability, long click !控件")
- self.ta_longpress(annotations_locators["Start extra ability, long click_按鈕"], 1000)
- time.sleep(2)
- with allure.step("斷言彈出框圖片與期望圖片是否一致"):
- self.get_image(imagepath)
- flag = self.image_assert(assertimagepath, imagepath, imagesucess)
- if flag is True:
- with open(imagesucess, "rb") as f:
- context = f.read()
- allure.attach(context, "匹配成功的圖片", attachment_type=allure.attachment_type.PNG)
- else:
- with open(imagepath, "rb") as f:
- context = f.read()
- allure.attach(context, "匹配失敗的圖片", attachment_type=allure.attachment_type.PNG)
- logger.info("匹配結(jié)果:%s" % flag)
- assert flag is True
運(yùn)行結(jié)果:
2.示例二
以Sensey組件為例,演示多點(diǎn)觸控的使用方法,驗(yàn)證Sensey組件的雙指檢測(cè)功能
- @allure.story('Sensey')
- @allure.title("sensey_006 雙指檢測(cè)")
- @allure.tag("L1")
- @allure.severity("normal") # blocker:阻塞缺陷 critical:嚴(yán)重缺陷 normal:一般缺陷 minor:次要缺陷 trivial:輕微缺陷
- @allure.description("雙指檢測(cè)")
- @pytest.mark.flaky(reruns=1, reruns_delay=5) # reruns:重試次數(shù) reruns_delay:重試的間隔時(shí)間
- def test_sensey_006(self, getlocator):
- logger.info("sensey_006 雙指檢測(cè)")
- self.ta_tap(["TOUCH DETECTOR", "XPATH", "//*[@text=\"TOUCH DETECTOR\"]"])
- self.ta_tap(["Touch Detection", "XPATH", "//*[@resource-id=\"Id_switch_touch\"]"])
- a1 = TouchAction(conf.driver).tap(x=530, y=1380).release()
- a2 = TouchAction(conf.driver).tap(x=730, y=1380).release()
- action = MultiAction(conf.driver)
- action.add(a1,a2)
- action.perform()
- time.sleep(0.5)
- text = self.get_element_text(["Result", "XPATH", "//*[@resource-id=\"Id_tv_result\"]"])
- assert text == "Two Finger Tap", "雙指檢測(cè)不正確"
運(yùn)行結(jié)果:
五、測(cè)試報(bào)告生成
使用allure-pytest插件
- @allure.feature('設(shè)置應(yīng)用') # 功能名稱(chēng)
- class TestSettings(BaseCase):
- @allure.story('聲音和振動(dòng)') # 子功能名稱(chēng)
- @allure.title('settings_001 設(shè)置來(lái)電鈴聲') # 用例標(biāo)題
- @allure.severity('normal') # 缺陷級(jí)別
- @allure.description('檢查是否可以設(shè)置來(lái)電鈴聲') # 用例描述
- def test_settings_001(self):
- # 測(cè)試步驟
- with allure.step('進(jìn)入聲音和振動(dòng)'):
- pass
- with allure.step('設(shè)置來(lái)電鈴聲'):
- pass
- with allure.step('斷言來(lái)電鈴聲設(shè)置成功'):
- pass
生成html報(bào)告和打開(kāi)
- if __name__ == '__main__':
- now = time.strftime('%Y%m%d%H%M%S', time.localtime())
- print('執(zhí)行腳本(%s)' % now)
- xml_path = './reports/report-%s/xml' % now
- html_path = './reports/report-%s/html' % now
- case_path = './testcases/'
- # 運(yùn)行測(cè)試腳本
- pytest.main(['-s', '-q', '--alluredir', xml_path, case_path])
- # 生成html報(bào)告
- cmd = 'allure generate %s -o %s --clean' % (xml_path, html_path)
- os.system(cmd)
- # 打開(kāi)測(cè)試報(bào)告
- cmd = 'allure open %s' % html_path
- os.system(cmd)
生成報(bào)告如下:

展開(kāi)詳情:

想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)