自動(dòng)化框架搭建 python+appium

框架功能

?業(yè)務(wù)功能的封裝

?測(cè)試用例封裝

?測(cè)試包管理

?截圖處理

?斷言處理

?日志獲取

?測(cè)試報(bào)告生成

?數(shù)據(jù)驅(qū)動(dòng)

?數(shù)據(jù)配置

測(cè)試案例

測(cè)試環(huán)境

Mac

Appium v1.18.3

app軟件

華為mate30

覆蓋用例

1.登錄場(chǎng)景

2.下單場(chǎng)景

框架設(shè)計(jì)圖


代碼實(shí)現(xiàn)

driver配置封裝

kyb_caps.yaml 配置表

# 測(cè)試平臺(tái) iOSAndroid

platformName: Android

# 連接模擬器

# 平臺(tái)版本

#platformVersion: 6.0.1

# 設(shè)備名字

deviceName: HUAIWEI Mate 30 Pro 5G

# 連接真機(jī)

platformVersion: '10.0'

uuid: *************3142


#便于后期維護(hù)渴庆,可以只修改appname

appname: ********debug.apk(包名)

# 支持中文輸入

unicodeKeyboard: True

# 重置輸入法到原有狀態(tài)

resetKeyboard: True

# 控制是否清除session信息

noReset: False

# app的包

appPackage: com.**********

# app activity

appActivity: com.**************.******Activity

# appium serverIPport

ip: 127.0.0.1

port: 4723

desired_caps.py文件

from appium import webdriver

import yaml

import logging

import logging.config

import os

CON_LOG='../config/log.conf'

logging.config.fileConfig(CON_LOG)

logging=logging.getLogger()

def appium_desired():

? ? with open('../config/********caps.yaml', 'r', encoding='utf-8') as file:

? ? ? ? data = yaml.load(file,Loader = yaml.FullLoader)

? ? desired_caps={}

? ? desired_caps['platformName'] = data['platformName']

? ? desired_caps['platformVersion'] = data['platformVersion']

? ? desired_caps['deviceName'] = data['deviceName']

? ? desired_caps['uuid'] = data['uuid']

? ? base_dir = os.path.dirname(os.path.dirname(__file__))

? ? app_path = os.path.join(base_dir, 'appname', data['appname'])

? ? desired_caps['appname'] = app_path

? ? desired_caps['appPackage'] = data['appPackage']

? ? desired_caps['appActivity'] = data['appActivity']

? ? desired_caps['noReset'] = data['noReset']

? ? desired_caps['unicodeKeyboard'] = data['unicodeKeyboard']

? ? desired_caps['resetKeyboard'] = data['resetKeyboard']

? ? logging.info('start app...')

? ? # driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)

? ? driver = webdriver.Remote('http://' + str(data['ip']) + ':' + str(data['port']) + '/wd/hub',desired_caps)

? ? return driver

if __name__ == '__main__':

? ? appium_desired()

相對(duì)路徑符號(hào)含義

“.”表示當(dāng)前目錄

“..” 表示當(dāng)前目錄的上一級(jí)目錄。

“./”表示當(dāng)前目錄下的某個(gè)文件或文件夾,視后面跟著的名字而定

“../”表示當(dāng)前目錄上一級(jí)目錄的文件或文件夾抢野,視后面跟著的名字而定团赁。

基類(lèi)封裝

baseView.py文件

class BaseView(object):

? ? def __init__(self,driver):

? ? ? ? self.driver = driver

? ? def find_element(self,*loc):

? ? ? ? return self.driver.find_element(*loc)

? ? def find_elements(self,*loc):

? ? ? ? return self.driver.find_elements(*loc)

? ? def get_window_size(self):

? ? ? ? return self.driver.get_window_size()

? ? def swipe(self,start_x,start_y,end_x,end_y,duration):

? ? ? ? return self.driver.swipe(start_x,start_y,end_x,end_y,duration)

common公共模塊封裝

公共方法封裝 : common_fun.py

from baseView.baseViews import BaseView

from common.desired_caps import appium_desired

from selenium.common.exceptions import NoSuchElementException

import logging

from selenium.webdriver.common.by import By

import time,os

import csv

class Common(BaseView):

#當(dāng)前時(shí)間

def gettime(self):

? ? self.now = time.strftime("%Y-%m-%d %H_%M_%S")

? ? return self.now

#截圖

def getScreenShot(self,module):

? ? time =self.gettime()

? ? image_file = os.path.dirname(os.path.dirname(__file__))+'/screenshots/%s_%s.png' %(module,time)

? ? print(image_file)

? ? logging.info('get %s screenshot' %module)

? ? self.driver.get_screenshot_as_file(image_file)

#讀取csv文件

def get_csv_data(self,csv_file,line):

? ? logging.info('========get_csv_data=========')

? ? with open(csv_file,'r',encoding='utf-8-sig') as file:

? ? ? ? reader = csv.reader(file)

? ? ? ? for index,row in enumerate(reader,1):

? ? ? ? ? ? if index == line:

? ? ? ? ? ? ? ? return row

#獲取屏幕尺寸

def get_size(self):

? ? x =self.driver.get_window_size()['width']

? ? y =self.driver.get_window_size()['height']

? ? return x, y


if __name__ == '__main__':

? ? driver = appium_desired()

? ? com = Common(driver)

? ? com.swipeLeft()

? ? com.getScreenShot(str('startapp'))

業(yè)務(wù)模塊封裝

1.登錄頁(yè)面業(yè)務(wù)邏輯模塊

loginView.py

import logging

from common.commin_fun import Common,NoSuchElementException

from common.desired_caps import appium_desired

from selenium.webdriver.common.by import By

from page.loginpage import LoginPage

import time

#登錄

class LoginView(Common):

? ? # 跳轉(zhuǎn)登錄

? ? logBtn = (By.ID, '*********:id/t*******e')

? ? # 用戶(hù)名

? ? username_type = (By.XPATH,'/**************************ditText')

? ? # 密碼

? ? password_type = (By.XPATH,'/*****************itText')

? ? # 登錄按鈕

? ? loginBtn = (By.ID, 'com.**********:id/bt****gin')

? ? # homepage = (By.ID,'com.***********:id/iv**nt')

? ? tip_commit = (By.ID, 'com.***********:id/tv_****dits')

? ? def login_action(self,username,password):

? ? ? ? logging.info('click logBtn')

? ? ? ? self.driver.find_element(*self.logBtn).click()

? ? ? ? logging.info('click logBtn finished')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('=========login_action=======')

? ? ? ? logging.info('username is:%s' %username)

?? ? ? self.driver.find_element(*self.username_type).send_keys(username)

? ? ? ? logging.info('usernameinput finished')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('=========login_action=======')

? ? ? ? logging.info('password is:%s' %password)

?? ? ? self.driver.find_element(*self.password_type).send_keys(password)

? ? ? ? logging.info('passwordinput finished')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('click loginBtn')

? ? ? ? self.driver.find_element(*self.loginBtn).click()

? ? ? ? logging.info('login loginBtn finished')

? ? ? ? time.sleep(3)

? ? def check_account_alert(self):

? ? ? ? logging.info('============check_account_alert===========')

? ? ? ? try:

? ? ? ? ? ? element = self.driver.find_element(self.tip_commit)

? ? ? ? except NoSuchElementException:

? ? ? ? ? ? pass

? ? ? ? else:

? ? ? ? ? ? logging.info('Logged in')

? ? def check_login_staus(self):

? ? ? ? logging.info('=======check_login_staus========')

? ? ? ? # self.check_market_ad()

? ? ? ? self.check_account_alert()

? ? ? ? try:

? ? ? ? ? ? self.driver.find_element(*self.tip_commit)

? ? ? ? except NoSuchElementException:

? ? ? ? ? ? logging.error('login Fail')

? ? ? ? ? ? self.getScreenShot('login Fail')

? ? ? ? ? ? return False

? ? ? ? else:

? ? ? ? ? ? logging.info('===========login success==============')

? ? ? ? ? ? return True

if __name__ == '__main__':

? ? driver = appium_desired()

? ? l = LoginView(driver)

? ? l.login_action('1584********692','q******************/an******View')

? ? #跳過(guò)動(dòng)畫(huà)

? ? skip_animation = (By.ID,'com.*********:id/skip')

? ? #跳過(guò)彈窗

? ? skip_popup = (By.ID,'com.**********:id/tv_ok')

? ? #首頁(yè)

? ? homepage = (By.ID, 'com.*******:id/iv*****ent')


? ? bj_cbd = (By.ID,'com.*************:id/ite****mg')


? ? reserve_room = (By.XPATH,'/***********************out')

? ? #同意

? ? agree = (By.ID,'com.**********:id/c*********s')

? ? #提交訂單

? ? submit = (By.ID,'com.***************:id/b************it')

? ? #立即支付

? ? immediate_payment = (By.ID,'com.**************id/b*******y')

? ? #賬戶(hù)余額

? ? account_balance = (By.ID,'com.***************id/c***********nt')

? ? #確認(rèn)支付

? ? confirm_payment = (By.ID,'com.*********************id/b*******y')

? ? def Yizhan_xiadan(self):

? ? ? ? logging.info('click homepage')

? ? ? ? self.driver.find_element(*self.homepage).click()

? ? ? ? logging.info('login homepage finished')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('=========click? yizhan_homepage=========')

? ? ? ? self.driver.find_element(*self.yizhan_homepage).click()

? ? ? ? logging.info('==============click yizhan_homepage finished===============')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('=========click? skip_animation=========')

? ? ? ? self.driver.find_element(*self.skip_animation).click()

? ? ? ? logging.info('==============click skip_animation finished===============')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('=========click? skip_popup=========')

? ? ? ? self.driver.find_element(*self.skip_popup).click()

? ? ? ? logging.info('==============click skip_popup finished===============')

? ? ? ? time.sleep(3)

? ? ? ? logging.info('============swipe up=============')

? ? ? ? self.swipe(868,1735,868,757,500)

? ? ? ? logging.info('============swipe up? finished=============')

? ? ? ? logging.info('==============cilck? bj_cbd=================')

? ? ? ? self.driver.find_element(*self.bj_cbd).click()

? ? ? ? logging.info('==============bj_cbd? finished===============')

? ? ? ? logging.info('============swipe up two=============')

? ? ? ? self.swipe(835, 1857, 835, 776, 696)

? ? ? ? logging.info('============swipe up two finished=============')

? ? ? ? logging.info('==================click reserve_room==============')

? ? ? ? self.driver.find_element(*self.reserve_room).click()

? ? ? ? logging.info('============reserve_room finished=============')

? ? ? ? logging.info('==================click agree==============')

? ? ? ? self.driver.find_element(*self.agree).click()

? ? ? ? logging.info('============agree finished=============')

? ? ? ? logging.info('==================click submit==============')

? ? ? ? self.driver.find_element(*self.submit).click()

? ? ? ? logging.info('============submit finished=============')

? ? ? ? logging.info('==================click immediate_payment==============')

? ? ? ? self.driver.find_element(*self.immediate_payment).click()

? ? ? ? logging.info('============immediate_payment finished=============')

? ? ? ? logging.info('==================click account_balance==============')

? ? ? ? self.driver.find_element(*self.account_balance).click()

? ? ? ? logging.info('============account_balance finished=============')

? ? ? ? logging.info('==================click confirm_payment==============')

? ? ? ? self.driver.find_element(*self.confirm_payment).click()

? ? ? ? logging.info('============confirm_payment finished=============')

data數(shù)據(jù)封裝

使用背景

在實(shí)際項(xiàng)目過(guò)程中瞎暑,我們的數(shù)據(jù)可能是存儲(chǔ)在一個(gè)數(shù)據(jù)文件中匕坯,如txt,excel掠抬、csv文件類(lèi)型氮块。我們可以封裝一些方法來(lái)讀取文件中的數(shù)據(jù)來(lái)實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)绍载。

案例

將測(cè)試賬號(hào)存儲(chǔ)在account.csv文件,內(nèi)容如下:

130*******745q******

enumerate()簡(jiǎn)介

enumerate()是python的內(nèi)置函數(shù)

enumerate在字典上是枚舉滔蝉、列舉的意思

對(duì)于一個(gè)可迭代的(iterable)/可遍歷的對(duì)象(如列表击儡、字符串),enumerate將其組成一個(gè)索引序列蝠引,利用它可以同時(shí)獲得索引和值

enumerate多用于在for循環(huán)中得到計(jì)數(shù)阳谍。

enumerate()使用

如果對(duì)一個(gè)列表,既要遍歷索引又要遍歷元素時(shí)立肘,首先可以這樣寫(xiě):

list= ["這", "是", "一個(gè)", "測(cè)試","數(shù)據(jù)"]

? ? foriinrange(len(list)):

? ? ? ? print(i,list[i])

>>>

0 這

1 是

2 一個(gè)

3 測(cè)試

4 數(shù)據(jù)

上述方法有些累贅边坤,利用enumerate()會(huì)更加直接和優(yōu)美:

list1 = ["這", "是", "一個(gè)", "測(cè)試","數(shù)據(jù)"]

? ? forindex,iteminenumerate(list1):

? ? ? ? print(index,item)

>>>

0 這

1 是

2 一個(gè)

3 測(cè)試

4 數(shù)據(jù)

數(shù)據(jù)讀取方法封裝

import csv

def get_csv_data(self,csv_file,line):

? ? logging.info('========get_csv_data=========')

? ? with open(csv_file,'r',encoding='utf-8-sig') as file:

? ? ? ? reader = csv.reader(file)

? ? ? ? for index,row in enumerate(reader,1):

? ? ? ? ? ? if index == line:

? ? ? ? ? ? ? ? return row

? ? csv_file='../data/account.csv'

? ? data=get_csv_data(csv_file,3)

? ? print(data)

utf-8utf-8-sig兩種編碼格式的區(qū)別

UTF-8以字節(jié)為編碼單元,它的字節(jié)順序在所有系統(tǒng)中都是一樣的谅年,沒(méi)有字節(jié)序的問(wèn)題茧痒,也因此它實(shí)際上并不需要BOM(“ByteOrder Mark”)。但是UTF-8 with BOM即utf-8-sig需要提供BOM融蹂。

config文件配置

日志文件配置 log.config

[loggers]

keys=root,infoLogger

[logger_root]

level=DEBUG

handlers=consoleHandler,fileHandler

[logger_infoLogger]

handlers=consoleHandler,fileHandler

qualname=infoLogger

propagate=0

[handlers]

keys=consoleHandler,fileHandler

[handler_consoleHandler]

class=StreamHandler

level=INFO

formatter=form02

args=(sys.stderr,)

[handler_fileHandler]

class=FileHandler

level=INFO

formatter=form01

args=('../logs/runlog.log', 'a')

[formatters]

keys=form01,form02

[formatter_form01]

format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

[formatter_form02]

format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

測(cè)試用例封裝

1.測(cè)試用例執(zhí)行開(kāi)始結(jié)束操作封裝myunit.py

import unittest

from common.desired_caps import appium_desired

import logging

from time import sleep

class StartEnd(unittest.TestCase):

? ? def setUp(self):

? ? ? ? logging.info('=========start============')

? ? ? ? self.driver = appium_desired()

? ? def tearDown(self):

? ? ? ? logging.info('===========end==========')

? ? ? ? sleep(5)

? ? ? ? self.driver.close_app()

2.登錄和下單用例:test_login.py

from common.myunit import StartEnd

from businessView.loginVIew import LoginView

from businessView.homePage import HomePage

from businessView.misu_xiadan import YizhanXiadan

import logging,unittest

from BSTestRunner import BSTestRunner

import time

#登錄

class LoginYest(StartEnd):

? ? csv_file ='../data/account.csv'

? ? def test_login_cg(self):

? ? ? ? logging.info('===============test_page===============')

? ? ? ? h = HomePage(self.driver)

? ? ? ? l = LoginView(self.driver)

? ? ? ? y = YizhanXiadan(self.driver)

? ? ? ? data = l.get_csv_data(self.csv_file, 1)

? ? ? ? h.get_into()

? ? ? ? l.login_action(data[0], data[1])

? ? ? ? self.assertTrue(l.check_login_staus(), msg=('login fail!!!'))

? ? ? ? time.sleep(3)

? ? ? ? y.Yizhan_xiadan()

if __name__ == '__main__':

? ? unittest.main()

執(zhí)行測(cè)試用例&報(bào)告生成

BSTestRunner下載地址

run.py文件

from BSTestRunner import BSTestRunner

import unittest

import time,logging

test_dir = '../test_case'

report_dir = '../reports'

discover = unittest.defaultTestLoader .discover(test_dir,pattern='test_login.py')

now = time.strftime('%Y-%m-%d %H_%M_%S')

report_name = report_dir + '/' + now + 'test_report.html'

with open(report_name,'wb') as f:

? ? runner = BSTestRunner(stream=f,title='***測(cè)試報(bào)告',description='*******登錄下單流程')

? ? logging.info('start run rest case')

? ? runner.run(discover)

注意:

pattern參數(shù)可以控制運(yùn)行不同模塊的用例旺订,如下所示表示運(yùn)行指定路徑以test開(kāi)頭的模塊

discover = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市超燃,隨后出現(xiàn)的幾起案子区拳,更是在濱河造成了極大的恐慌,老刑警劉巖意乓,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件樱调,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)笆凌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)圣猎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人乞而,你說(shuō)我怎么就攤上這事送悔。” “怎么了爪模?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵欠啤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我屋灌,道長(zhǎng)洁段,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任声滥,我火速辦了婚禮眉撵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘落塑。我一直安慰自己纽疟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布憾赁。 她就那樣靜靜地躺著污朽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪龙考。 梳的紋絲不亂的頭發(fā)上蟆肆,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音晦款,去河邊找鬼炎功。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缓溅,可吹牛的內(nèi)容都是我干的蛇损。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼坛怪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淤齐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起袜匿,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤更啄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后居灯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體祭务,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡内狗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了义锥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片其屏。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缨该,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情川背,我是刑警寧澤贰拿,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站熄云,受9級(jí)特大地震影響膨更,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缴允,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一荚守、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧练般,春花似錦矗漾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至摄职,卻和暖如春誊役,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谷市。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工蛔垢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人迫悠。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓鹏漆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親及皂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甫男,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容