Appium+unittest+PageObject自动化测试框架综合实践
Appium自动化测试框架如下图:
框架中包含的脚本以此如下展示:
1.app目录下存放着测试需要的apk包
2.baseView目录下脚本中封装着所有页面需要的方法
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-04 17:17 # @Author : zhouyang # @File : 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)
3.businessView目录下存放着所有页面具体实现的方法,比如登录页面,注册页面
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-05 11:42 # @Author : zhouyang # @File : loginView.py from common.desired_caps import appium_desired from common.commom_fun import Commom import logging from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException class LoginView(Commom): username_type=(By.ID,'com.tal.kaoyan:id/login_email_edittext') password_type=(By.ID,'com.tal.kaoyan:id/login_password_edittext') submit_type=(By.ID,'com.tal.kaoyan:id/login_login_btn') tip_commit=(By.ID,'com.tal.kaoyan:id/tip_commit') button_mysefl=(By.ID,'com.tal.kaoyan:id/mainactivity_button_mysefl') usercenter_username=(By.ID,'com.tal.kaoyan:id/activity_usercenter_username') RightButton_textview=(By.ID,'com.tal.kaoyan:id/myapptitle_RightButton_textview') logout_text=(By.ID,'com.tal.kaoyan:id/setting_logout_text') def login_action(self,username,password): self.check_cancleBtn() self.check_skipBtn() logging.info('=================login===================') logging.info('input username:%s'%username) self.driver.find_element(*self.username_type).send_keys(username) logging.info('input password:%s' %password) self.driver.find_element(*self.password_type).send_keys(password) logging.info('click loginBtn') self.driver.find_element(*self.submit_type).click() logging.info('===============login finish==============') 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('=======close alert=======') element.click() def check_loginStatus(self): logging.info('======check_loginStatus======') self.check_market_ad() self.check_account_alert() try: self.driver.find_element(*self.button_mysefl).click() element=self.driver.find_element(*self.usercenter_username) except NoSuchElementException: logging.error('========login false=========') self.get_screenshot('login fail') return False else: logging.info('=========login success==========') self.logout_action() return True def logout_action(self): logging.info('=========logout action=========') self.driver.find_element(*self.RightButton_textview).click() self.driver.find_element(*self.logout_text).click() self.driver.find_element(*self.tip_commit).click() if __name__ == '__main__': driver=appium_desired() l=LoginView(driver) l.login_action('自学网2018','zxw208') l.check_loginStatus()
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-18 11:50 # @Author : zhouyang # @File : register.py '''注册模块''' from common.commom_fun import Commom from common.desired_caps import appium_desired import logging from selenium.webdriver.common.by import By from selenium.common.exceptions import NoSuchElementException from random import randint class RegisterVeiw(Commom): register_text=(By.ID,'com.tal.kaoyan:id/login_register_text') register_userheader=(By.ID,'com.tal.kaoyan:id/activity_register_userheader') item_image=(By.ID,'com.tal.kaoyan:id/item_image') save=(By.ID,'com.tal.kaoyan:id/save') register_username=(By.ID,'com.tal.kaoyan:id/activity_register_username_edittext') register_password=(By.ID,'com.tal.kaoyan:id/activity_register_password_edittext') register_email=(By.ID,'com.tal.kaoyan:id/activity_register_email_edittext') register_btn=(By.ID,'com.tal.kaoyan:id/activity_register_register_btn') perfectinfomation_time=(By.ID,'com.tal.kaoyan:id/activity_perfectinfomation_time') text1=(By.ID,'android:id/text1') school_name=(By.ID,'com.tal.kaoyan:id/perfectinfomation_edit_school_name') forum_title=(By.ID,'com.tal.kaoyan:id/more_forum_title') university=(By.ID,'com.tal.kaoyan:id/university_search_item_name') perfectinfomation_major=(By.ID,'com.tal.kaoyan:id/activity_perfectinfomation_major') subject=(By.ID,'com.tal.kaoyan:id/major_subject_title') group=(By.ID,'com.tal.kaoyan:id/major_group_title') search_item=(By.ID,'com.tal.kaoyan:id/major_search_item_name') perfectinfomation_goBtn=(By.ID,'com.tal.kaoyan:id/activity_perfectinfomation_goBtn') button_mysefl = (By.ID, 'com.tal.kaoyan:id/mainactivity_button_mysefl') usercenter_username = (By.ID, 'com.tal.kaoyan:id/activity_usercenter_username') def register_action(self,username,password,email): logging.info('========register_action=========') self.check_cancleBtn() self.check_skipBtn() self.driver.find_element(*self.register_text).click() #点击注册 #添加头像 logging.info('set userheader') self.driver.find_element(*self.register_userheader).click() self.driver.find_elements(*self.item_image)[1].click() self.driver.find_element(*self.save).click() #填写用户名、密码、Email logging.info('username is %s'%username) self.driver.find_element(*self.register_username).send_keys(username) logging.info('password is %s' % password) self.driver.find_element(*self.register_password).send_keys(password) logging.info('email is %s' % email) self.driver.find_element(*self.register_email).send_keys(email) # 点击立即注册 logging.info('register') self.driver.find_element(*self.register_btn).click() #判断是否进入注册信息页面 try: self.driver.find_element(*self.perfectinfomation_time) except NoSuchElementException: logging.error('regiter fail') self.get_screenshot('regiter fail') return False else: self.add_register_info() if self.check_registerStatus(): return True else: return False def add_register_info(self): logging.info('=======add_register_info=======') #填写年份 self.driver.find_element(*self.perfectinfomation_time).click() self.driver.find_elements(*self.text1)[1].click() #2015 #选择学校 logging.info('select school') self.driver.find_element(*self.school_name).click() self.driver.find_elements(*self.forum_title)[1].click() self.driver.find_elements(*self.university)[1].click() #选择专业 logging.info('select major') self.driver.find_element(*self.perfectinfomation_major).click() self.driver.find_elements(*self.subject)[1].click() self.driver.find_elements(*self.group)[2].click() self.driver.find_elements(*self.search_item)[0].click() #注册 self.driver.find_element(*self.perfectinfomation_goBtn).click() def check_registerStatus(self): logging.info('========check_registerStatus==========') self.check_market_ad() try: self.driver.find_element(*self.button_mysefl).click() element = self.driver.find_element(*self.usercenter_username) except NoSuchElementException: logging.error('========register false=========') self.get_screenshot('register fail') return False else: logging.info('=========register success==========') return True if __name__ == '__main__': driver=appium_desired() r=RegisterVeiw(driver) username='zxw2000'+'fly'+str(randint(1000,9999)) password='zxw2000'+str(randint(1000,9999)) email='zxw2000'+str(randint(1000,9999))+'@163.com' r.register_action(username,password,email)
4.common目录下存放着公共方法
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-04 17:22 # @Author : zhouyang # @File : commom_fun.py from common.desired_caps import appium_desired from baseView.baseView import BaseView from selenium.common.exceptions import NoSuchElementException import logging,time,os,csv from selenium.webdriver.common.by import By class Commom(BaseView): cancelBtn=(By.ID,'android:id/button2') skipBtn=(By.ID,'com.tal.kaoyan:id/tv_skip') wemedia_cacel=(By.ID,'com.tal.kaoyan:id/view_wemedia_cacel') def check_cancleBtn(self): logging.info('===========check cancleBtn==========') try: cancelBtn = self.driver.find_element(*self.cancelBtn) except NoSuchElementException: logging.info('no cancelBtn') else: cancelBtn.click() def check_skipBtn(self): logging.info('===========check cancelBtn===========') try: skipBtn = self.driver.find_element(*self.skipBtn) except NoSuchElementException: logging.info('no cancelBtn') else: skipBtn.click() def get_size(self): x = driver.get_window_size()['width'] y = driver.get_window_size()['height'] return (x, y) def swipeLeft(self): l = self.get_size() x1 = int(l[0] * 0.9) x2 = int(l[0] * 0.1) y1 = int(l[1] * 0.5) driver.swipe(x1, y1, x2, y1, 1000) def getTime(self): self.now=time.strftime('%Y_%m_%d %H-%M-%S') return self.now def get_screenshot(self,module): time=self.getTime() image_file=os.path.dirname(os.path.dirname(__file__))+'/screenshot/%s_%s.png' %(module,time) logging.info('get %s screenshot'%module) self.driver.get_screenshot_as_file(image_file) def check_market_ad(self): logging.info('========check_market_ad=========') try: element=self.driver.find_element(*self.wemedia_cacel) except NoSuchElementException: pass else: logging.info('========close market========') element.click() 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 if __name__ == '__main__': driver=appium_desired() com=Commom(driver) com.check_cancleBtn() com.check_skipBtn() # def get_csv_data(csv_file,line): # 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,1) # print(data) # lists=['这','是','一个','列表'] # for index,row in enumerate(lists): # print(index,row)
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-03 11:05 # @Author : zhouyang # @File : capability_yaml.py ''' 从desired_caps.yaml文件中获取capability数据,登录考研帮app,把日志保存在文件中 ''' 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/desired_caps.yaml','r',encoding='utf-8') as file: data = yaml.load(file) desired_caps = {} desired_caps['platformName'] = data['platformName'] desired_caps['platformVerion'] = data['platformVersion'] desired_caps['deviceName'] = data['deviceName'] #app使用相对路径 base_dir = os.path.dirname(os.path.dirname(__file__)) app_dir = os.path.join(base_dir, 'app', data['appname']) desired_caps['app'] = app_dir desired_caps['noReset'] = data['noReset'] desired_caps['appPackage'] = data['appPackage'] desired_caps['appActivity'] = data['appActivity'] desired_caps['unicodeKeyboard'] = data['unicodeKeyboard'] desired_caps['resetKeyboard'] = data['resetKeyboard'] logging.info('start info...') driver = webdriver.Remote('http://' + str(data['ip']) + ':' + str(data['port']) + '/wd/hub', desired_caps) driver.implicitly_wait(8) return driver if __name__ == '__main__': appium_desired()
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-05 16:18 # @Author : zhouyang # @File : myunit.py from common.desired_caps import appium_desired import logging import unittest from time import sleep class StartEnd(unittest.TestCase): def setUp(self): logging.info('===============setup==============') self.driver=appium_desired() def tearDown(self): logging.info('=============teardown============') sleep(5) self.driver.close_app()
5.config目录下存放所有配置文件,其中yaml文件中“:”后面一定要空一个格,否则会报错;.conf文件中内容,日志存放路径可以修改,日志表现形式等都可以修改
desired_caps.yaml
platformName: Android platformVersion: 4.4.2 deviceName: 127.0.0.1:62001 #真机 #platformVersion: 4.4.2 #udid: #deviceName: 127.0.0.1:62001 appname: kaoyan3.1.0.apk appPackage: com.tal.kaoyan appActivity: com.tal.kaoyan.ui.activity.SplashActivity noReset: False unicodeKeyboard: True resetKeyboard: True ip: 127.0.0.1 port: 4723
log.conf
[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.stdout,) [handler_fileHandler] class=FileHandler level=INFO formatter=form01 args=('../logs/runlog_conf.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
6.data目录中存放的是测试需要的数据,比如csv文件,Excel文件等
7.logs目录下存放测试过程中产生的日志,存放目录和日志名及日志内容均取决于log.conf文件中的
args=('../logs/runlog_conf.log', 'a'),a表示追加,否则第二次执行测试后产生的日志会覆盖
8.reports目录存放测试过程中产生的测试报告,测试报告的路径,名称,内容等都在run_test.py文件中定义
9.screenshot目录存放测试过程中的截图,路径,名称等都取决于common目录下的common_fun.py文件下的get_screenshot()方法
10.test_case目录下存放着所有的测试用例
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-05 16:24 # @Author : zhouyang # @File : test_login.py ''' unittest写的测试用例 ''' import unittest from common.myunit import StartEnd from businessView.loginView import LoginView import logging class Test_Login(StartEnd): def test_login_zxw2018(self): logging.info('=========test_login_zxw2018=========') l=LoginView(self.driver) l.login_action('自学网2018','zxw2018') self.assertTrue(l.check_loginStatus()) def test_login_zxw2017(self): logging.info('=========test_login_zxw2017=========') l=LoginView(self.driver) l.login_action('自学网2017','zxw2017') self.assertTrue(l.check_loginStatus()) def test_login_error(self): logging.info('=========test_login_error=========') l=LoginView(self.driver) l.login_action('123','456') self.assertTrue(l.check_loginStatus(),msg='login fail') if __name__ == '__main__': unittest.main()
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-22 16:32 # @Author : zhouyang # @File : test_regiter.py '''注册测试用例''' from common.myunit import StartEnd from businessView.register import RegisterVeiw import logging from random import randint import unittest class RegisterTest(StartEnd): def test_user_register(self): logging.info('=======start regiter========') r = RegisterVeiw(self.driver) username = 'zxw2000' + 'fly' + str(randint(1000, 9999)) password = 'zxw2000' + str(randint(1000, 9999)) email = 'zxw2000' + str(randint(1000, 9999)) + '@163.com' self.assertTrue(r.register_action(username, password, email)) if __name__ == '__main__': unittest.main()
11.test_run目录下存放着run_test.py文件,用于执行测试,生成测试报告,也可以在里面添加发送邮件功能,生成的测试报告直接以邮件形式发送到邮箱
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019-07-22 16:42 # @Author : zhouyang # @File : run_test.py '''执行测试用例,生成测试报告''' import unittest import logging # from BSTestRunner import BSTestRunner from HTMLTestRunner import HTMLTestRunner import time #指定测试用例和测试报告的路径 report_dir='../reports/' test_dir='../test_case' #加载测试用例 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=HTMLTestRunner(stream=f,title=u'login test repotr',description=u'kyb login test report') logging.info('start run testcasse......') runner(discover)