项目背景

[项目地址点击这里],整体项目框架通过 python+appium+unittest 实现,参照项目源码食用本篇文章更佳。

今年新换的工作以 PC 客户端和 web 为主,很少接触到 APP,怕手生给忘了,刚好最近觉得手机里面现在越来越多的 APP 需要每天进行签到,点赞,评论,定时给某人发消息,转账等操作,就用 APP 自动化来给自己的生活带点便利吧。python+appium+unittest 写好框架,以后有新的 APP 只要用几分钟时间把这个 APP 的任务添加一下就好了,下文中的每一条 APP 任务实际就是自动化测试中的每一条测试用例。

此项目只有在执行任务的时候才会启动服务和安卓模拟器,执行完毕后自动关闭所有服务和模拟器进程,其他时段不会占用浪费机器资源,另外写此框架的主要目的是替代手工每天能定时跑,所以在任务运行时段 Windows 机器需要正常稳定,一般放在半夜凌晨执行。


环境准备

以下是我使用时候的版本,根据自己情况安装所需版本就行。

  1. JDK (1.8.0_91)
  2. SDK (25.1.7)
  3. python 3
  4. node.js (v6.11.0)
  5. appium server (1.4.16)
  6. Appium-Python-Client (0.24)
  7. 雷电安卓模拟器(安卓 5.1.1,720*1280)

项目详解

流程图简介

1. 获取设备名

手机连接电脑或者打开电脑上的安卓模拟器,在 cmd 命令行直接输入 adb devices 查看设备名,下面的emulator-5554就是设备名。
需要注意的是,有些模拟器自带的 adb.exe 工具与 SDK 自带的 adb 会冲突,具体表现就是打开了模拟器后,adb 命令不能正常执行,因为进程冲突了,解决办法是在模拟器安装路径找到 adb.exe,把它重新命名或者直接删除。

C:\Users\pei>adb devices
List of devices attached
emulator-5554   device

2. 启动/关闭 appium 服务

启动命令
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"
)

3. 启动/关闭安卓模拟器

在安卓模拟器的安装目录找到启动模拟器的可执行文件,将它的路径复制出来,在 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('所有任务执行完毕,关闭模拟器')

4. 执行 APP 任务

这部分是整个框架的主体部分,每个 APP 的执行代码都写在这里,用到 unittest 单元测试框架,所有的函数写在AppTask类中,AppTask 继承 unittest.TestCase 方法。

基础模块

基础模块封装了全部 APP 任务都要用到的公共方法,这里分为两部分。

APP 任务

每一个 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):
    '''网易云音乐任务'''

5. 整合

前面已经明确了 appium server,安卓模拟器以及具体 APP 任务的启动和关闭过程,将这些过程整合单独写在一个run.py文件来管理,也就是启动运行整个框架的文件。
在这个文件里,有两个主要的函数。

最后依次执行启动 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 测试报告中添加截图我还不太会,在这儿向懂得的同学请教。



↙↙↙阅读原文可查看相关链接,并与作者交流