Appinum 自动化测试利器入门

1 前言

之前有些app,笔者进行学习任务时需要签到什么的,就想着能不能使用程序自动化的方式,代替我去进行操作,因为那些操作每天重复,也没有什么意义。最近几天使用Appinum进行简单任务的自动化测试时,刚好遇到了一些坑,这里记录下,方便以后再次遇到。

2 什么是Appinum?

  • Appium是c/s架构的,Appium是基于webdriver协议添加了对移动设备自动化api扩展而成的, webdriver是基于http协议的,第一次连接会建立一个session会话,并通过post发送一个json告知服务端相关测试信息 。

  • Appium会首先开启一个监听4723端口的server,接收测试脚本发送过来的对应请求,再将对应的请求发送给中间件Bootstrap.jar(比如点击一个App中的一个Button就是一条请求)

  • Bootstrap.jar
    监听4724端口由Appium发送过来的相关请求,并且将请求转换成UiAutomator可以识别的命令发给UiAutomator进行处理

3 环境准备

这个Appinum需要的环境还是蛮多的,在进行自动化测试之前,还是尽可能多的把环境准备下

3.1 设置JDK变量

这个比较常见基础变量,没有此环境的自行百度哈

注: 如果没有node也可以进行appinum测试,就不需要安装node,如果有问题就把node也装一下,因为笔者本身就有node环境,不确定node环境会不会有影响

3.2 设置Android环境

3.2.1 windows

既然时进行App自动化测试,那肯定需要android运行环境的,笔者为了方便,使用的Android Studio下的SDK环境

  • 新增系统变量ANDROID_HOME,设置Android环境变量,具体以自己实际安装的SDK路径为准
    C:\Users\26310\AppData\Local\Android\Sdk

  • 编辑path,新建以下2个变量
    %ANDROID_HOME%\tools
    %ANDROID_HOME%\platform-tools

3.2.2 Mac环境

vim ~/.bash_profile
source ~/.bash_profile
source ~/.zshrc(mac默认为该位置,如果使用的bash_profile,且退出命令终端窗口maven命令失效,需要在该zshrc内容里面加载source ~/.bash_profile)

vim ~/.zshrc
source ~/.zshrc

#andoid环境
ANDROID_HOME=/Users/xxx/Library/Android/sdk
export ANDROID_HOME
export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools

3.3 下载Appium工具

多系统下载地址: https://github.com/appium/appium-desktop/releases/tag/v1.22.2

Windows系统:直接点击下载

3.4 下载Appium Inspector工具

下载地址: https://github.com/appium/appium-inspector/releases/tag/v2022.2.1

3.5 手机需要开启调试模式

一般情况是,设置里面,手机版本号多次点击5~10次,就会开启开发者模式

4 启动Appium服务

  • 基础配置信息如下,点击Start Server即可启动:

    在这里插入图片描述

  • 出现如下界面,说明启动成功

    在这里插入图片描述

5 连接Appinum服务

打开手机,使用数据线连接电脑,开启手机开发者调试模式(也可以使用模拟器)

5.1 使用命令行连接

在cmd命令行中输入可以简单连接:adb connect 127.0.0.1:4723

5.2 使用Appium Inspector工具连接

  • 核心参数如下:

    在这里插入图片描述

  • 核心参数说明:
    appium:deviceName,标识手机的设备名称,这个可以通过adb devices获取
    appium:platformName,标识系统平台名称,常见的有AndroidIOS
    appium:platformVersion,设备版本号

    注:在使用Appium Inspector连接时可能会报以下错误,所以如果需要使用Appium Inspector,推荐使用使用Appium客户端来进行启动,开启跨域请求:
    Could not connect to server; are you sure it's running? If you are using the browser version, also ensure your Appium server has been started with --allow-cors.

  • 如果连接失败,还可以使用模拟器代替
    比如说开夜神模拟器,打开设置,调成手机开发者模式,初次进入的话,进入设置,点击版本号5次,可以激活使用开发者模式,进入后打开USB调试功能

  • 连接成功界面如下:
    在这里插入图片描述

6 常用命令

6.1 查看是否连接成功

  • 在cmd中输入以下命令:
    adb devices (-l)

  • 获取设备安装的所有包名列表,需要找一下下~
    adb shell pm list package
    adb shell pm list package | findStr jingdong

注:此种方式适合简单的adb(android debug brige)操作,如果复杂的操作,还是建议使用Appium Inspector连接

6.2 进程占用问题

如果重新启动Appium时提示,address already in user 127.0.0.1:port,可以杀掉进程重启

  • Windows系统杀掉进程:

    端口被占用的查找与kill进程(62001为查找的端口号)
    netstat -aon | findstr 62001
    杀掉进程
    taskkill -f -pid 进程号

  • Mac系统杀掉进程:

    查看端口号
    lsof -i tcp:4723
    杀掉进程
    sudo kill -9 29443

6.3 通过命令获取app自动化核心参数

  • 通过adb shell,获取Desired Capabilities需要的核心参数appPackageappActivity,而不同的app ,也会有不同的Activity和Package

    adb shell dumpsys window w | findstr \/ | findstr name=

  • 其他核心参数,通过手机,设置里面查看即可
    deviceName,标识手机的设备名称,这个可以通过adb devices获取
    platformName,标识系统平台名称,常见的有AndroidIOS
    platformVersion,设备版本号

7 Appium定位元素的几种方法总结

这个可以参考写的比较详细的一篇博文:https://blog.csdn.net/lovedingd/article/details/111058898

8 应用测试案例

以下测试案例,笔者基于Python语言 3.10 版本实现,当然了,Java语言,以及其他语言也是可以的

8.1 dingDing测试案例

自动化测试一个钉钉打卡的场景,代码示例如下:

#!/usr/bin/python3
# coding=utf-8

import json
import os
import sys
import time

from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait


def android_driver():
    desired_caps = {
        "deviceName": "D3Hxxxxxxxxxx",
        "platformName": "Android",
        "platformVersion": "10.0",
        # 与工具连接时间
        "newCommandTimeout": 3600,
        'appPackage': 'com.alibaba.android.rimet',
        'appActivity': '.biz.LaunchHomeActivity',
        # 不重置,appium1.6以后的版本需要这个设置才可以保存app的登录状态
        'noReset': 'True',
        "connectHardwareKeyboard": True,
        'automationName': "UiAutomator2"
    }

    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    print(json.dumps(desired_caps))
    return driver


def open_app():
    try:
        # 连接手机
        driver = android_driver()

        # 等待界面完全加载完成(3秒)
        driver.wait_activity("com.alibaba.android.rimet.biz.LaunchHomeActivity", 3)
        return driver
    except:
        print("打开手机app时异常,异常原因:", sys.exc_info()[0])
        raise


def awake_phone():
    # 在adb shell中,有一个经常使用的命令,模拟唤醒屏幕功能按键输入
    os.system("adb shell input keyevent 224")

    # 向上滑动屏幕
    os.system("adb shell input swipe 200 150 200 650")


def do_login(driver):
    WebDriverWait(driver, 20, 1).until(lambda x: x.find_elements(by=By.ID, value='com.android.systemui:id/row1'))

    rowZero = driver.find_elements(by=By.ID, value='com.android.systemui:id/row2')
    colOne = rowZero[0].find_element(by=By.ID, value='com.android.systemui:id/key2')
    colThree = rowZero[0].find_element(by=By.ID, value='com.android.systemui:id/key4')

    rowTwo = driver.find_elements(by=By.ID, value='com.android.systemui:id/row3')
    colTwoThree = rowTwo[0].find_element(by=By.ID, value='com.android.systemui:id/key8')

    colOne.click()
    colTwoThree.click()
    colTwoThree.click()
    colThree.click()

    # print(driver.find_element(by=By.ID, value='com.android.systemui:id/pinEntry').text())


def da_ka():
    # 工作台
    list = driver.find_elements(by=By.ID, value='com.alibaba.android.rimet:id/home_app_item')
    list[2].click()

    # 显示等待加载,超时时间为20s,每隔1秒搜索一次元素是否存在,如果元素存在返回定位对象并退出
    search_button = WebDriverWait(driver, 20, 1).until(lambda x: x.find_elements(by=By.XPATH,
                                                                                 value='(//*[@resource-id="__react-content"]//android.view.View[@text="考勤打卡"])'))

    el1 = driver.find_elements(by=By.XPATH,
                               value='(//*[@resource-id="__react-content"]//android.view.View[@text="考勤打卡"])')
    el1[1].click()

    # 定位等待5秒
    time.sleep(5)

    el2 = driver.find_element(by=By.XPATH,
                              value='(//*[@resource-id="__react-content"]//android.view.View[@text="上班打卡"])')
    el2.click()

    driver.quit()


if __name__ == '__main__':
    driver = open_app()

    awake_phone()

    # 数字解锁登录
    do_login(driver)

    try:
        da_ka()
    except:
        print("打卡异常,异常原因:", sys.exc_info()[0])
        raise

8.2 studyEnhanceCountry测试案例

自动化测试一个studyEnhanceCountry阅读新闻的场景,代码示例如下:

#!/usr/bin/python3
# coding=utf-8
import json
import os
import sys
import time

from selenium.webdriver.support.wait import WebDriverWait
from appium import webdriver
# For W3C actions
# 滑动手机
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.common.by import By

'''
    desired_caps = {
        "deviceName": "D3XXXXXXXXXXX",
        "platformName": "Android",
        "platformVersion": "10.0",
        # 与工具连接时间
        "newCommandTimeout": 3600,
        # 在Android真机上跑自动化脚本时,发现在启动App时报java.lang.SecurityException: Permission Denial: starting Intent,因为配置中的appActivity并不是App启动时最先加载时的Activity, 所以Appium无法启动App
        'appPackage': 'com.ehai',
        'appActivity': '.view.oldview.activity.LoadingActivity',
        # 如果指定了 appWaitPackage 和 appWaitActivity,Appium 将自动等待,直到这些Activity启动
        'appWaitPackage': 'com.huawei.android.launcher',
        'appWaitActivity': 'com.huawei.android.launcher.unihome.UniHomeLauncher',
        # 不重置,appium1.6以后的版本需要这个设置才可以保存app的登录状态
        'noReset': 'True',
        "connectHardwareKeyboard": True,
        # appWaitDuration	Timeout in milliseconds used to wait for the appWaitActivity to launch (default 20000)
        'appWaitDuration': 5000,
        'automationName': "UiAutomator2"
    } 
    '''

# 阅读消息,时长控制,单位s
timeControl = 65


def android_driver():
    desired_caps = {
        "deviceName": "D3H5xxxxxxxxxx",
        "platformName": "Android",
        "platformVersion": "10.0",
        # 与工具连接时间
        "newCommandTimeout": 3600,
        'appPackage': 'cn.xuexi.android',
        'appActivity': 'com.alibaba.android.rimet.biz.SplashActivity',
        # 不重置,appium1.6以后的版本需要这个设置才可以保存app的登录状态
        'noReset': 'True',
        "connectHardwareKeyboard": True,
        # 'automationName': "UiAutomator2"
    }

    driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
    print(json.dumps(desired_caps))
    return driver


def open_app():
    try:
        # 连接手机
        driver = android_driver()

        # 等待界面完全加载完成(8秒)
        driver.wait_activity("com.alibaba.android.rimet.biz.SplashActivity", 8)

        # 等待app加载2秒
        time.sleep(2)
        return driver
    except:
        print("打开手机app时异常,异常原因:", sys.exc_info()[0])
        raise


def watch_channel_new(driver, str_channel):
    WebDriverWait(driver, 20, 1).until(lambda x: x.find_elements(by=By.XPATH, value=str_channel))
    el2 = driver.find_element(by=AppiumBy.XPATH, value=str_channel)
    el2.click()

    # 等待数据加载2秒
    time.sleep(2)

    # 组合定位,获取某个频道的,部分新闻列表
    new_list = "//*[@resource-id='cn.xuexi.android:id/general_card_title_id' and @package='cn.xuexi.android']"
    search_button = WebDriverWait(driver, 20, 1).until(lambda x: x.find_elements(by=By.XPATH, value=new_list))
    el3 = driver.find_elements(by=By.XPATH, value=new_list)
    for i in range(len(el3)):
        el3[i].click()

        time.sleep(5)
        # 向下滑动,选中部分阅读内容
        # swip = SwipeOperate(driver)
        # swip.swipe_to_bottom(0.75, 10)

        # KEYCODE_PAGE_UP 向上翻页键 92
        # os.system("adb shell input keyevent 92")
        # KEYCODE_PAGE_DOWN 向下翻页键 93
        os.system("adb shell input keyevent 93")

        time.sleep(5)

        # 阅读内容65秒
        time.sleep(timeControl - 10)
        driver.back()


def watch_new(driver):
    # header = driver.find_element_by_id("com.ss.android.article.lite:id/ayv")
    # list = header.find_elements_by_class_name("android.widget.TextView")
    # list = header.find_elements_by_xpath("//*[@resource-id='com.ss.android.article.lite:id/bb'][@clickable='true']")

    # 显示等待加载,超时时间为20s,每隔1秒搜索一次元素是否存在,如果元素存在返回定位对象并退出
    search_button = WebDriverWait(driver, 20, 1).until(
        lambda x: x.find_elements(by=By.ID, value='cn.xuexi.android:id/home_bottom_tab_icon_large'))
    # 学习中心
    el1 = driver.find_elements(by=By.ID, value='cn.xuexi.android:id/home_bottom_tab_icon_large')
    el1[0].click()

    # 要闻频道-精确定位
    new_channel = "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.FrameLayout[2]/android.widget.FrameLayout[1]/android.support.v4.view.ViewPager/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.view.ViewGroup/android.widget.LinearLayout[2]/android.widget.TextView"
    watch_channel_new(driver, new_channel)

    # 新思想频道-精确定位
    new_opinion_channel = "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.FrameLayout[2]/android.widget.FrameLayout[1]/android.support.v4.view.ViewPager/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.view.ViewGroup/android.widget.LinearLayout[3]/android.widget.TextView"
    watch_channel_new(driver, new_opinion_channel)


def awake_phone():
    # 唤醒屏幕,在adb shell里有一个非常使用的命令,模拟功能按键输入
    os.system("adb shell input keyevent 224")


def watch_video(driver):
    # 电视台
    el1 = driver.find_elements(by=By.ID, value='cn.xuexi.android:id/home_bottom_tab_button_contact')
    el1[0].click()

    # video list 精确定位
    video_element = "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.LinearLayout/android.widget.FrameLayout[2]/android.widget.FrameLayout[1]/android.support.v4.view.ViewPager/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.ViewGroup/android.widget.FrameLayout/android.widget.FrameLayout[2]/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.ListView/android.widget.FrameLayout"
    search_button = WebDriverWait(driver, 20, 1).until(
        lambda x: x.find_elements(by=AppiumBy.XPATH, value=video_element))
    videoList = driver.find_elements(by=AppiumBy.XPATH, value=video_element)

    for i in range(len(videoList)):
        videoList[i].click()
        # 阅读内容65秒
        time.sleep(timeControl)
        driver.back()


if __name__ == '__main__':
    awake_phone()

    driver = open_app()

    # 查看文章消息
    watch_new(driver)

    # 切换其他app测试
    # driver.start_activity('com.ehai', '.view.oldview.activity.LoadingActivity')

    # 试听学习
    watch_video(driver)

    # 退出app
    driver.quit()

好啦,本篇内容就简单介绍到这里了,虽然是个入门教程,但是笔者还是很辛苦的,踩了很多的坑,花费了一天多才搞出来,有需要的老铁们,可以白嫖了~

如果这篇内容对你有帮助,欢迎老铁三连击,先点赞,关注,加收藏,然后再看~

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值