Appium
一、移动自动化测试环境搭建
安装JDK-安装Android SDK-安装Appium-安装模拟器-安装Appium python
1、安装JDK
2、安装Android SDK
方式一:安装android studio,安装sdk
方式二:直接下载android sdk
1、下载安装包,并解压,尽量不要放置到中文目录;
2、解压之后的安装包只包含基本的SDK工具,它不包含Android平台或任何第三方库。需要使用SDKManager安装所需要的工具;
3、配置环境变量:
ANDROID_HOME=D:\Android\sdk
在Path中添加:%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools;
提示:tools有查看元素工具,我们必须使用;platform-tools是adb命令工具所在目录。
验证安装:
C:\Users\Administrator>adb --version
Android Debug Bridge version 1.0.41
Version 33.0.3-8952118
Installed as E:\adb\platform-tools\adb.exe
3、appium安装
说明: 需要安装appium服务端程序和python中调用的api库
- 服务端:
作用:将脚本发送给手机
安装:双击安装程序 appium-desktop-setup-1.8.0.exe ,一直到完成即可。
https://github.com/appium/appium-desktop/releases - python的appium. api库
作用:自动化测试使用api
安装:pip install Appium-Python-Client==1.2.0
4、模拟器连接adb
mumu模拟器:
打开模拟器,进入安装目录,进入cmd
adb connect 127.0.0.1:7555
adb命令
获取包名和启动名
- 包名:决定程序的唯一性(不是应用的名字);一个安卓应用的唯一标识符,操作应用需要依赖包名
- 启动名:界面名(activity), 应用中界面标识符,允许重复。目前可以理解,一个界面名,对应着一个界面
1、mac/linux: adb shell dumpsys window | grep usedApp 2、windows: adb shell dumpsys window | findstr usedApp
上传和下载命令
上传: adb push 路径\xxx.txt /sdcard
下载: adb pull /sdcard/xxx.txt 本地文件夹路径
启动时间命令
adb shell am start -W 包名/启动名
例:
adb shell am start -W com.android.settings/.Settings t31}
查看日志
adb logcat -c && adb logcat -v time>e://1752.log
UIAutomatorViewer查看元素信息
使用JDK1.8,其他版本jdk可能会报错
查看APP元素信息步骤
1. 进入SDK目录下的目录:
mac 在 tools/bin 目录下,打开 uiautomatorviewer
windows 在 tools 目录下,打uiautomatorviewer.bat
2. 电脑连接真机或打开android模拟器
3. 启动待测试app
4. 点击 uiautomatorviewer 的左上角 Device Screenshot (从左数第二个按钮)
5. 点击希望查看的控件
6. 查看右下角 Node Detail 相关信息
报错问题
-
adb: adb server version (41) doesn't match this client (39);
/bin/adb文件和~/AndroidSdk/platform-tools/adb两个adb文件,删除前者adb文件并将后者adb文件复制到/bin/目录下即可 -
Error while obtaining UI hierarchy XML file: com.android.ddmlib.SyncException: Remote object doesn't exist!
这表示你的android设备的系统是android 9.0以上版本(包括9.0),对于9.0以上版本的android系统,uiautomatorviewer工具无法直接获取页面内容
方式一:切换安卓版本,最有效的办法
方式二:
此时可以通过adb命令把页面截图和页面元素属性保存下来,然后导入uiautomatorviewer中- 把android设备切换到想要的画面
- 截取uix资源文件
cmd下输入
adb shell uiautomator dump /sdcard/screen.uix //获取uix保存在android系统的/sdcard目录下
adb pull /sdcard/screen.uix E:\screen //把android系统中的uix文件pull到电脑中
截取图片
cmd下输入
adb shell screencap -p /sdcard/screen.png //获取图片保存在android系统的/sdcard目录下
adb pull /sdcard/screen.png E:\screen //把android系统中的图片pull到电脑中
把uix文件和png图片导入uiautomatorviewer中
二、移动自动化appium
1、appium-client执行
配置Desired Capabilities
配置完以后,点击Start Session
2、python脚本中,实现appium-client的功能
- 打开模拟器
- 启动Appium服务器
- 获取要测试APP的包名/界面名
一定注意版本:pip install Appium-Python-Client==1.2.0
不然新版的api不相同
获取driver
from appium import webdriver
# 定义字典变量
desired_caps = {}
# 字典追加启动参数
# 需要连接的手机的平台(不限制大小写)
desired_caps["platformName"] = "Android"
# 注意:版本号必须正确
# 需要连接的手机的版本号(比如 5.2.1 的版本可以填写 5.2.1 或 5.2 或 5 ,以此类推)
desired_caps["platformVersion"] = "12"
# 需要连接的手机的设备号(andoird平台下,可以随便写,但是不能不写)
desired_caps["deviceName"] = "111"
# 需要启动的程序的包名
desired_caps["appPackage"] = "com.android.settings"
# 需要启动的程序的界面名
desired_caps["appActivity"] = ".Settings"
# 设置中⽂
desired_caps["unicodeKeyboard"] = True
desired_caps["resetKeyboard"] = True
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
3、基础API
启动应用
driver.start_activity(appPackage, appActivity)
appPackage:要打开的程序的包名
appActivity:要打开的程序的界面名
例:三秒后跳转通讯录
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
time.sleep(3)
# 三秒后跳转通讯录
driver.start_activity('com.android.contacts', '.activities.PeopleActivity')
driver.quit()
获取当前包名、启动名
当前应⽤包名:driver.current_package
当前应⽤启动名:driver.current_activity
例
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
time.sleep(3)
# 三秒后跳转通讯录
driver.start_activity('com.android.contacts', '.activities.PeopleActivity')
# 打印包名和启动名
print("当前所在应⽤包名:", driver.current_package)
print("当前所在应⽤启动名:", driver.current_activity)
driver.quit()
关闭驱动和app
# 关闭当前操作的app,不会关闭驱动对象
driver.close_app()
# 关闭驱动
driver.quit()
- 关闭app后还能继续操作手机其它应用;
- 关闭驱动,可以理解为锁屏,不能在进行任何操作。
安装、卸载、是否安装 app
# 安装app,app_path为安装文件完整路径名
driver.install_app(app_path)
# 卸载app,app_id为app包名
driver.remove_app(app_id)
# 判断app是否安装,app_id为app包名
driver.is_app_installed(app_id)
置于后台
# 置于后台,seconds表示秒数
driver.background_app(seconds)
小结
跳转应用:driver.start_activity(appPackage, appActivity)
获取包名:driver.current_package
获取界面名:driver.current_activity
关闭APP:driver.close_app()
关闭驱动:driver.quit()
安装app:driver.install_app(app_path)
卸载app:driver.remove_app(app_id)
判断app是否安装:driver.is_app_installed(app_id)
置于后台:background_app(seconds)
例:
"""
需求:
1、判断计算器是否安装,如果安装,则进⾏卸载,否则安装
com.google.android.calculator/com.android.calculator2.Calculator
2、启动设置界⾯
3、置于后台3秒钟
4、关闭设置界⾯
5、关闭app驱动
"""
import time
import os
from appium import webdriver
# 定义字典变量
desired_caps = {}
# 字典追加启动参数
# 需要连接的手机的平台(不限制大小写)
desired_caps["platformName"] = "Android"
# 注意:版本号必须正确
# 需要连接的手机的版本号(比如 5.2.1 的版本可以填写 5.2.1 或 5.2 或 5 ,以此类推)
desired_caps["platformVersion"] = "9"
# 需要连接的手机的设备号(andoird平台下,可以随便写,但是不能不写)
desired_caps["deviceName"] = "111"
# 需要启动的程序的包名
desired_caps["appPackage"] = "com.android.launcher3"
# 需要启动的程序的界面名
desired_caps["appActivity"] = ".Launcher"
# 设置中⽂
desired_caps["unicodeKeyboard"] = True
desired_caps["resetKeyboard"] = True
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
time.sleep(3)
is_installed = driver.is_app_installed('com.google.android.calculator')
if is_installed:
print('计算器正在卸载...')
# 卸载
driver.remove_app("com.google.android.calculator")
print('计算器卸载成功...')
else:
print("计算器不存在,正在安装...")
# 安装
path = os.path.dirname(__file__)
driver.install_app(path + "/Calculator.apk")
print("安装计算器app成功!")
time.sleep(3)
# 启动设置界⾯
print("启动设置界⾯...")
driver.start_activity("com.android.settings", ".Settings")
time.sleep(3)
print("设置应⽤置于后台3秒钟...")
# 置于后台3秒
driver.background_app(3)
print("关闭设置app...")
# 关闭设置
driver.close_app()
print("关闭设置app后获取包名:", driver.current_package)
time.sleep(3)
print("退出driver驱动")
driver.quit()
4、元素定位API
定位单个元素
通过UiAutoMatorViewer可以获取到元素信息,appium提供根据这些信息找到具体元素方法
# ID定位
driver.find_element_by_id(resource-id属性值)
# class定位
driver.find_element_by_class_name(class属性值)
# xpath定位
driver.find_element_by_xpath(xpath表达式)
# name定位
driver.find_element_by_accessibility_id(content-desc属性值)
注意:
当定位到多个符合条件的元素时,默认返回第一个
例子:
需求:打开手机《设置》应用,完成下面的步骤
- ①.使用ID定位,定位“放大镜”按钮,并点击
- ②.使用CLASS定位,定位“输入框”,输入“hello”
- ③.使用XPATH定位,定位“返回”,并点击
- ④.使用NAME定位,定位“放大镜”按钮,并点击
- ⑤.等待3s,关闭app
"""
打开手机《设置》应用,完成下面的步骤
①.使用ID定位,定位“放大镜”按钮,并点击
②.使用CLASS定位,定位“输入框”,输入“hello”
③.使用XPATH定位,定位“返回”,并点击
④.使用NAME定位,定位“放大镜”按钮,并点击
⑤.等待3s,关闭app
"""
import time
from appium import webdriver
# 定义字典变量
desired_caps = {}
# 字典追加启动参数
# 需要连接的手机的平台(不限制大小写)
desired_caps["platformName"] = "Android"
# 注意:版本号必须正确
# 需要连接的手机的版本号(比如 5.2.1 的版本可以填写 5.2.1 或 5.2 或 5 ,以此类推)
desired_caps["platformVersion"] = "9"
# 需要连接的手机的设备号(andoird平台下,可以随便写,但是不能不写)
desired_caps["deviceName"] = "111"
# 需要启动的程序的包名
desired_caps["appPackage"] = "com.android.settings"
# 需要启动的程序的界面名
desired_caps["appActivity"] = ".Settings"
# 设置中⽂
desired_caps["unicodeKeyboard"] = True
desired_caps["resetKeyboard"] = True
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
# ①.使用ID定位,定位“放大镜”按钮,并点击
# com.android.settings:id/search_action_bar
# ID定位
driver.find_element_by_id('com.android.settings:id/search_action_bar').click()
driver.find_element_by_xpath("//*[@class='android.widget.ImageButton']").click()
# ②.使用CLASS定位,定位“输入框”,输入“hello”
# android.view.ViewGroup
driver.find_element_by_class_name('android.view.ViewGroup').click()
driver.find_element_by_class_name('android.widget.EditText').send_keys("hello")
# ③.使用XPATH定位,定位“返回”,并点击
# "//*[@class='android.widget.ImageButton']"
driver.find_element_by_xpath("//*[@class='android.widget.ImageButton']").click()
# ④.使用NAME定位,定位“放大镜”按钮,并点击
# 向上导航
# name定位
driver.find_element_by_accessibility_id('向上导航').click()
print('流程执行完毕...')
# ⑤.等待3s,关闭app
time.sleep(3)
driver.close_app()
driver.quit()
定位一组元素
# ID定位一组
driver.find_elements_by_id(resource-id属性值)
# class定位一组
driver.find_elements_by_class_name(class属性值)
# xpath定位一组
driver.find_elements_by_xpath(xpath表达式)
# name定位一组
driver.find_elements_by_accessibility_id(content-desc属性值)
使用定位一组元素时,返回的数据是列表
例
需求:打开手机《设置》应用,完成下面的步骤:
①.使用ID定位,获取所有 resource-id 为 ”com.android.settings:id/title“ 的元素,并打印其文字内容
②.使用CLASS定位,获取所有class 为 ”android.widget.TextView“ 的元素,并打印其文字内容
③.使用XPATH定位,获取所有包含 ”设“ 的元素,并打印其文字内容
④.等待3s,关闭app
"""
需求:打开手机《设置》应用,完成下面的步骤:
①.使用ID定位,获取所有 resource-id 为 ”com.android.settings:id/title“ 的元素,并打印其文字内容
②.使用CLASS定位,获取所有class 为 ”android.widget.TextView“ 的元素,并打印其文字内容
③.使用XPATH定位,获取所有包含 ”设“ 的元素,并打印其文字内容
④.等待3s,关闭app
"""
import time
from appium import webdriver
# 定义字典变量
desired_caps = {}
# 字典追加启动参数
# 需要连接的手机的平台(不限制大小写)
desired_caps["platformName"] = "Android"
# 注意:版本号必须正确
# 需要连接的手机的版本号(比如 5.2.1 的版本可以填写 5.2.1 或 5.2 或 5 ,以此类推)
desired_caps["platformVersion"] = "9"
# 需要连接的手机的设备号(andoird平台下,可以随便写,但是不能不写)
desired_caps["deviceName"] = "111"
# 需要启动的程序的包名
desired_caps["appPackage"] = "com.android.settings"
# 需要启动的程序的界面名
desired_caps["appActivity"] = ".Settings"
# 设置中⽂
desired_caps["unicodeKeyboard"] = True
desired_caps["resetKeyboard"] = True
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
# ID定位一组
print('ID定位一组:', driver.find_elements_by_id('android:id/title'))
# class定位一组
print('class定位一组:', driver.find_elements_by_class_name('android.widget.TextView'))
# xpath定位一组
print('xpath定位一组:', driver.find_elements_by_xpath("//*[contains(@text,'设')]"))
print('流程执行完毕...')
# ⑤.等待3s,关闭app
time.sleep(3)
driver.close_app()
driver.quit()
模拟操作-点击、输入、清空
模拟点击
element.click()
模拟输入
element.send_keys(value)
清除文本
element.clear()
默认输入中文无效,但不会报错,需要在 ”前置代码“ 中增加两个参数:
desired_caps[unicodeKeyboard] = True
desired_caps['resetKeyboard'] = True
获取元素信息-文本、位置和大小
获取文本 element.text
获取位置 element.location
获取大小 element.size
例子:
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
# 2、点击放⼤镜
driver.find_element_by_id('com.android.settings:id/search_action_bar').click()
# 3、获取搜索⽂本值
# android:id/search_src_text
text = driver.find_element_by_id('android:id/search_src_text').text
print(text)
# 4、计算搜索框中⼼触摸点
size = driver.find_element_by_xpath("//*[@resource-id='android:id/search_src_text']").size
print("⼤⼩为:", size)
location = driver.find_element_by_xpath("//*[@resource-id='android:id/search_src_text']").location
print("位置:", location)
x = location.get("x") + (size.get("width") / 2)
y = location.get("y") + (size.get("height") / 2)
print("触摸中⼼位置x:{} , y:{}".format(x, y))
# 等待3s,关闭app
time.sleep(3)
driver.close_app()
driver.quit()
获取元素属性
获取元素属性
element.get_attribute(属性名)
获取属性值对应的属性名和实际UIAutoMatorViewer显示的不一定一致
获取resource-id resourceId
获取content-desc name
获取class className
获取text text
例:
# 放⼤镜
els = driver.find_elements_by_id("com.android.settings:id/search_action_bar")
for el in els:
print("--" * 50)
print("1、enabled属性值为:", el.get_attribute("enabled"))
print("2、text属性值为:", el.get_attribute("text"))
print("3、content-desc属性值为:", el.get_attribute("name"))
print("4、resource-id属性值为:", el.get_attribute("resourceId"))
print("5、class属性值为:", el.get_attribute("className"))
print("--" * 50)
滑动
swipe(start_x,start_y,end_x,end_y)
- 特点:精准滑动(基于两个坐标 点滑动)
- 说明:针对坐标点进⾏操作
- swipe适合多次滑动,duration时间越长惯性越大
driver.swipe(start_x, start_y, end_x, end_y, duration=None)
driver.swipe(100,1000,100,400,duration=2000)
start_x: 起点X轴坐标
start_y: 起点Y轴坐标
end_x: 终点X轴坐标
end_y: 终点Y轴坐标
duration: 滑动这个操作一共持续的时间长度,单位:ms
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
driver.implicitly_wait(10)
"""
需求:从屏幕100,400 位置滑动到100,800位置
"""
# 练习1
driver.swipe(100, 400, 100, 800, duration=2000)
scroll滑动
- 从一个元素滑动到另一个元素,直到页面自动停止
- 特点:滚动(有惯性存在,滚动下不按下第⼀个元素)
- 说明:针对两个元素进⾏操作
- scroll滑动是两个元素之间的滑动只适合滑动一次的操作
- 惯性很大
driver.scroll(origin_el, destination_el)
origin_el:滑动开始的元素
destination_el: 滑动结束的元素
拖拽:drag_and_drop【推荐】
- 特点:拖拽(没有惯性,按下开始元素拖拽到指定元素位置)
- 说明:针对两个元素进⾏精准操作
- 从一个元素滑动到另一个元素,第二个元素替代第一个元素原本屏幕上的位置
- scroll滑动是两个元素之间的滑动只适合滑动一次的操作
- 无惯性
driver.drag_and_drop(origin_el, destination_el)
origin_el:滑动开始的元素
destination_el: 滑动结束的元素
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
sleep(1)
driver.implicitly_wait(10)
"""
需求:
1、从应用滑动到更多
2、从应用拖拽到更多
"""
exe = driver.find_element_by_xpath("//*[@text='应用']")
more = driver.find_element_by_xpath("//*[@text='更多']")
# 1、滚动
# driver.scroll(exe,more)
# 2、拖拽
driver.drag_and_drop(exe,more)
5、高级手势Api
高级手势TouchAction:轻敲、按下、抬起、等待、长按、移动
实现步骤:
# 创建TouchAction对象
touch_action = TouchAction(driver)
# 调用高级手势对象提供所想执行的手势方法
touch_action.手势方法
# 执行手势
touch_action.perform()
所有手势都要通过执行perform()函数才会运行
也可以链式调用
TouchAction(driver).手势方法.perform()
轻敲
轻敲:模拟手指对某个元素或坐标按下并快速抬起
- 创建TouchAction对象
touch_action = TouchAction(driver)
- 调用轻敲,元素对象或坐标二选一
touch_action.tap(element=None,x=None,y=None)
- 执行手势
touch_action.perform()
例子:
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
driver.implicitly_wait(10)
# - 调用轻敲,元素对象或坐标二选一
for i in range(5):
TouchAction(driver).tap(element=None, x=768, y=697).perform()
time.sleep(5)
driver.quit()
按下和抬起
按下和抬起 模拟手指一直按下,模拟手指抬起。可以用来组合成轻敲或长按的操作
实现方法
- 创建TouchAction对象
touch_action = TouchAction(driver)
- 调用按下,元素对象或坐标二选一
touch_action.press(el=None,x=None,y=None)
- 调用抬起
touch_action.release()
- 执行手势
touch_action.perform()
- 换个写法,连续调用也OK。
TouchAction(driver).press(el=None,x=None,y=None).perform()
TouchAction(driver).release().perform()
例子:
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
driver.implicitly_wait(10)
TouchAction(driver).press(el=None, x=768, y=697).perform()
TouchAction(driver).release().perform()
time.sleep(5)
driver.quit()
"""
手势操作:TaouchAction类
1、轻敲:tap(元素,x,y)
2、按下:press
3、抬起:release()
重点:执行手势,必须调用perform()方法
# 需求:
1、使用按下和抬起 点击WLAN
"""
# 获取WLAN元素
wlan = driver.find_element_by_xpath("//*[@text='WLAN']")
loc = wlan.location
print(loc)
# 效果类似点击
TouchAction(driver).press(x=loc.get("x"),y=loc.get("y")).release().perform()
长按
长按 模拟手指对元素或坐标的长按操作
实现方法
- 模拟手指对元素或者坐标的长按操作。
TouchAction(driver).long_press(el=None,duration=1000).perform()
TouchAction(driver).long_press(x=None,y=None,duration=1000).perform()
例子
"""
手势操作:TaouchAction类
1、轻敲:tap(元素,x,y)
2、按下:press
3、抬起:release()
4、长安:long_press()
重点:执行手势,必须调用perform()方法
# 需求:
1、使用点击WLAN
2、长按3秒无线,出现菜单(修改网络)
"""
# 获取WLAN元素
wlan = driver.find_element_by_xpath("//*[@text='WLAN']")
wlan.click()
# 必须暂时一定时间 暂停3秒
sleep(3)
# 效果类似点击
TouchAction(driver).long_press(x=777,y=375).perform()
移动和思考时间(移动和等待)
手指移动 :模拟手指移动操作
思考时间 :模拟手指等待,在执行下一步时等待一段指定时间
实现方法
- 模拟手指对元素或坐标的移动操作。
TouchAction(driver).move_to(el=None,x=None,y=None).perform()
- 模拟手指暂停到当前动作指定时间。
TouchAction(driver).wait(ms=time).perform()
例子:
"""
需求:绘制解锁图案 Z
"""
# 应用和通知 滑动到 存储
exe = driver.find_element_by_xpath("//*[@text='应用和通知']")
more = driver.find_element_by_xpath("//*[@text='存储']")
driver.drag_and_drop(more, exe)
loc = driver.find_element_by_xpath("//*[@text='安全性和位置信息']")
TouchAction(driver).tap(element=loc).perform()
# 打开设置屏幕锁定方式 点击绘图
TouchAction(driver).tap(element=None, x=150, y=360).perform()
# 点击图案
TouchAction(driver).tap(element=driver.find_element_by_xpath("//*[@text='图案']")).perform()
# 绘制Z
TouchAction(driver).press(x=681, y=463).wait(100).move_to(x=922, y=456).wait(100).move_to(x=680, y=696).wait(
100).move_to(x=919, y=696).wait(100).perform()
6、手机操作
获取分辨率和截图
实现方法
获取手机分辨率。
driver.get_window_size()
获取手机截图。
get_screenshot_as_file(filename)
获取当前网络类型、设置手机网络网络类型
实现方法
- 获取手机网络。
driver.network_connection()
- 获取手机网络。
driver.set_network_connection(connectionType)
connectionType:表示网络设置类型
网络类型:
例子:
"""
需求:
1、查看当前网络类型
2、设置网络类型为飞行模式
3、获取当前屏幕分辨率
4、截图保存
"""
# 1、查看当前网络类型
print("当前网络类型为:",driver.network_connection)
# 2、设置网络类型为飞行模式
driver.set_network_connection(6)
print("设置之后的网络类型为:",driver.network_connection)
# 3、获取当前屏幕分辨率
print("当前屏幕分辨率为:",driver.get_window_size())
# 4、截图保存
driver.get_screenshot_as_file("./screen.png")
按键
发送键到设备。
driver.press_keycode(keycode)
keycode:表示手机设备的默认键码
Android KeyCode列表参考
- 常用字母数字键
/**
* 0键 - 9键 (数字键盘)
*/
public static final int KEYCODE_0 = 7;
public static final int KEYCODE_1 = 8;
public static final int KEYCODE_2 = 9;
public static final int KEYCODE_3 = 10;
public static final int KEYCODE_4 = 11;
public static final int KEYCODE_5 = 12;
public static final int KEYCODE_6 = 13;
public static final int KEYCODE_7 = 14;
public static final int KEYCODE_8 = 15;
public static final int KEYCODE_9 = 16;
/**
* A键 - Z键 (字母键盘)
*/
public static final int KEYCODE_A = 29;
public static final int KEYCODE_B = 30;
public static final int KEYCODE_C = 31;
public static final int KEYCODE_D = 32;
public static final int KEYCODE_E = 33;
public static final int KEYCODE_F = 34;
public static final int KEYCODE_G = 35;
public static final int KEYCODE_H = 36;
public static final int KEYCODE_I = 37;
public static final int KEYCODE_J = 38;
public static final int KEYCODE_K = 39;
public static final int KEYCODE_L = 40;
public static final int KEYCODE_M = 41;
public static final int KEYCODE_N = 42;
public static final int KEYCODE_O = 43;
public static final int KEYCODE_P = 44;
public static final int KEYCODE_Q = 45;
public static final int KEYCODE_R = 46;
public static final int KEYCODE_S = 47;
public static final int KEYCODE_T = 48;
public static final int KEYCODE_U = 49;
public static final int KEYCODE_V = 50;
public static final int KEYCODE_W = 51;
public static final int KEYCODE_X = 52;
public static final int KEYCODE_Y = 53;
public static final int KEYCODE_Z = 54;
- 功能键
功能键位于键盘的左下角,主要控制输入的操作,比如回车、删除等。
public static final int KEYCODE_ENTER = 66; //回车
public static final int KEYCODE_DEL = 67; //删除
public static final int KEYCODE_TAB = 61; //Tab
/**
* 方向键
*/
public static final int KEYCODE_DPAD_UP = 19; // 上箭头
public static final int KEYCODE_DPAD_DOWN = 20; // 下箭头
public static final int KEYCODE_DPAD_LEFT = 21; // 左箭头
public static final int KEYCODE_DPAD_RIGHT = 22; // 右箭头
- 控制键
如Home按钮、Menu按钮、返回按钮、音量键、亮度键等控制设备的按键。
/**
* 控制键
*/
public static final int KEYCODE_BACK = 4; //返回键(左下角)
public static final int KEYCODE_HOME = 3; //Home键(中间大圆钮)
public static final int KEYCODE_APP_SWITCH = 187; //任务键(最近打开的应用列表键)
public static final int KEYCODE_VOLUME_UP = 24; //音量增加
public static final int KEYCODE_VOLUME_DOWN = 25; //音量减少
public static final int KEYCODE_BRIGHTNESS_UP = 220;//增加亮度
public static final int KEYCODE_BRIGHTNESS_DOWN = 221;//降低亮度
例子:
"""
需求:
1、点击三次音量+ 24
2、点击返回 4
3、点击两次音量- 25
"""
i = 0
# 三次增大音量
while i < 3:
driver.press_keycode(24)
print('voice +')
i += 1
# 点击返回
driver.press_keycode(4)
print('return')
i = 0
# 两次减小音量
while i < 2:
driver.press_keycode(25)
print('voice -')
i += 1
操作通知栏
应⽤场景:检查服务器发送的通知
- 打开通知栏。
driver.open_notifications()
appium官方并没有为我们提供关闭通知的api,那么现实生活中怎么关闭,就怎样操作就行,比如,手指从下往上滑动,或者,按返回键
例子
"""
需求:
1、打开通知栏,点击通知栏的信息
"""
# 打开通知栏
driver.open_notifications()
sleep(2)
# 查找信息并点击
driver.find_element_by_xpath("//*[@text='应⽤宝.apk']").click()
7、其他
- toast获取
toast消息为移动应⽤中,⼀种⿊底⽩字提示信息,有时间限制。
- 为什么要获取toast消息?
断⾔内容
步骤
- 安装依赖库
- 安装依赖库: pip install uiautomator2
- 配置driver启⽤参数
desired_caps['automationName'] = 'Uiautomator2'
- 编写代码获取⽂本值
# 获取toast消息 msg = driver.find_element_by_xpath("//*[contains(@text,'请先勾选同意')]").text print("toast消息为:",msg)
例子:
from time import sleep
from appium import webdriver
# 定义字典变量
from appium.webdriver.common.touch_action import TouchAction
desired_caps = {}
# 字典追加启动参数
desired_caps["platformName"] = "Android"
desired_caps["platformVersion"] = "6.0.1"
desired_caps["deviceName"] = "192.168.56.101:5555"
desired_caps["appPackage"] = "com.netease.newsreader.activity"
desired_caps["appActivity"] = "com.netease.nr.phone.main.MainActivity"
# 获取toast
desired_caps['automationName'] = 'Uiautomator2'
# 设置中文
desired_caps["unicodeKeyboard"] = True
desired_caps["resetKeyboard"] = True
# 获取driver/
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
driver.implicitly_wait(10)
"""
需求:
1、获取网易新闻未同意协议进行登录 --> toast消息
"""
# 点击未登录
driver.find_element_by_xpath("//*[@text='未登录']").click()
# 点击登录
driver.find_element_by_xpath("//*[@text='登录']").click()
# 点击微信登录
driver.find_element_by_xpath("//*[@text='微信登录']").click()
# 获取toast消息
msg = driver.find_element_by_xpath("//*[contains(@text,'请先勾选同意')]").text
print("toast消息为:",msg)
sleep(3)
driver.quit()
- webview App测试
WebView App 通过手机浏览器访问的项目
- 项目页面上的元素不能直接通过UiAutoMatorView查看元素
- 不能直接进行定位
- 打开项目需要先操作Native App(浏览器)网址的输入框
- 需要浏览器驱动环境才能实现
- app中嵌套web信息,如果不切换⽆法定位操作
# 获取当前所有的环境
print(driver.contexts)
# 切换环境
driver.switch_to.context("WEBVIEW_com.android.browser")
例子:
from time import sleep
from appium import webdriver
# 定义字典变量
from appium.webdriver.common.touch_action import TouchAction
desired_caps = {}
# 字典追加启动参数com.tencent.news/.activity.SplashActivity
desired_caps["platformName"] = "Android"
desired_caps["platformVersion"] = "6.0.1"
desired_caps["deviceName"] = "192.168.56.101:5555"
desired_caps["appPackage"] = "com.android.browser"
desired_caps["appActivity"] = ".BrowserActivity"
# 获取toast
desired_caps['automationName'] = 'Uiautomator2'
# 设置中文
desired_caps["unicodeKeyboard"] = True
desired_caps["resetKeyboard"] = True
# 获取driver
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
driver.implicitly_wait(10)
# 打开浏览器
driver.find_element_by_xpath("//*[@resource-id='com.android.browser:id/url']").send_keys("https://m.baidu.com/")
# 输入回车
driver.press_keycode(66)
sleep(3)
# 获取当前所有的环境
print(driver.contexts)
# 切换环境
driver.switch_to.context("WEBVIEW_com.android.browser")
# 定位元素操作
driver.find_element_by_xpath("//*[@id='index-kw']").send_keys("test123456")
sleep(3)
driver.quit()
- Monkey
主要用于Android 的压力测试 自动的一个压力测试小工具, 主要目的就是为了测试app是否会Crash.
-
安装
不需要安装,Monkey程序由Android系统自带,使用Java语言写成,在Android文件系统中的存放路径是:/system/framework/monkey.jar;
-
启动方式
- 可以通过PC机CMD窗口中执行:
adb shell monkey {+命令参数}
来进行Monkey测试; - 在PC上adb shell 进入Android系统,通过执行
monkey {+命令参数}
来进行Monkey 测试;
- 可以通过PC机CMD窗口中执行:
-
使用monkey测试 包 随机事件100次 输入日志文件
adb shell monkey -p cn.goapk.market 100 > 路径/log.txt
adb shell monkey -p com.yunmall.lc -v -v 10000 >xxx.log -p :包名 -v -v :⽇志 10000 :乱抓的时间次数 -s :设置随机种⼦数(两次执⾏随机种⼦数⼀样,执⾏的事件也是⼀样)
参数
adb shell monkey -p fishjoy.control.menu -p cn.goapk.market 100
指定app包
-p {被测试的app包名}
用此参数指定一个或多个包。
指定包之后,monkey将只允许系统启动指定的app。
如果指定包列表, monkey将允许系统启动设备中的所有app。
adb shell monkey -p cn.goapk.market -v -v 100
日志级别
-v 通过多个-v的个数来指定查看日志级别。
-v :缺省值,仅提供启动提示、测试完成和最终结果等少量信息
-v -v :提供较为详细的日志,包括每个发送到Activity的事件信息
-v -v -v :最详细的日志,包括了测试中选中/未选中的Activity信息
随机种子数
-s (随机数种子)
用于指定伪随机数生成器的seed值,如果seed相同,则两次Monkey测试所产生的事件序列也相同
monkey测试1:adb shell monkey -p cn.goapk.market –s 10 100
monkey测试2:adb shell monkey -p cn.goapk.market –s 10 100
事件间隔时间 --throttle <毫秒>
adb shell monkey -p cn.goapk.market --throttle 3000 100
monkey测试执行和日志分析
-
一般在功能测试完毕之后
-
执行方式:
- 多次小批量事件测试≤10W次事件
- 大批量随机性测试
- 正常情况
- 如果Monkey测试顺利执行完成,在log的最后,会打印出当前执行事件的次数和所花费的时间; //Monkey finished 代表执行完成
- 执行过程中出现无响应、崩溃或异常退出后,打开日志进行分析
- 异常情况
-
- 程序无响应的问题: 在日志中搜索“ANR” (可能仅仅是因为卡)
-
- 崩溃问题:在日志中搜索“Exception” (如果出现空指针,NullPointerException) 肯定是有bug Monkey执行中断,在log最后也能看到当前执行次数
-
8、报错问题
java.lang.SecurityException: Permission Denial: starting Intent
三、appium+selenium+po封装
需求:
1、app订单业务
2、web发货业务
page页面公共方法
base.py
# page页面公共方法
import os
import time
from selenium.webdriver.support.wait import WebDriverWait
from base import log
from config import DIR_PATH
class Base:
# app下单流程自动化用例实现
# 初始化方法
def __init__(self, driver):
log.info("正在初始化,driver对象:{}".format(driver))
self.driver = driver
# 查找元素方法
def base_find(self, loc, timeout=10, poll=0.5):
# .format方法,{}占位符会替换为loc
print("-->正在查找:{}".format(loc))
# * 是一个解包运算符,它用于将可迭代对象(例如列表、元组)解包成单独的元素
return WebDriverWait(self.driver, timeout, poll).until(lambda x: x.find_element(*loc))
# 点击方法
def base_click(self, loc):
self.base_find(loc).click()
# 输入方法
def base_input(self, loc, value):
# 获取元素
el = self.base_find(loc)
# 清空
el.clear()
# 输入
el.send_keys(value)
# 获取文本方法
def base_get_text(self, loc):
return self.base_find(loc).text
# 截图方法
def base_get_img(self):
img_path = DIR_PATH + os.sep + "img" + os.sep + "{}.png".format(time.strftime("%Y%m%d%H%M%S"))
self.driver.get_screenshot_as_file(img_path)
# 2、web后台实现发货业务
# 由于web发货页面需要切换iframe标签,所以在Base类中新增两个方法(1、切换frame 2、回到默认目录)
# 切换frame
def base_switch_frame(self, loc):
# 获取元素
el = self.base_find(loc)
# 执行切换
self.driver.switch_to.frame(el)
# 恢复frame
def base_default_frame(self):
self.driver.switch_to.default_content()
页面page
1、页面模块数据
page/__init__.py
from selenium.webdriver.common.by import By
order_on = None
"""
一、以下为app登录模块配置信息
"""
# 我的
app_login_me = By.XPATH, "//*[@text='我的']"
# 登录图片(连接)
app_login_link = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/head_img']"
# 用户名
app_username = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/mobile_et']"
# 密码
app_pwd = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/pwd_et']"
# 协议
app_pro = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/agree_btn']"
# 登录按钮
app_login_btn = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/login_tv']"
# 昵称
app_nickname = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/nick_name_tv']"
"""
二、以下为app订单模块配置信息
"""
# 首页
app_order_index = By.XPATH, "//*[@text='首页']"
# 搜索框 -1 android.widget.EditText
app_order_search_text1 = By.XPATH, "//*[@class='android.widget.EditText']"
# 搜索框 -2 com.tpshop.malls:id/search_et
app_order_search_text2 = By.XPATH, "//*[@class='android.widget.EditText']"
# 搜索按钮
app_order_search_btn = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/search_btn']"
# 点击第一张图片 com.tpshop.malls:id/product_pic_img
app_order_img = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/product_pic_img']"
# 加入购物车 com.tpshop.malls:id/add_cart_tv
app_order_add_cart = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/add_cart_tv']"
# 确定
app_order_cart_ok = By.XPATH, "//*[@text='确定']"
# 购物车
app_order_cart = By.XPATH, "//*[@text='购物车']"
# 立即购买
app_order_now_purchase = By.XPATH, "//*[@text='立即购买']"
# 提交订单
app_order_submit_order = By.XPATH, "//*[@text='提交订单']"
# 点击立即支付
app_order_now_pay = By.XPATH, "//*[@text='立即支付']"
# 输入支付密码 com.tpshop.malls:id/pwd_et
app_order_pay_pwd = "//*[@resource-id='com.tpshop.malls:id/pwd_et']"
# 确定
app_order_pay_ok = By.XPATH, "//*[@text='确定']"
# 订单编号 com.tpshop.malls:id/pay_trade_no_tv
app_order_no = By.XPATH, "//*[@resource-id='com.tpshop.malls:id/pay_trade_no_tv']"
"""
三、web后台登录配置信息整理
"""
# 用户名
web_login_username = By.CSS_SELECTOR, "[name='username']"
# 密码
web_login_pwd = By.CSS_SELECTOR, "[name='password']"
# 验证码
web_login_verify = By.CSS_SELECTOR, "[name='vertify']"
# 登录按钮
web_login_submit = By.CSS_SELECTOR, "[name='submit']"
# 昵称
web_login_nickname = By.CSS_SELECTOR, ".bgdopa-t"
# 订单菜单
web_order = By.XPATH, "//a[text()='订单']"
# 左侧菜单 发货单
web_order_goods = By.XPATH, "//a[text()='发货单']"
# iframe
web_order_iframe = By.CSS_SELECTOR, "#workspace"
# 去发货 //div[text()='202112161517008312']/../..//td[@class='handle']//a[1]
web_order_go_goods = By.XPATH, "//a[text()='去发货']"
# 物流公司
web_order_company = By.CSS_SELECTOR, "[value='YZPY']"
# 配送单号
web_order_order_no = By.CSS_SELECTOR, "#invoice_no"
# 确认发货
web_order_goods_ok = By.CSS_SELECTOR, "ncap-btn-send"
# 打印配置单
web_order_print_order = By.CSS_SELECTOR, ".fa-print"
# 获取订单编号
web_order_on = By.XPATH, "//div[@id='printDiv']/div[@class='contactinfo']/dl[1]/dd[2]"
# 难点:如何根据指定单号,查找对应的去发货元素?
# 思路:想找共同的父级,在逐级查找
2、app登录page结构(page_app_login.py)
from time import sleep
import page
from base.base import Base
"""
将操作步骤进行封装+业务组合方法
"""
class PageAppLogin(Base):
# 1、点击 我的
def page_app_click_me(self):
self.base_click(page.app_login_me)
# 2、点击 登录头像
def page_app_click_login_link(self):
self.base_click(page.app_login_link)
# 3、输入用户名
def page_app_input_username(self, username):
self.base_input(page.app_username, username)
# 4、输入密码
def page_app_input_pwd(self, pwd):
self.base_input(page.app_pwd, pwd)
# 5、点击勾选协议
def page_app_click_pro(self):
self.base_click(page.app_pro)
# 6、点击登录按钮
def page_app_click_login_btn(self):
self.base_click(page.app_login_btn)
# 7、获取登录昵称
def page_app_get_nickname(self):
sleep(2)
return self.base_get_text(page.app_nickname)
# 8、组合业务方法
def page_app_login(self, username="13600001111", pwd="123456"):
self.page_app_click_me()
self.page_app_click_login_link()
self.page_app_input_username(username)
self.page_app_input_pwd(pwd)
self.page_app_click_pro()
self.page_app_click_login_btn()
3、订单业务结构搭建(page_order.py)
from time import sleep
import page
from base.base import Base
class PageAppOrder(Base):
# 点击首页
def page_app_click_index(self):
sleep(1)
self.base_click(page.app_order_index)
# 点击搜索框
def page_app_click_search_text(self):
sleep(1)
self.base_click(page.app_order_search_text1)
# 输入搜索内容
def page_app_input_search_text(self,value="小米"):
sleep(1)
self.base_input(page.app_order_search_text1,value)
# 点击搜索按钮
def page_app_click_search_btn(self):
sleep(1)
self.base_click(page.app_order_search_btn)
# 选择商品
def page_app_select_photo(self):
sleep(1)
self.base_click(page.app_order_img)
# 点击加入购物车
def page_app_add_cart(self):
sleep(1)
self.base_click(page.app_order_add_cart)
# 点击确定
def page_app_cart_ok(self):
sleep(1)
self.base_click(page.app_order_cart_ok)
# 点击购物车
def page_app_click_cart(self):
sleep(1)
self.base_click(page.app_order_cart)
# 点击立即购买
def page_app_now_purchase(self):
sleep(1)
self.base_click(page.app_order_now_purchase)
# 点击提交订单
def page_app_click_submit_order(self):
sleep(1)
self.base_click(page.app_order_submit_order)
# 点击 立即支付
def page_app_click_now_pay(self):
sleep(1)
self.base_click(page.app_order_now_pay)
# 输入 密码
def page_app_input_pwd(self,pwd="123456"):
sleep(1)
self.base_input(page.app_pwd,pwd)
# 点击确定
def page_app_click_sure(self):
sleep(1)
self.base_click(page.app_order_pay_ok)
# 获取订单编号
def page_app_get_order_on(self):
sleep(2)
return self.base_get_text(page.app_order_no)
# 下单业务方法
def page_app_order(self,search_value="小米", pwd="123456"):
self.page_app_click_index()
self.page_app_click_search_text()
self.page_app_input_search_text(search_value)
self.page_app_click_search_btn()
self.page_app_select_photo()
self.page_app_add_cart()
self.page_app_cart_ok()
self.page_app_click_cart()
self.page_app_now_purchase()
self.page_app_click_submit_order()
self.page_app_click_now_pay()
self.page_app_input_pwd(pwd)
self.page_app_click_sure()
4、web登录
import page
from base.base import Base
class PageWebLogin(Base):
# 1、输入用户名
def page_web_username(self, value):
self.base_input(page.web_login_username, value)
# 2、输入密码
def page_web_pwd(self, value):
self.base_input(page.web_login_pwd, value)
# 3、输入验证码
def page_web_verify_code(self, value):
self.base_input(page.web_login_verify, value)
# 4、点击登录按钮
def page_web_login_btn(self):
self.base_click(page.web_login_submit)
# 5、获取登录昵称
def page_web_nickname(self):
return self.base_get_text(page.web_login_nickname)
# 登录业务方法
def page_web_login(self, username="admin", pwd="123456", code="8888"):
self.page_web_username(username)
self.page_web_pwd(pwd)
self.page_web_verify_code(code)
self.page_web_login_btn()
5、web订单
import page, time
from base.base import Base
class PageWebOrder(Base):
# 订单菜单
def page_web_order_menu(self):
self.base_click(page.web_order)
# 左侧 发货单
def page_web_order_goods(self):
self.base_click(page.web_order_goods)
# 工作区域 去发货
def page_web_go_goods(self):
# 切换iframe
self.base_switch_frame(page.web_order_iframe)
self.base_click(page.web_order_go_goods)
# 物流公司
def page_order_company(self):
self.base_click(page.web_order_company)
# 配送单号
def page_order_input_order_no(self):
value = str(time.strftime("%Y%m%d%H%M%S"))
self.base_input(page.web_order_order_no, value)
# 确认发货
def page_order_goods_ok(self):
self.base_click(page.web_order_goods_ok)
# 打印配置单
def page_order_print_order(self):
self.base_click(page.web_order_print_order)
# 获取订单编号
def page_order_get_on(self):
return self.base_get_text(page.web_order_on)
# 订单发货业务
def page_order_go_goods(self):
self.page_web_order_menu()
self.page_web_order_goods()
self.page_web_go_goods()
self.page_order_company()
self.page_order_input_order_no()
self.page_order_goods_ok()
self.page_order_print_order()
6、工具类
utils.py
import os
import json
import appium.webdriver
import logging.handlers
from selenium import webdriver
from config import DIR_PATH, HOST
class GetDriver:
__app_driver = None
__web_driver = None
# 获取App Driver
@classmethod
def get_app_driver(cls):
if cls.__app_driver is None:
# 设置启动
desired_caps = {}
# 必填-且正确
desired_caps['platformName'] = 'Android'
# 必填-且正确
desired_caps['platformVersion'] = '9'
# 必填
desired_caps['deviceName'] = '192.168.0.111:5555'
# APP包名 com.tpshop.malls/.SPMainActivity
desired_caps['appPackage'] = "com.tpshop.malls"
# 启动名
desired_caps['appActivity'] = ".SPMainActivity"
# 设置中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 设置driver
cls.__app_driver = appium.webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return cls.__app_driver
# 获取Web Driver
@classmethod
def get_web_driver(cls):
if cls.__web_driver is None:
cls.driver = webdriver.Chrome()
cls.driver.get(HOST)
cls.driver.maximize_window()
return cls.driver
# 读取json工具
def read_json(filename, key):
file_path = DIR_PATH + os.sep + "data" + os.sep + filename
arrays = []
with open(file_path, "r", encoding="utf-8")as f:
for data in json.load(f).get(key):
arrays.append(tuple(data.values())[1:])
return arrays
def write_json(value):
file_path = DIR_PATH + os.sep + "data" + os.sep + "expect.json"
with open(file_path, "w", encoding="utf-8")as f:
data = {"expect": [{"desc": "app订单编号", "order_no": value}]}
json.dump(data, f)
# 日志封装
# 日志应用
# 1、记录程序运行步骤 base->info
# 2、记录程序错误 script->error
class GetLog:
__log = None
@classmethod
def get_log(cls):
if cls.__log is None:
# 获取日志器
cls.__log = logging.getLogger()
# 设置入口级别
cls.__log.setLevel(logging.INFO)
# 获取处理器
filename = DIR_PATH + os.sep + "log" + os.sep + "tpshop_auto.log"
tf = logging.handlers.TimedRotatingFileHandler(filename=filename,
when="midnight",
interval=1,
backupCount=3,
encoding="utf-8")
# 获取格式器
fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
fm = logging.Formatter(fmt)
# 将格式器添加到处理器
tf.setFormatter(fm)
# 将处理器添加到日志器
cls.__log.addHandler(tf)
# 返回日志器
return cls.__log
if __name__ == '__main__':
GetDriver.get_app_driver()
7、config.py
import os
# 项目路径
DIR_PATH = os.path.dirname(__file__)
# web后台url
HOST = "http://hmshop-test.net/admin"
8、run_suite
# 执行自动化测试并生成报告
import os
import unittest
from htmltestreport import HTMLTestReport
from config import DIR_PATH
suite = unittest.defaultTestLoader.discover("./script")
file_path = DIR_PATH + os.sep + "report" + os.sep + "tpshop_auto.html"
HTMLTestReport(file_path).run(suite)