[项目地址点击这里],整体项目框架通过 python+appium+unittest 实现,参照项目源码食用本篇文章更佳。
今年新换的工作以 PC 客户端和 web 为主,很少接触到 APP,怕手生给忘了,刚好最近觉得手机里面现在越来越多的 APP 需要每天进行签到,点赞,评论,定时给某人发消息,转账等操作,就用 APP 自动化来给自己的生活带点便利吧。python+appium+unittest 写好框架,以后有新的 APP 只要用几分钟时间把这个 APP 的任务添加一下就好了,下文中的每一条 APP 任务实际就是自动化测试中的每一条测试用例。
此项目只有在执行任务的时候才会启动服务和安卓模拟器,执行完毕后自动关闭所有服务和模拟器进程,其他时段不会占用浪费机器资源,另外写此框架的主要目的是替代手工每天能定时跑,所以在任务运行时段 Windows 机器需要正常稳定,一般放在半夜凌晨执行。
以下是我使用时候的版本,根据自己情况安装所需版本就行。
手机连接电脑或者打开电脑上的安卓模拟器,在 cmd 命令行直接输入 adb devices 查看设备名,下面的emulator-5554
就是设备名。
需要注意的是,有些模拟器自带的 adb.exe 工具与 SDK 自带的 adb 会冲突,具体表现就是打开了模拟器后,adb 命令不能正常执行,因为进程冲突了,解决办法是在模拟器安装路径找到 adb.exe,把它重新命名或者直接删除。
C:\Users\pei>adb devices
List of devices attached
emulator-5554 device
appium -a 127.0.0.1 -p 4723 -bp 4728 --chromedriver-port 9519 -U emulator-5554
注:如果只连接了一台设备的话,上面这些参数也可以不用指定,直接在命令行写appium
启动就好。
以上命令通过 python 脚本调用系统命令执行以后不太方便直接中断进程,因此换一个思路,将它写到.bat 批处理文件,通过脚本来启动执行这个文件,任务结束后,再启动一个批处理文件,获取启动 appium 服务的窗口句柄并用 taskkill 强制关闭,实现方法分别如下。
rem 启动appium服务
@echo off
title start_appium_server
cmd /c "appium -a 127.0.0.1 -p 4723 -bp 4728 --chromedriver-port 9519 -U emulator-5554"
rem 关闭appium服务
@echo off
title stop_appium_server
tasklist -v | find "start_appium_server">nul
if %errorlevel%==0 (
taskkill -fi "WINDOWTITLE eq start_appium_server"
)
在安卓模拟器的安装目录找到启动模拟器的可执行文件,将它的路径复制出来,在 cmd 里输入start +路径
命令就能直接启动模拟器,例如我这里是雷电模拟器,其他的类似:
start D:\Application\dnplayer2\dnplayer.exe
关闭模拟器的话直接 taskkill 结束进程名就好。
taskkill -f -im dnplayer.exe
将这两个命令写到一个 python 文件的两个函数中,方便后面统一调用执行。
#coding=utf-8
import os
def start_android_devices():
'''启动安卓模拟器'''
command = r'start D:\Application\dnplayer2\dnplayer.exe'
os.system(command)
print('模拟器启动成功')
def stop_android_devices():
'''结束安卓模拟器进程'''
command = r'taskkill -f -im dnplayer.exe'
os.system(command)
print('所有任务执行完毕,关闭模拟器')
这部分是整个框架的主体部分,每个 APP 的执行代码都写在这里,用到 unittest 单元测试框架,所有的函数写在AppTask
类中,AppTask 继承 unittest.TestCase 方法。
基础模块封装了全部 APP 任务都要用到的公共方法,这里分为两部分。
一是每个 APP 任务的第一步,即启动 APP,此处没有用 unittest 自带的 setUp() 方法,而是自定义了一个 basic 函数,这个函数接受两个参数,分别是 APP 的包名和启动 APP 的 activity 名,APP 任务的第一步都是先调用这个公共函数,将driver
定义成全局变量。
注意:webdriver.Remote
方法第一个 url 参数中间的端口号要与上面启动 appium server 的端口号一致,默认是 4723。
def basic(package_name,activity_name):
'''启动应用'''
global driver
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = package_name
desired_caps['appActivity'] = activity_name
desired_caps["unicodeKeyboard"] = "True"
desired_caps["resetKeyboard"] = "True"
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
time.sleep(20)
二是每个 APP 任务结束后退出应用,释放 appium 实例,将关闭 APP 的代码写在 unittest 自带的 tearDown() 方法里面,这个方法不用写在整个文件的最后面,每执行一个 test 开头的任务后,都会自动执行 tearDown()。
def tearDown(self):
'''关闭应用'''
driver.quit()
每一个 APP 任务都写成一个函数,为了使用 unittest 框架快速批量执行,命名都以test
开头,例如京东金融test_001_jd_financ
,然后在该函数的第一步先调用上面封装的 basic(package_name,activity_name) 函数来启动应用,两个参数分别就是京东金融 APP 的包名和启动 APP 的 activity 名,下面以京东金融为例。
def test_001_jd_finance(self):
'''京东金融签到/领取提额包'''
#----------启动应用----------
AppTask.basic('com.jd.jrapp','.WelcomeActivity')
#----------九宫格滑动解锁----------
TouchAction(driver).press(x=180, y=598).move_to(x=0, y=0).wait(100).move_to(x=0, y=181).wait(100).move_to(x=0, y=181).wait(100).move_to(x=181, y=0).wait(100).move_to(x=181,y=0).release().perform()
time.sleep(2)
# ----------检验是否有更新----------
update = driver.page_source.find('跳过') #判断是否有更新按钮
if update != -1:
driver.find_element_by_id('com.jd.jrapp:id/cancel').click() #点击"跳过",不更新
time.sleep(1)
else:
pass
#----------个人中心签到----------
driver.find_element_by_id('com.jd.jrapp:id/fourthLayout').click() #点击个人中心
time.sleep(1)
driver.swipe(100,500,100,500,10) #点击"签到"按钮
time.sleep(20)
#----------断言是否成功----------
self.assertIn('已签', driver.page_source,msg='任务有失败,请到截图目录查看截图'+str(screenshot_path))
每个 APP 任务的最后面都用 assert 来断言,断言方法有很多种 (assertIn,assertNotIn,assertEqual...),这里只判断页面中有没有签到成功的字样,就用 assertIn 包含关系来断言,第一个参数包含在第二个参数内,第二个参数用 driver.page_source 方法获取当前页面所有元素,msg 参数是提示信息。
具体到每个 APP 任务里面除了基础的功能实现外,最重要的就是做好各种容错处理,哪个页面突然就有了活动或者广告弹窗,又或者应用突然跳出更新的窗口等等,就会打断任务的执行过程,因此在写脚本的时候尽量熟悉 APP 的操作逻辑,对弹窗做好容错处理,并加上适量的打印信息和截图方便调试和排查错误。
目前任务并不是很多所有 APP 都写在一个 python 文件里,以后有新增的任务就直接在这个文件最下面添加新的函数即可,后期如果任务较多可以考虑分开,每个任务单独一个 python 文件,或者同类型的任务写在一个文件。
#coding=utf-8
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
import time,random,unittest
class AppTask(unittest.TestCase):
def basic(package_name,activity_name):
'''启动应用'''
def tearDown(self):
'''关闭应用'''
def test_001_jd_finance(self):
'''京东金融任务'''
def test_002_qq(self):
'''掌上生活任务'''
def test_003_alipay(self):
'''支付宝任务'''
def test_003_alipay(self):
'''网易云音乐任务'''
前面已经明确了 appium server,安卓模拟器以及具体 APP 任务的启动和关闭过程,将这些过程整合单独写在一个run.py
文件来管理,也就是启动运行整个框架的文件。
在这个文件里,有两个主要的函数。
一个是批量执行 APP 任务并同时生成 HTML 结果报告的函数
设置好 APP 任务所在的路径后,用unittest.defaultTestLoader.discover
方法批量执行任务,同时用HTMLTestRunner
生成测试报告。
test_path = 'E:/daily_task_2'
report_path = 'E:/daily_task_2/report/'
def run_tasks():
'''执行所有APP任务'''
discover = unittest.defaultTestLoader.discover(test_path, pattern='test_*.py')
now = time.strftime('%Y-%m-%d')
filename = report_path + now + ' result.html' # 这个filename是生成的自动化测试报告的文件名
fp = open(filename, 'wb')
runner = HTMLTestRunner(stream=fp, title='APP任务执行情况')
runner.run(discover)
fp.close()
另一个是发送邮件函数,将报告发送给自己的邮箱实现简单监控的目的。
上一步用 HTMLTestRunner 已经在本地生成了执行报告,将报告添加至附件发送即可,发送邮件的模块可以参考之前的这篇文章:《定时任务与 Python 发送邮件 (Windows 平台)》。
最后依次执行启动 appium 服务,启动安卓模拟器,执行任务,发送邮件,关闭模拟器,关闭 appium 服务即可自动完成整套流程。
if __name__ == '__main__':
process() #打印当前开发进度
start_appium_server() #启动appium服务
start_android_devices() #启动模拟器
time.sleep(15)
run_tasks() #执行APP任务
send_mail() #发送邮件
stop_android_devices() #关闭模拟器
stop_appium_server() # 关闭appium服务
绿色代表该 APP 任务执行成功并断言正确。
橙色代表该 APP 任务执行成功但断言错误,请查看截图文件或者打开 APP 确认任务是否完成。
红色代表任务执行失败,可能是 APP 有更新,页面突然有活动/广告弹窗或者网络问题导致,请检查网络和 APP。
目前执行过程中的截图是放在项目根目录的 screenshot 文件夹下,直接在 HTML 测试报告中添加截图我还不太会,在这儿向懂得的同学请教。