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)