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

框架功能

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

?測試用例封裝

?測試包管理

?截圖處理

?斷言處理

?日志獲取

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

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

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

測試案例

測試環(huán)境

Mac

Appium v1.18.3

app軟件

華為mate30

覆蓋用例

1.登錄場景

2.下單場景

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


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

driver配置封裝

kyb_caps.yaml 配置表

# 測試平臺(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í)目錄的文件或文件夾,視后面跟著的名字而定。

基類封裝

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è)務(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')

? ? # 用戶名

? ? 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')

? ? #跳過動(dòng)畫

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

? ? #跳過彈窗

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

? ? #首頁

? ? 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')

? ? #賬戶余額

? ? 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)目過程中,我們的數(shù)據(jù)可能是存儲(chǔ)在一個(gè)數(shù)據(jù)文件中,如txt,excel、csv文件類型。我們可以封裝一些方法來讀取文件中的數(shù)據(jù)來實(shí)現(xiàn)數(shù)據(jù)驅(qū)動(dòng)。

案例

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

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

enumerate()簡介

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

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

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

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

enumerate()使用

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

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

? ? foriinrange(len(list)):

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

>>>

0 這

1 是

2 一個(gè)

3 測試

4 數(shù)據(jù)

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

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

? ? forindex,iteminenumerate(list1):

? ? ? ? print(index,item)

>>>

0 這

1 是

2 一個(gè)

3 測試

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)中都是一樣的,沒有字節(jié)序的問題,也因此它實(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

測試用例封裝

1.測試用例執(zhí)行開始結(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í)行測試用例&報(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='***測試報(bào)告',description='*******登錄下單流程')

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

? ? runner.run(discover)

注意:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容