基於python+appium+yaml安卓UI自動化測試分享
結構介紹
之前分享過一篇安卓UI測試,但是沒有實現資料與程式碼分離,後期維護成本較高,所以最近抽空優化了一下。
不想看文章得可以直接去Github,歡迎拍磚
大致結構如下:
-
testyaml管理用例,實現資料與程式碼分離,一個模組一個資料夾
-
public 存放公共檔案,如讀取配置檔案、啟動appium服務、讀取Yaml檔案、定義日誌格式等
-
page 存放最小測試用例集,一個模組一個資料夾
-
results 存放測試報告及失敗截圖
-
logs 存放日誌
- testcase 存放測試用例
- runtest.py 執行所有測試用例
yaml格式介紹
首先看下yaml檔案的格式,之前也寫過一點關於yaml語法學習的文章
testcase部分是重點,其中:
-
element_info:定位元素資訊
-
find_type:屬性,id、xpath、text、ids
-
operate_type: click、sendkeys、back、swipe_up 為back就是返回,暫時就四種
上面三個必填,operate_type必填!!!!!!
-
send_content:send_keys 時用到
-
index:ids時用到
-
times: 返回次數或者上滑次數
testinfo:
- id: cm001
title: 新增終端門店
execute: 1
testcase:
-
element_info: 客戶
find_type: text
operate_type: click
-
element_info: com.fiberhome.waiqin365.client:id/cm_topbar_tv_right
find_type: id
operate_type: click
-
element_info: com.fiberhome.waiqin365.client:id/custview_id_singletv_inputtext
find_type: ids
operate_type: send_keys
send_content: auto0205
index: 0
-
element_info:
find_type:
operate_type: swipe_up
times: 1
-
element_info: 提交
find_type: text
operate_type: click
-
element_info:
find_type:
operate_type: back
times: 1
程式碼部分
公共部分
個人覺得核心的就是公共部分,相當於建房子,公共部分搞好了,後面僅僅是呼叫即可,建房子把架子搭好,後面就添磚加瓦吧。
讀取配置檔案readconfig.py
設定日誌格式logs.py
獲取裝置GetDevices.py
這幾個通用的就不做介紹了
- 讀取yaml檔案 GetYaml.py
主要用來讀取yaml檔案
#coding=utf-8
#author=`Shichao-Dong`
import sys
reload(sys)
sys.setdefaultencoding(`utf8`)
import yaml
import codecs
class getyaml:
def __init__(self,path):
self.path = path
def getYaml(self):
```
讀取yaml檔案
:param path: 檔案路徑
:return:
```
try:
f = open(self.path)
data =yaml.load(f)
f.close()
return data
except Exception:
print(u"未找到yaml檔案")
def alldata(self):
data =self.getYaml()
return data
def caselen(self):
data = self.alldata()
length = len(data[`testcase`])
return length
def get_elementinfo(self,i):
data = self.alldata()
# print data[`testcase`][i][`element_info`]
return data[`testcase`][i][`element_info`]
def get_findtype(self,i):
data = self.alldata()
# print data[`testcase`][i][`find_type`]
return data[`testcase`][i][`find_type`]
def get_operate_type(self,i):
data = self.alldata()
# print data[`testcase`][i][`operate_type`]
return data[`testcase`][i][`operate_type`]
def get_index(self,i):
data = self.alldata()
if self.get_findtype(i)==`ids`:
return data[`testcase`][i][`index`]
else:
pass
def get_send_content(self,i):
data = self.alldata()
# print data[`testcase`][i][`send_content`]
if self.get_operate_type(i) == `send_keys`:
return data[`testcase`][i][`send_content`]
else:
pass
def get_backtimes(self,i):
data = self.alldata()
if self.get_operate_type(i)==`back` or self.get_operate_type(i)==`swipe_up`:
return data[`testcase`][i][`times`]
else:
pass
def get_title(self):
data = self.alldata()
# print data[`testinfo`][0][`title`]
return data[`testinfo`][0][`title`]
- 啟動appium服務 StartAppiumServer.py
主要是啟動appium並返回埠port,這個port在下面的driver中需要
#coding=utf-8
#author=`Shichao-Dong`
from logs import log
import random,time
import platform
import os
from GetDevices import devices
log = log()
dev = devices().get_deviceName()
class Sp:
def __init__(self, device):
self.device = device
def __start_driver(self, aport, bpport):
"""
:return:
"""
if platform.system() == `Windows`:
import subprocess
subprocess.Popen("appium -p %s -bp %s -U %s" %
(aport, bpport, self.device), shell=True)
def start_appium(self):
"""
啟動appium
p:appium port
bp:bootstrap port
:return: 返回appium埠引數
"""
aport = random.randint(4700, 4900)
bpport = random.randint(4700, 4900)
self.__start_driver(aport, bpport)
log.info(
`start appium :p %s bp %s device:%s` %
(aport, bpport, self.device))
time.sleep(10)
return aport
def main(self):
"""
:return: 啟動appium
"""
return self.start_appium()
def stop_appium(self):
```
停止appium
:return:
```
if platform.system() == `Windows`:
os.popen("taskkill /f /im node.exe")
if __name__ == `__main__`:
s = Sp(dev)
s.main()
- 獲取driver GetDriver.py
platformName、deviceName、appPackage、appActivity這些解除安裝配置檔案config.ini檔案中,可以直接通過readconfig.py檔案讀取獲得。
appium_port有StartAppiumServer.py檔案返回
s = Sp(deviceName)
appium_port = s.main()
def mydriver():
desired_caps = {
`platformName`:platformName,`deviceName`:deviceName, `platformVersion`:platformVersion,
`appPackage`:appPackage,`appActivity`:appActivity,
`unicodeKeyboard`:True,`resetKeyboard`:True,`noReset`:True
}
try:
driver = webdriver.Remote(`http://127.0.0.1:%s/wd/hub`%appium_port,desired_caps)
time.sleep(4)
log.info(`獲取driver成功`)
return driver
except WebDriverException:
print `No driver`
if __name__ == "__main__":
mydriver()
- 重新封裝find等命令,BaseOperate.py
裡面主要是一些上滑、返回、find等一些基礎操作
#coding=utf-8
#author=`Shichao-Dong`
from selenium.webdriver.support.ui import WebDriverWait
from logs import log
import os
import time
```
一些基礎操作:滑動、截圖、點選頁面元素等
```
class BaseOperate:
def __init__(self,driver):
self.driver = driver
def back(self):
```
返回鍵
:return:
```
os.popen("adb shell input keyevent 4")
def get_window_size(self):
```
獲取螢幕大小
:return: windowsize
```
global windowSize
windowSize = self.driver.get_window_size()
return windowSize
def swipe_up(self):
```
向上滑動
:return:
```
windowsSize = self.get_window_size()
width = windowsSize.get("width")
height = windowsSize.get("height")
self.driver.swipe(width/2, height*3/4, width/2, height/4, 1000)
def screenshot(self):
now=time.strftime("%y%m%d-%H-%M-%S")
PATH = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)
screenshoot_path = PATH(`../results/screenshoot/`)
self.driver.get_screenshot_as_file(screenshoot_path+now+`.png`)
def find_id(self,id):
```
尋找元素
:return:
```
exsit = self.driver.find_element_by_id(id)
if exsit :
return True
else:
return False
def find_name(self,name):
```
判斷頁面是否存在某個元素
:param name: text
:return:
```
findname = "//*[@text=`%s`]"%(name)
exsit = self.driver.find_element_by_xpath(findname)
if exsit :
return True
else:
return False
def get_name(self,name):
```
定位頁面text元素
:param name:
:return:
```
# element = driver.find_element_by_name(name)
# return element
findname = "//*[@text=`%s`]"%(name)
try:
element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(findname))
# element = self.driver.find_element_by_xpath(findname)
self.driver.implicitly_wait(2)
return element
except:
self.screenshot()
log.error(`未定位到元素:`+`%s`)%(name)
def get_id(self,id):
```
定位頁面resouce-id元素
:param id:
:return:
```
try:
element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_id(id))
# element = self.driver.find_element_by_id(id)
self.driver.implicitly_wait(2)
return element
except:
self.screenshot()
log.error(`未定位到元素:`+`%s`)%(id)
def get_xpath(self,xpath):
```
定位頁面xpath元素
:param id:
:return:
```
try:
element = WebDriverWait(self.driver, 10).until(lambda x: x.find_element_by_xpath(xpath))
# element = self.driver.find_element_by_xpath(xpath)
self.driver.implicitly_wait(2)
return element
except:
self.screenshot()
log.error(`未定位到元素:`+`%s`)%(xpath)
def get_ids(self,id):
```
定位頁面resouce-id元素組
:param id:
:return:列表
```
try:
# elements = self.driver.find_elements_by_id(id)
elements = WebDriverWait(self.driver, 10).until(lambda x: x.find_elements_by_id(id))
self.driver.implicitly_wait(2)
return elements
except:
self.screenshot()
log.error(`未定位到元素:`+`%s`)%(id)
def page(self,name):
```
返回至指定頁面
:return:
```
i=0
while i<10:
i=i+1
try:
findname = "//*[@text=`%s`]"%(name)
self.driver.find_element_by_xpath(findname)
self.driver.implicitly_wait(2)
break
except :
os.popen("adb shell input keyevent 4")
try:
findname = "//*[@text=`確定`]"
self.driver.find_element_by_xpath(findname).click()
self.driver.implicitly_wait(2)
except:
os.popen("adb shell input keyevent 4")
try:
self.driver.find_element_by_xpath("//*[@text=`工作臺`]")
self.driver.implicitly_wait(2)
break
except:
os.popen("adb shell input keyevent 4")
- Operate.py
我認為最關鍵的一步了,後面沒有page都是呼叫這個檔案進行測試,主要是根據讀取的yaml檔案,然後進行if…else…判斷,根據對應的operate_type分別進行對應的click、sendkeys等操作
#coding=utf-8
#author=`Shichao-Dong`
from GetYaml import getyaml
from BaseOperate import BaseOperate
class Operate:
def __init__(self,path,driver):
self.path = path
self.driver = driver
self.yaml = getyaml(self.path)
self.baseoperate=BaseOperate(driver)
def check_operate_type(self):
```
讀取yaml資訊並執行
element_info:定位元素資訊
find_type:屬性,id、xpath、text、ids
operate_type: click、sendkeys、back、swipe_up 為back就是返回,暫時就三種
上面三個必填,operate_type必填!!!!!!
send_content:send_keys 時用到
index:ids時用到
times:
:return:
```
for i in range(self.yaml.caselen()):
if self.yaml.get_operate_type(i) == `click`:
if self.yaml.get_findtype(i) == `text`:
self.baseoperate.get_name(self.yaml.get_elementinfo(i)).click()
elif self.yaml.get_findtype(i) == `id`:
self.baseoperate.get_id(self.yaml.get_elementinfo(i)).click()
elif self.yaml.get_findtype(i) == `xpath`:
self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).click()
elif self.yaml.get_findtype(i) == `ids`:
self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].click()
elif self.yaml.get_operate_type(i) == `send_keys`:
if self.yaml.get_findtype(i) == `text`:
self.baseoperate.get_name(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
elif self.yaml.get_findtype(i) == `id`:
self.baseoperate.get_id(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
elif self.yaml.get_findtype(i) == `xpath`:
self.baseoperate.get_xpath(self.yaml.get_elementinfo(i)).send_keys(self.yaml.get_send_content(i))
elif self.yaml.get_findtype(i) == `ids`:
self.baseoperate.get_ids(self.yaml.get_elementinfo(i))[self.yaml.get_index(i)].send_keys(self.yaml.get_send_content(i))
elif self.yaml.get_operate_type(i) == `back`:
for n in range(self.yaml.get_backtimes(i)):
self.baseoperate.back()
elif self.yaml.get_operate_type(i) == `swipe_up`:
for n in range(self.yaml.get_backtimes(i)):
self.baseoperate.swipe_up()
def back_home(self):
```
返回至工作臺
:return:
```
self.baseoperate.page(`工作臺`)
公共部分的程式碼就介紹這麼多,在編寫這個框架的時候,大部分精力都花在這部分,所以個人覺得還是值得好好研究的
Page部分
page部分是最小用例集,一個模組一個資料夾,以客戶為例,
目前寫了兩個用例,一個新增,一個排序,檔案如下:
程式碼如下,非常的簡潔,
import sys
reload(sys)
sys.setdefaultencoding(`utf8`)
import codecs,os
from public.Operate import Operate
from public.GetYaml import getyaml
PATH = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)
yamlpath = PATH("../../testyaml/cm/cm-001addcm.yaml")
class AddcmPage:
def __init__(self,driver):
self.path = yamlpath
self.driver = driver
self.operate = Operate(self.path,self.driver)
def operateap(self):
self.operate.check_operate_type()
def home(self):
self.operate.back_home()
執行用例
這部分用了unittest,執行所有測試用例和生成報告。
一個模組一個用例,以客戶為例:CmTest.py
from page.cm.CmAddcmPage import AddcmPage
from page.cm.CmSortcmPage import SortcmPage
from public.GetDriver import mydriver
driver = mydriver()
import unittest,time
class Cm(unittest.TestCase):
def test_001addcm(self):
```
新增客戶
:return:
```
add = AddcmPage(driver)
add.operateap()
add.home()
def test_002sortcm(self):
```
客戶排序
:return:
```
sort = SortcmPage(driver)
sort.sortlist()
sort.home()
def test_999close(self):
driver.quit()
time.sleep(10)
if __name__ == "__main__":
unittest.main()
首先從page層將需要執行的用例都import進來,然後用unittest執行即可。
如果想要執行所有的測試用例,需要用到runtest.py
import time,os
import unittest
import HTMLTestRunner
from testcase.CmTest import Cm
def testsuit():
suite = unittest.TestSuite()
suite.addTests([unittest.defaultTestLoader.loadTestsFromTestCase(Cm),
])
# runner = unittest.TextTestRunner(verbosity=2)
# runner.run(suite)
now=time.strftime("%y-%m-%d-%H-%M-%S")
PATH = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)
dirpath = PATH("./results/waiqin365-")
filename=dirpath + now +`result.html`
fp=open(filename,`wb`)
runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=`waiqin365 6.0.6beta test result`,description=u`result:`)
runner.run(suite)
fp.close()
if __name__ =="__main__":
testsuit()
這邊的思路差不多,也是先匯入再裝入suite即可
總結
就目前而言,暫時算是實現了資料與用例的分離,但是yaml的編寫要求較高,不能格式上出錯。
同時也有一些其他可以優化的地方,如:
作者:邁阿密小白
連結:https://www.jianshu.com/p/00aff8435a92
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
相關文章
- 基於PhantomFlow的自動化UI測試UI
- 自動化測試系列 —— UI自動化測試UI
- 一種基於 cypress 的 UI 自動化測試框架UI框架
- 關於Web端-UI自動化測試WebUI
- 關於 ui 自動化測試 driver 疑問?UI
- 自動化測試系列(三)|UI測試UI
- 基於Dubbo的http自動測試工具分享HTTP
- UI自動化測試實戰UI
- UI自動化測試之AirtestUIAI
- UI 自動化測試平臺UI
- 基於 Htte 的 API 自動化測試API
- UI自動化測試框架Cypress初探UI框架
- Postman實現UI自動化測試PostmanUI
- UI自動化測試工程實踐UI
- 基於Python豆瓣自動化測試【2】Python
- swift - 基於TCL的自動化測試框架Swift框架
- [opendx] 基於 appium 的移動端 UI 自動化測試平臺-介紹篇APPUI
- APP UI自動化測試思路總結APPUI
- UI自動化測試-web元素選擇UIWeb
- 前端ui自動化測試sdk封裝前端UI封裝
- 「UI 測試自動化selenium」彙總UI
- 前端自動化UI測試的完整方案前端UI
- 基於postman的api自動化測試實踐PostmanAPI
- Java + SikuliX 基於影像實現自動化測試Java
- 基於GUI的自動化軟體測試工具GUI
- 基於 HttpRunner + Django + Vue + Element UI 的介面自動化測試平臺,生產可用HTTPDjangoVueUI
- 關於 SAP UI5 應用的自動化測試方法UI
- 內部UI自動化測試培訓之python基礎UIPython
- 移動端 UI 自動化測試框架對比UI框架
- android 5個自動化測試Ui框架AndroidUI框架
- ui自動化測試,頁面方法的使用UI
- [原創]淺談Web UI自動化測試WebUI
- 搭建 WPF 上的 UI 自動化測試框架UI框架
- UI自動化測試介紹及入門UI
- 基於LangChain手工測試用例轉Web自動化測試生成工具LangChainWeb
- 基於LangChain手工測試用例轉介面自動化測試生成工具LangChain
- 基於LangChain手工測試用例轉App自動化測試生成工具LangChainAPP
- iOS自動化測試之KIF使用分享iOS