移动端自动化
移动端测试环境搭建
移动端自动化测试的基础环境配置需要搭建下面三个环境:
1、Java环境
2、AndroidSDK环境
3、Android模拟器
下载链接: https://pan.baidu.com/s/1XP5ki3uHy4b7giiePv5QhA 提取码: mb4p
环境说明
Java:Android的应用层开发使用的是Java语言,所以需要使 Java环境。
AndroidSDK:提供Android开发的软件开发工具包。
Android模拟器:模拟安卓手机的一个程序。
Java环境安装
下载JDK1.8安装即可,详见:https://www.cnblogs.com/lyang-a/p/11462369.html
AndroidSDK环境安装
AndroidSDK下载的官网地址:https://www.androiddevtools.cn/
Window 平台解压后,进入解压缩目录,双击 SDK Manager.exe ,下载所需工具包即可。
Mac平台解压后,进入解压缩目录,进入tools目录,双击 android 这个文件,打开 Android SDK Manager, 下载所需工具包。
参考地址:https://www.pianshen.com/article/399239299/
AndroidSDK环境变量配置:
注意:android-sdk 文件夹需注意,路径中不要有中文。
1. 进入我的电脑 -> 属性 -> 高级系统设置 -> 环境变量
2. 在系统变量下点击新建 -> 变量名: ANDROID_HOME -> 变量值(文件路径): D:\android-sdk -> 点击确定按钮
3. 在系统变量下找到系统的path变量,最后添加:;%ANDROID_HOME%\platform-tools;%ANDROID_HOME%\tools;(最前面是一个分号,如果path变量最后已有分号,可不用添加) -> 点击确定按钮
mac平台的AndroidSDK环境变量配置:
1.进入命令行,
vim ~/.bash_profile
2.设置环境变量
ANDROID_HOME=电脑存放的路径/android-sdk-macosx
PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
export ANDROID_HOME
export PATH
3. 重写打开终端
验证环境变量是否配置成功:
adb version
android_sdk中的adb.exe版本和夜神模拟器中的nox_adb命令版本不一致,则会报错:
解决办法:
1、把android_sdk\platform-tools目录下adb.exe复制一份,名字修改为:nox_adb.exe
2、把夜神模拟器中bin目录下的nox_adb.exe进行替换
3、重启夜神模拟器
4、打开终端输入 【adb devices】即可发现连接的设备
adb命令
adb全名Andorid Debug Bridge,是一个调试工具,它可以在电脑通过终端命令操作安卓手机或者模拟器。
adb的工作原理
adb 包含三个部分:
1、Client端:运行在开发机器中,即你的开发PC机。用来发送adb命令。
2、Daemon守护进程:运行在调试设备中, 即手机或模拟器,用来接收和执行adb命令。
3、Server端:作为一个后台进程同样运行在开发机器中, 用来管理PC中的Client端和手机的Daemon之间的通信。
adb的常用命令
查看adb版本
adb version
或
adb --version
获取包名和界面名
包名:app的唯一标识,好比身份证号码
界面名:app中显示界面对应的名字
示例:
1、打开设置
2、打开终端输入命令
Windows过滤使用 findstr
,Mac过滤使用 grep
Windows:
adb shell dumpsys window windows | findstr mFocusedApp
Mac:
adb shell dumpsys window windows | grep mFocusedApp
3、得到结果数据
mFocusedApp=AppWindowToken{d02598c token=Token{85dfcde ActivityRecord{11a3419 u0 com.android.settings/.Settings t38}}}
此处包名为:com.android.settings
界面名为:.Settings
文件传输
将电脑上的文件发送到手机
adb push 电脑的文件的路径 手机的文件夹的路径
# 示例
adb push D:\2233.png /sdcard
将手机上的文件发送到电脑
adb pull 手机的文件的路径 电脑的文件夹的路径
# 示例
adb pull /sdcard/2233.png D:\
安装程序
adb install apk路径
adb install -r apk路径
-r 覆盖安装
# 示例
adb install -r bug.apk
卸载程序
卸载程序时需先查询到指定软件的包名,根据包名进行删除
adb uninstall 包名
# 示例
adb uninstall cn.itcast.myapplication
查看连接设备
# 查看手机的连接状态adb devices
# -s:指定某个设备例:
# 指定某个设备运行命令adb -s 设备号 命令
adb -s 设备号 push xxx
adb -s 设备号 install xxx
adb -s 127.0.0.1:62001 install D:/bug.apk
进入Android系统终端
进入Android手机中的linux系统中,Android系统是基于Linux系统的。
adb shell
开启/关闭adb服务
adb kill-server # 关闭adb服务
adb start-server # 开启adb服
查看App启动时间
adb shell am start -W 包名/界面名
ThisTime:该界面启动耗时时间(毫秒),如果关心应用有界面Activity启动耗时,参考ThisTime
TotalTime:应用自身启动耗时时间(毫秒),如果只关心某个应用自身启动耗时,参考TotalTime
WaitTime: 系统启动应用的耗时时间(毫秒),如果关心系统启动应用耗时,参考WaitTime
查看手机上所有的包名
adb shell pm list packages
查看日志
adb logcat
过滤级别:级别从高到低(E > W > I > D > V),E 表示错误、W表示警告
# 过滤日志级别adb logcat *:级别
另一种查看包名的方式:
1、先 【adb logcat | findstr START】
2、再打开想要查看的程序
查看电池
adb shell dumpsys battery
查看内存
adb shell dumpsys meminfo \
查看cpu
adb shell top
s:表示按什么排序
例:adb shell top -s 9  # 按照CPU使用率进行排序
monkey介绍
monkey是用来做Android的压力测试的(ios不能做),要想对ios做压力测试需用到 fastmonkey这个第三方工具。
monkey主要用来测试App的健壮性。monkey测试、健壮性测试、压力测试。
命令:
adb shell monkey 选项 操作次数
一次操作表示一次次数,比如:手指按下一次、移动一次、手指抬起一次都表示一次操作,共完成了3次操作。
monkey命令的选项
-p 包名,要测试哪个应用程序
-v 日志级别,一般用两个-v即可
-s seed值,用来复现bug,当seed值相同时,monkey所产生的事件序列也相同
--throttle 事件的间隔时间,单位:毫秒,一般300毫秒左右就可以。
--pct-xxx 设置事件的百分比
--pct-touch 点击事件
--pct-motion 滑动事件
monkey输出日志
将monkey操作的日志保存到日志中使用【>】进行写入
adb shell monkey -p 包名 操作次数 > 日志的路径
monkey的操作
adb shell monkey -p 包名 -v -v [--pct-touch 百分比数字] --throttle 2000 次数 > 日志的路径
提示:使用 【-v -v】可以详细的查看执行的操作
查看日志,如果有 monkey finished表示执行过程中没有问题,如果没有在终端需要观察 anr 或 exception异常信息
尝试复现,使用 -s 参数。例:adb shell monkey -v -v -s 1628066493671 20
移动端自动化测试环境搭建
Android自动化测试需要搭建下面5个环境,如下:
1、Java环境
2、AndroidSDK环境
3、Android模拟器
4、Appium Desktop(Appium 服务端)
5、Appium-Python-Client(Appium Python客户端)
具体执行流程:
1、先通过Appium-Python-Client工具编写操作手机App的Python代码;
2、当运行编写好的Python代码时会连接Appium服务端;
3、Appium服务端通过解析操作的代码会启动手机上对应的App,把Python代码操作手机App的效果会呈现出来,最终完成自动化测试。
Appium Desktop(Appium 服务端)介绍
Appium Desktop是一款适用于Mac,Windows和Linux的开源应用程序,它以美观而灵活的用户界面提供强大的Appium服务。
作用:
Appium服务端通过解析代码,驱动Android设备来执行自动化测试脚本。
下载地址:https://github.com/appium/appium-desktop/releases/
Appium-Python-Client介绍
它是移动端定位App元素和操作App元素的一个第三方库,它还负责与Appium服务器建立连接,并将测试脚本的指令发送到Appium服务器,Appium服务器再驱动手机或者模拟器展示相关的效果,类似于web端自动化测试中的Selenium库。
Appium库是基于Selenium库开发的,Appium库中的WebDriver类是继承Selenium库中的WebDriver类。
安装Python语言的Appium客户端库:
pip install Appium-Python-Client
appium入门示例
需求:
使用 Appium-Python-Client 库 打开Android模拟器的设置界面
实现步骤:
1、开启appium服务器
2、启动Android模拟器,打开设置界面
3、打开电脑终端,输入查看包名和界面名的adb命令
adb shell dumpsys window windows | grep mFocusedApp
4、编写Python的Appium客户端代码,实现打开Android模拟器的设置界面
import time
from appium import webdriver
# 创建子弹,封装启动app的参数
desired_capabilities = {
# 连接手机的平台名称
"platformName": "Android",
# 连接手机的平台版本,也就是手机的操作系统版本, 至少匹配大版本,比如:写7
"platformVersion": "7.1",
# 设备名称,随便写, 但是不能为空
"deviceName": "127.0.0.1:62001",
# 启动程序的包名
"appPackage": "com.android.settings",
# 启动程序的界面名
"appActivity": ".Settings"
}
# 连接appium服务器,并创建驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
# 延时2秒钟
time.sleep(2)
# 退出app
driver.quit()
参数说明:
platformName: 连接手机的平台名称
platformVersion: 手机的操作系统版本
deviceName: 设备名称,随便写, 但是不能为空
appPackage: 启动程序的包名
appActivity: 启动程序的界面名
其它参数说明:
# 设置输入中文,默认不支持,下面两个参数一般都是配合使用
unicodeKeyboard 使用 Unicode 输入法,默认值 false
resetKeyboard 重置输入法到原有状态,如果单独使用,将会被忽略,默认值 false
noReset:不重置应用,默认为false
noSign 跳过检查和对应用进行 debug 签名的步骤,默认值 false,appium 默认会对应用进行重签名,可能导致官方服务器不认,一般设置成True
autoGrantPermissions 自动同意app所需要的各种权限,默认是false
uiautomatorviewer
uiautomatorviewer是一个手机端查看控件的特征信息的一个工具。
示例:
查看手机端设置程序右上角的放大镜按钮的 resource-id
步骤:
1、打开在电脑终端输入 uiautomatorviewer;
2、启动android模拟器,打开设置应用;
3、打开终端输入uiautomatorviewer进行打开,打开后点击左上角的第三个按钮,关联手机屏幕;
4、点击放大镜按钮;
5、查看Node Detail 中的 resource-id 信息。
说明:
resource-id 好比 html元素的id,但是在手机端 resource-id这个属性值可以重复。
class 好比 html元素的class,表示类属性。
在手机端定位一般都使用xpath,更加灵活和方便。
元素定位操作
根据元素的特征信息进行元素的定位,比如:利用xpath根据元素的属性信息进行定位
注意点:
元素的定位基于当前屏幕范围内的可见元素。
手机端元素的定位方式:
id定位
class定位
xpath定位
定位一个元素的方法:
使用id定位一个元素:driver.find_element_by_id(resource-id),定位的是resource-id属性值
使用class定位一个元素:driver.find_element_by_class_name(class_value),定位的是class属性值
使用xpath定位一个元素:driver.find_element_by_xpath(xpath_value),根据xpath表达式定位元素
定位content-desc元素:driver.find_element_by_accessibility_id
注意:定位一个元素时,如果多个元素都有相同的特征信息,使用该方法定位到的是第一个。
定位一组元素的方法:
使用id定位一组元素:driver.find_elements_by_id(resource-id)
使用class定位一组元素:driver.find_elements_by_class_name(class_value)
使用xpath定位一组元素:driver.find_elements_by_xpath(xpath_value)
定位一个元素的示例:
"""
使用id定位设置界面的放大镜并点击
使用class定位搜索框并输入内容“显示”
使用xpath定位返回按钮并点击
"""
import time
from appium import webdriver
# 创建字典,封装启动app的参数
desired_capabilities = {
# 连接手机的平台名称
"platformName": "Android",
# 连接手机的平台版本,也就是手机的操作系统版本, 至少匹配大版本,比如:写7
"platformVersion": "7.1",
# 设备名称,随便写, 但是不能为空
"deviceName": "127.0.0.1:62001",
# 启动程序的包名
"appPackage": "com.android.settings",
# 启动程序的界面名
"appActivity": ".Settings",
# 设置支持中文输入
"unicodeKeyboard": True,
"resetKeyboard": True
}
# 连接appium服务器,并创建驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
# 使用id定位放大镜
search_button = driver.find_element_by_id("com.android.settings:id/search")
# 点击放大镜
search_button.click()
time.sleep(0.5)
# 使用class定位搜索框
search_box = driver.find_element_by_class_name("android.widget.EditText")
# 默认输入不了中文,需要设置启动app参数,unicodeKeyboard和resetKeyboard这两个参数为True
search_box.send_keys("显示")
time.sleep(0.5)
# 使用xpath定位返回按钮
# back_button = driver.find_element_by_xpath("//android.widget.ImageButton[@content-desc='收起']")
# 简单操作
back_button = driver.find_element_by_xpath("//*[@content-desc='收起']")
back_button.click()
# find_element_by_accessibility_id 可以用于定位content-desc的属性值
# back_button = driver.find_element_by_accessibility_id("收起")
# back_button.click()
time.sleep(2)
# 退出app
driver.quit()
说明:输出的信息里面有空行,是因为定位class为android.widget.TextView的元素,没有内容。
定位一组元素的示例:
"""
需求:
通过程序打开android手机或者模拟器的设置界面
通过id定位所有resource-id为 android:id/title 的元素,并打印每一个元素的文本内容
通过class定位所有的class为 android.widget.TextView 的元素,并打印每一个元素的文本内容
通过xpath定位所有包含 '设' 的元素,并打印每一个元素的文本内容。
"""
import time
from appium import webdriver
# 创建字典,封装启动app的参数
desired_capabilities = {
# 连接手机的平台名称
"platformName": "Android",
# 连接手机的平台版本,也就是手机的操作系统版本, 至少匹配大版本,比如:写7
"platformVersion": "7.1",
# 设备名称,随便写, 但是不能为空
"deviceName": "127.0.0.1:62001",
# 启动程序的包名
"appPackage": "com.android.settings",
# 启动程序的界面名
"appActivity": ".Settings",
# 设置支持中文输入
"unicodeKeyboard": True,
"resetKeyboard": True
}
# 连接appium服务器,并创建驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
# 定位id为android:id/title的所有元素
title_list = driver.find_elements_by_id("android:id/title")
for element in title_list:
# 获取元素的内容
print(element.text)
time.sleep(0.5)
# 定位class为android.widget.TextView的所有元素
text_view_list = driver.find_elements_by_class_name("android.widget.TextView")
for text_view in text_view_list:
# 获取元素的内容
print(text_view.text)
time.sleep(0.5)
# 使用xpath定位文本内容包含 设 的所有元素
element_list = driver.find_elements_by_xpath("//*[contains(@text, '设')]")
for element in element_list:
# 获取元素的内容
print(element.text)
time.sleep(2)
# 退出app
driver.quit()
定位单个元素和定位一组元素的注意点:
使用find_elements_by_xx,如果传入的特征信息没有定位到元素,则返回一个空列表,比如: []
使用find_element_by_xx,如果传入的特征信息没有定位到元素,则会抛出一个NoSuchElementException的异常。
元素等待
应用场景:
由于某些原因,我们想定位的元素没有立刻出来,此时如果直接定位可能会报错,比如有以下原因:
1、由于网络速度原因
2、服务器处理请求原因
3、电脑配置原因
元素等待的分类:
隐式等待
显示等待
隐式等待
应用场景:
当定位所有元素时,设置超时时间为同一个值的时候,此时使用隐式等待,设置隐式等待能够作用于所有元素。
注意点:
当定位的元素,超过指定时间了还没有定位到,此时会抛出一个 NoSuchElementException异常
操作代码:
driver.implicitly_wait(10)
显示等待
应用场景:
当定位每个元素时,设置超时时间不一样时,此时使用显示等待, 它只能作用于某个元素的。
注意点:
当定位的元素,超过指定时间了还没有定位到,此时会抛出一个 TimeoutException异常
操作代码:
WebDriverWait(driver, 超时时间, 搜索时间间隔).until(lambda定位元素的函数)
示例:
"""
打开Android手机或者模拟器中的设置,使用显示等待定位 “返回” 按钮,超时时间设置为5秒,每1秒查找一次。
"""
import time
from appium import webdriver
# 封装启动app的参数
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
desired_capabilities = {
"platformName": "Android",
"platformVersion": "7.1",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.android.settings",
"appActivity": ".Settings",
# 设置支持中文输入
"unicodeKeyboard": True,
"resetKeyboard": True
}
# 连接appium服务器,并创建驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
# 显示等待,如果元素没有定位到则会抛出 TimeoutException异常
# WebDriverWait(driver, 5, 1).until(lambda current_driver: driver.find_element_by_xpath("//*[@content-desc='收起']"))
def element_wait(timeout, poll_frequency, element):
WebDriverWait(driver, timeout, poll_frequency).until(lambda current_driver: driver.find_element(*element))
element_wait(5, 1, (By.XPATH, "//*[@content-desc='搜索设置']"))
element_wait(5, 1, (By.XPATH, "//*[@content-desc='收起']"))
time.sleep(3)
driver.quit()
隐式等待和显示等待的区别
作用域不同:
显示等待作用于单个元素有效
隐式等待作用于所有元素有效
相同点:他们都是在找不到元素时触发元素等待
方法不同:
显示等待方法封装在WebDriverWait类中
隐式等待直接通过driver.implicitly_wait()方法来设置
抛出的异常不用:
隐式等待超时没有找到,会抛出NoSuchElementException异常
显示等待超时没有找到,会抛出TimeoutException异常
元素操作方法
点击元素
# 对元素进行点击操作
element.click()
输入和清空输入框内容
# 对文本框进行输入内容操作
element.send_keys(value)
# 对文本框进行清空内容操作
element.clear()
获取元素的文本内容
比如:按钮、文本框、输入框等控件可以获取它们的文本内容
# 通过text属性获取元素的文本内容
element.text
获取元素的位置和大小
# 获取元素的位置
element.location
# 获取元素的大小
element.size
获取元素的属性值
当定位到元素后,想要使用元素的属性信息时,此时就要获取元素的属性值了。
# 根据属性名获取属性值,value表示属性名
element.get_attribute(value)
示例:
import time
from appium import webdriver
# 封装启动app的参数
desired_capabilities = {
"platformName": "Android",
"platformVersion": "7.1",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.android.settings",
"appActivity": ".Settings",
# 设置支持中文输入
"unicodeKeyboard": True,
"resetKeyboard": True
}
# 连接appium服务器,并创建驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
search_button = driver.find_elements_by_id("com.android.settings:id/search")
# 点击操作
search_button.click()
search_text = driver.find_element_by_class_name("android.widget.EditText")
# 根据属性名获取属性值, resourceId:获取resource_id属性对应的值
resource_id = search_text.get_attribute("resourceId")
print(resource_id)
# 对文本框进行输入内容操作
search_text.send_keys("显示")
# 通过text属性获取元素的文本内容
element = search_text.text
print(element)
# 对文本框进行清空内容操作
search_text.clear()
back_button = driver.find_element_by_class_name("android.widget.ImageButton")
# 根据属性名获取属性值, name:获取text或content-desc属性对应的值
name = back_button.get_attribute("name")
print(name)
# 获取元素的位置
location = back_button.location
# 获取元素的大小
size = back_button.size
print("location:", location)
print("size:", size)
time.sleep(3)
driver.quit()
滑动和拖拽操作
在做自动化测试中,有些元素需要滑动几次屏幕后才会发现,才能进行定位元素的,此时需要用到滑动或者拖拽操作。
滑动操作方法
swipe()方法:
根据开始和结束的坐标点进行滑动
语法:
driver.swipe(开始x坐标, 开始y坐标, 结束x坐标, 结束y坐标, 滑动这次操作共持续的时间(毫秒))
scroll()方法:
根据开始元素位置和结束元素位置进行滑动
语法:
driver.scroll(开始元素, 结束元素, 滑动这次操作共持续的时间(毫秒))
提示:这个两个方法都用滑动惯性,想要滑动的惯性小,把持续时间设置大一些,比如: 5000毫秒
拖拽操作方法
drag_and_drop()方法:
根据两个元素的位置进行滑动,从第一个元素的位置滑动到第二个元素的位置。
语法:
driver.drag_and_drop(开始元素, 结束元素)
提示: 这个drag_and_drop没有滑动惯性,没有持续时间参数。
为了更好的看到滑动效果,建议开启指针,开启指针位置(模拟器):
1、打开设置,找到关于平板电脑,点击进去
2、找到版本号,连点5下,方可打开开发者模式
3、回到设置界面,点击开发者选项
4、找到输入 --> 指针位置,开启该功能即可
示例:
import time
from appium import webdriver
# 封装启动app的参数
desired_capabilities = {
"platformName": "Android",
"platformVersion": "7.1",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.android.settings",
"appActivity": ".Settings",
# 设置支持中文输入
"unicodeKeyboard": True,
"resetKeyboard": True
}
# 连接appium服务器,并创建驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
def swipe_operation(start_x, start_y, end_x, end_y, duration=0):
driver.swipe(start_x, start_y, end_x, end_y, duration)
# swipe_operation(200, 300, 200, 1000)
# swipe_operation(200, 1000, 200, 300, 3000)
window_size = driver.get_window_size()
print(window_size)
x = window_size["width"] / 2
start_y = window_size["height"] / 4 * 3
end_y = window_size["height"] / 4
swipe_operation(x, start_y, x, end_y)
show_control = driver.find_element_by_xpath("//*[@text='显示']")
wallpaper_control = driver.find_element_by_xpath("//*[@text='更换壁纸']")
# scroll:两元素之间的滑动,有惯性
# driver.scroll(show_control, wallpaper_control)
driver.scroll(show_control, wallpaper_control, 3000)
# drag_and_drop:两元素之间的拖拽,无惯性
driver.drag_and_drop(show_control, wallpaper_control)
time.sleep(3)
driver.quit()
滑动和拖拽方法的选择
有惯性:
坐标之间滑动使用:swipe()方法
元素之间滑动使用:scroll()方法
无惯性:
元素之间拖拽使用:drag_and_drop()方法
测试设置界面的搜索功能
"""
需求:
利用pytest框架完成自动化测试设置界面的搜索功能
测试数据为: 电池、WLAN
"""
import time
import pytest
from appium import webdriver
class TestSettingSearch:
data_list = [
{"search_data": "电池", "except_value": True},
{"search_data": "WLAN", "except_value": True},
{"search_data": "ee", "except_value": False}
]
def setup(self):
# 封装启动app的参数
desired_capabilities = {
"platformName": "Android",
"platformVersion": "7.1",
"deviceName": "127.0.0.1:62001",
"appPackage": "com.android.settings",
"appActivity": ".Settings",
# 设置支持中文输入
"unicodeKeyboard": True,
"resetKeyboard": True
}
# 连接appium服务器,并创建驱动对象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_capabilities)
# 设置隐式等待
self.driver.implicitly_wait(2)
def teardown(self):
time.sleep(3)
self.driver.quit()
@pytest.mark.parametrize("data", data_list)
def test_search_value(self, data):
print("数据为:", data)
# 定位放大镜控件
self.driver.find_element_by_id("com.android.settings:id/search").click()
# 定位搜索框
search_text = self.driver.find_element_by_id("android:id/search_src_text")
# 向搜索框输入内容
search_text.send_keys(data["search_data"])
search_content = self.driver.find_elements_by_id("com.android.settings:id/title")
if data["except_value"]:
assert len(search_content) > 0
else:
assert len(search_content) == 0
if __name__ == '__main__':
pytest.main(["-sv", "--alluredir=allure_data", "--clean-alluredir"])
# 生成测试报告命令
# allure generate allure_data --clean