python+appium+yaml移動端自動化測試框架實(shí)現(xiàn)詳解
結(jié)構(gòu)介紹
之前分享過一篇安卓UI測試,但是沒有實(shí)現(xiàn)數(shù)據(jù)與代碼分離,后期維護(hù)成本較高,所以最近抽空優(yōu)化了一下。
不想看文章得可以直接去Github,歡迎拍磚
大致結(jié)構(gòu)如下:

testyaml管理用例,實(shí)現(xiàn)數(shù)據(jù)與代碼分離,一個模塊一個文件夾
public 存放公共文件,如讀取配置文件、啟動appium服務(wù)、讀取Yaml文件、定義日志格式等
page 存放最小測試用例集,一個模塊一個文件夾
results 存放測試報告及失敗截圖

logs 存放日志


testcase 存放測試用例runtest.py 運(yùn)行所有測試用例
yaml格式介紹
首先看下yaml文件的格式,之前也寫過一點(diǎn)關(guān)于yaml語法學(xué)習(xí)的文章
testcase部分是重點(diǎn),其中:
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: 返回次數(shù)或者上滑次數(shù)
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
代碼部分
公共部分
個人覺得核心的就是公共部分,相當(dāng)于建房子,公共部分搞好了,后面僅僅是調(diào)用即可,建房子把架子搭好,后面就添磚加瓦吧。
讀取配置文件readconfig.py
設(shè)置日志格式logs.py
獲取設(shè)備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"未找到y(tǒng)aml文件")
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服務(wù) 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端口參數(shù)
"""
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等一些基礎(chǔ)操作
#coding=utf-8
#author='Shichao-Dong'
from selenium.webdriver.support.ui import WebDriverWait
from logs import log
import os
import time
'''
一些基礎(chǔ)操作:滑動、截圖、點(diǎn)擊頁面元素等
'''
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 = "http://*[@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 = "http://*[@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 = "http://*[@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 = "http://*[@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("http://*[@text='工作臺']")
self.driver.implicitly_wait(2)
break
except:
os.popen("adb shell input keyevent 4")
Operate.py
我認(rèn)為最關(guān)鍵的一步了,后面沒有page都是調(diào)用這個文件進(jìn)行測試,主要是根據(jù)讀取的yaml文件,然后進(jìn)行if...else...判斷,根據(jù)對應(yīng)的operate_type分別進(jìn)行對應(yīng)的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信息并執(zhí)行
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()
運(yùn)行用例
這部分用了unittest,運(yùn)行所有測試用例和生成報告。
一個模塊一個用例,以客戶為例: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層將需要運(yùn)行的用例都import進(jìn)來,然后用unittest運(yùn)行即可。
如果想要運(yùn)行所有的測試用例,需要用到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()
這邊的思路差不多,也是先導(dǎo)入再裝入suite即可
總結(jié)
就目前而言,暫時算是實(shí)現(xiàn)了數(shù)據(jù)與用例的分離,但是yaml的編寫要求較高,不能格式上出錯。
同時也有一些其他可以優(yōu)化的地方,如:
- 對彈窗的判斷
- 斷開后重連機(jī)制
- 失敗后重跑機(jī)制
到此這篇關(guān)于python+appium+yaml移動端自動化測試框架實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)python appium yaml 自動化測試 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Appium+Python+pytest自動化測試框架的實(shí)戰(zhàn)
- Python+Appium自動化測試的實(shí)戰(zhàn)
- Python3 + Appium + 安卓模擬器實(shí)現(xiàn)APP自動化測試并生成測試報告
- Python+Appium實(shí)現(xiàn)自動化測試的使用步驟
- Python+appium框架原生代碼實(shí)現(xiàn)App自動化測試詳解
- Appium+Python自動化測試之運(yùn)行App程序示例
- Appium Python自動化測試之環(huán)境搭建的步驟
- Python腳本在Appium庫上對移動應(yīng)用實(shí)現(xiàn)自動化測試
- python+appium實(shí)現(xiàn)自動化測試的示例代碼
相關(guān)文章
python中torch.nn.identity()方法詳解
今天看源碼時遇到的這個恒等函數(shù),就如同名字那樣占位符,并沒有實(shí)際操作,下面這篇文章主要給大家介紹了關(guān)于python中torch.nn.identity()方法的相關(guān)資料,需要的朋友可以參考下2022-03-03
python 將numpy維度不同的數(shù)組相加相乘操作
這篇文章主要介紹了python 將numpy維度不同的數(shù)組相加相乘操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
Python利用Charles 實(shí)現(xiàn)全部自動答題思路流程分析
這篇文章主要介紹了Python利用Charles 實(shí)現(xiàn)全部自動答題思路流程分析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
python GUI實(shí)現(xiàn)小球滿屏亂跑效果
這篇文章主要為大家詳細(xì)介紹了python GUI實(shí)現(xiàn)小球滿屏亂跑效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-05-05
python3實(shí)現(xiàn)將json對象存入Redis以及數(shù)據(jù)的導(dǎo)入導(dǎo)出
這篇文章主要介紹了python3實(shí)現(xiàn)將json對象存入Redis以及數(shù)據(jù)的導(dǎo)入導(dǎo)出,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07
純python實(shí)現(xiàn)機(jī)器學(xué)習(xí)之kNN算法示例
本篇文章主要介紹了純python實(shí)現(xiàn)機(jī)器學(xué)習(xí)之kNN算法示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03

