获取当前应用的包名和启动名
driver.current_package # 获取当前应用包名
driver.current_activity # 获取当前界面启动名
关闭当前 APP
driver.close_app() # 这是一个方法,不是属性,关闭前置代码中打开的 APP
启动其它的 APP
driver.start_activity( "包名","启动名" ) # 可以在当前 APP 内再次打开另外一个 APP
退出( 关闭 ) 当前 APP
driver.quit() # 可以关闭当前次的会话,失去与手机之间的连接
安装卸载 APP
driver.install_app( "包所在路径" ) # 安装对应的 APK 包
driver.remove_app( "包名" ) # 卸载之前确保该应用已经安装在手机上
判断是否安装了某个 APP
driver.is_app_installed( "包名" ) # 传入一个包名,返回布尔值,判断当前应用是否被安装
在后台运行 APP
driver.background_app( 时间 ) # 接收一个时间参数,单位是毫秒,让应用置于后台运行一定的时间
查询手机的时间
driver.device_time # 这是一个查询当前设备时间的属性
获取当前设备的屏幕分辨率
driver.get_window_size() # 返回一个字典,包含了设备的宽度和高度
发送键到设备
driver.keyevent( 码值 ) # 可以发送一个数值代表具体的手机操作,例如 4 就代表点击返回键
进行手机截图
driver.get_screenshot_as_file( "./1.png" ) # 将当前手机所在界面进行截图,传入的是希望的最终文件路径
获取当前屏幕内元素结构
driver.page_source # 这是一个属性,可以返回当前手机界面中对应元素的 xml 文档格式信息
操作手机通知栏【了解】
driver.open_notifications()
获取手机当前网络【了解】
print(driver.network_connection) # 这是一个属性,返回一个数字,表示当前设备连接的网络
设置手机网络【了解】
driver.set_network_connection(1) # 接收的是一个数字类型,表示不同的网络类型状态
我们之前在 adb 操作中可以通过 adb push 或者 adb pull 命令轻松的实现文件在PC与手机设备之间的传输操作,但是有了在 appium 的 webdriver 库中不能直接对文件整体进行操作,它允许操作是文件里具体的数据内容。同时手机对于数据的编码有自已的要求,所以如果我们想要将PC机上的一个文件最终上传到手机里,需要做的事情是先拿到该文件中的数据源,然后进行一系统的编码、解码操作,最终将满足手机支持的数据格式以字符的形式写入到手机对应的存储位置
# -*- coding=utf-8 -*-
import base64
# 导入python需要的 webdriver 库
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
# 读取2.txt里的内容
with open( "2.txt","r",encoding="utf8" ) as f1:
original_data = f1.read()
# 将从文件中读取出来的原始数据进行编码,编码成 utf8 [ 因为手机支持是经过 base64规则加密之后的utf8 ]
utf8_data = original_data.encode("utf8")
# 将上述编码成 utf8 规则的字符进行 base64 加密处理
base64_utf8_data = base64.b64encode( utf8_data )
# 将数据处理成 base64 加密后的utf8 编码之后还需要处理成 str 的格式才能写入手机
str_data = str( base64_utf8_data,"utf8" )
# 将具体的内容写入到手机具体的目录当中
driver.push_file( "./sdcard/000.txt",str_data )
拉取文件与上传文件思路完全一致,不一样的就是具体的代码书写,一个是从PC到手机,一个是从手机到 PC
# -*- coding=utf-8 -*-
import base64
# 导入python需要的 webdriver 库
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
# 先从手机的相应的目录下拿到对应的文件数据
phone_data = driver.pull_file( "./sdcard/0000.txt" )
# 我们从手机里拿到的是经过 base64 规则之后的 utf8 编码字符,所以我们首先需要进行 base64 的解密操作
utf8_data = base64.b64decode( phone_data )
# 此时我们手机有的是 utf8 对应的编码形式,我们如果想要写到PC机里,就需要对该数据进行字符化。
str_utf8 = str( utf8_data,"utf8" )
# 将数据写入到对应的目录里
with open( "./a.txt","w+",encoding="utf8" ) as f:
f.write( str_utf8 )
启动 uiautomator 工具 【因为后期经常使用,所以建议生成快捷方式】
通过该工具可以执行:打开之前已保存的截国、截取当前屏幕、带框框架截取当前屏幕、保存当前屏幕信息截图
注意:
如果在我们截屏的时候出现了错误,那么最有可能的就是 uiautomator 的 adb 服务与 我们之前的 appium adb 服务产生了冲突,所以在此时我们可以选择将 appium 服务器停止,先将需要的屏幕信息进行保存,然后再开启 appium 服务。
在一些分辨率比较好的电脑设备上执行打开操作的时候,有可能会无法显示正常的下拉框,所以此时我们可以将当前的设备分辨率调低然后进行操作。
示例代码
# -*- coding=utf-8 -*-
# 导入python需要的 webdriver 库
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
driver.find_element_by_xpath( "//*[contains(@text,'WLAN')]" ).click()
我们当前最终想要做的事情就是通过 python 去编写测试脚本,从而让我们之前手工的测试操作可以演变成运行对应的 py 文件。所以在这个过程中我们肯定会做的事情就是找到某个元素 ,然后对这个元素进行具体的操作。这以这个找元素就是我们的获取 | 定位元素,在 appium-python-client 当中定义了多种定义元素的方法函数,我们只需要调用就可以!
在 appium-python-client 的webdriver 库中,按着类型来说常见的查找元素方式就是 按着类名查找、按id名查找、按xpath 路径进行查找,为了方便记忆。此处给分成二类三种规则,分别是一次只找一个,一次找多个。不论是哪一种规则,都可以按 类名、id 名、xpath 路径进行查找
driver.find_element_by_class_name( "类名" ) # 传入一个类名会返回一个元素
driver.find_element_by_id( "id名" ) # 传入一个id名会返回对应的一个元素
driver.find_element_by_xpath( "xpath 规则" ) # 传入一个路径规则 会返回对应的一个元素
按类名查找多个
driver.find_elements_by_class_name( "类名" ) # 传入一个类名会返回具有该类名的多个元素
按id名查找多个
driver.find_elements_by_id( "id名" ) # 传入一个id名会返回具有该id名的多个元素
按xpath 查找多个
driver.find_elements_by_xpath( "xpath 规则" ) # 传入一个路径规则 会返回满足该规则的所有元素
通过上面的二类 六个函数我们可以发现在 webdriver 库中,选择到元素个数与采用的规则无法,如果使用的是?单个元素单个元素类型的方法,返回的结果一定是单个。如果使用的是?多个元素多个元素?类型的方法返回的一定是列表。
如果在使用?单个元素单个元素类型的方法时传入了一个不存在的属性值( id class xpath ),这个时候会抛出语法异常
如果在使用多个元素多个元素类型的方法时传入了一个不存在的属性值( id class xpath ),这个时候不会有任何错误的语法提示。
如果在使用多个元素多个元素类型的方法时传入了一个只存在一次的属性值,那么这个时候返回的是一个元素,但是该元素依然被放在了一个列表当中,且该列表里只有一个元素,我们需要使用下标进行访问。
单个元素. 类似于 find_element_by_id() 这类每次只查找一个的元素的方法 ??
多个元素. 类似于 find_elements_by_id() 这类每次可以获取到多个元素的方法 ??
当我们使用 python 脚本获取到一个元素之后就可以对它执行具体的操作,这里我们列举了几个经常使用到的事件操作以及常见的属性获取操作
点击操作
driver.find_element_by_XXX("定位元素的属性").click() # 找到某个元素后对它执行点击
输入字符操作
driver.find_element_by_XXXX( "定位元素的属性" ).send_keys( "写入的内容" ) # 在选中的输入控制中输入具体的内容【中文需要处理】
清空输入内容操作
driver.find_element_by_XXX( "定位元素的属性" ).clear() # 将选中的输入控制内容清空
获取不同属性值操作
# -*- coding=utf-8 -*-
# 导入python需要的 webdriver 库
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
# 使用的思路就是先获取到某个具体的元素,然后用这个元素对象去调用 get_attribute() 方法
# 在该方法中传入想要获取的属性值名称,常用的四个值有:
# resourceID 获取当前元素的 id 值
# className 获取当前元素的 类名 值
# text 获取当前的 text 属性值
# name 获取当前元素的 content-des或text 属性值
serarch_btn = driver.find_element_by_id( "com.android.settings:id/search" )
print( serarch_btn.get_attribute( "name" ) )
属性操作截图示例
属性和事件操作注意事项
desired_caps["resetKeyboard"] = True # 重置设备的输入键盘
desired_caps["unicodeKeyboard"] = True # 采用unicode码输入
# -*- coding=utf-8 -*-
# 导入python需要的 webdriver 库
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
desired_caps["resetKeyboard"] = True # 重置设备的输入键盘
desired_caps["unicodeKeyboard"] = True # 采用unicode码输入
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
# 获取设置界面中的搜索按钮并点击
driver.find_element_by_id( "com.android.settings:id/search" ).click()
# 进入到搜索界面之后选中 搜索框 并输入测试内容
driver.find_element_by_id( "android:id/search_src_text" ).send_keys( "测试中文字符" )
在我们使用元素定位方法去查找元素的时候由于不同的原因可能会出现某个元素我们无法立即获取到。但有的时候这个元素是真正存在于我们页面 DOM 中的,所以针对这种元素真实存在而我们无法立即获取到的情况,我们就可以在具体获取它之前添加对应的等待操作。其中appium-python-client 中能使用的操作有三种:强制等待、隐式等待、显示等待
固名思义,所谓的强制等待指的就是在我们定位某个具体元素之前强行的设置一段等待时间,这个操作我们通过 time.sleep() 来设置一定的时间就可以。让代码休眠一段时间后再执行
time.sleep( 3 ) # 表示将代码在等待 3s 之后执行
隐式等待由 webdriver 对象实例来调用,它的规则就是如果某个元素在第一时间没有查找到,那么就会等待上一定的时间,然后接着找,如果没有找到那么就会抛出没有找到的异常。
driver.implicitly_wait( 3 ) # 如果元素没有找到则等待 3s 钟之后继续查找,如果找不到则抛出异常
显式等待指的就是针对于某一个具体元素的获取操作,定义一段时长,在这个时长内每隔一定的时间就去查找一次元素。如果超出我们定义的时长还没有找到,那么就会抛出没有找到的异常
wait = WebDriverWait(driver,5,1) # driver 是当前连接对象 5是自定义timeout时长 1是查询的频率
wait.until( lambda x:x.find_element_by_id( "com.android.settings:id/search" )) # lambda 是匿名函数
我们使用 appium-python-client 里的 webdriver 库最终想要做的事情就是使用它来完成最终的自动化Python脚本去代替之前的手工操作,而在实际的手工操作中我们肯定会用到各种各样的手势动作,所以在 webdriver 库中就会有这样的一些API 存在,此处分为二类进行讲解:单一手势 + TouchAction 类自定义组合手势
# 在此之前执行连接手机操作
# 执行 swipe 操作
driver.swipe( 350,700,350,200,3000 ) # 前四个位置参数为数值,分别表示起始位置的绝对坐标。最后一个参数为时间,单位是毫秒,表示滑动的时长
driver.scroll( 350,700,350,200 ) # 语法规则和 swipe 一样,只是android无效
# -*- coding=utf-8 -*-
# 导入python需要的 webdriver 库
import time
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
from selenium.webdriver.support.wait import WebDriverWait
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
desired_caps["resetKeyboard"] = True # 重置设备的输入键盘
desired_caps["unicodeKeyboard"] = True # 采用unicode码输入
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
# 获取 swipe() 操作的起点和终点
save_btn = driver.find_element_by_xpath( "//*[contains(@text,'存储')]" )
more_btn = driver.find_element_by_xpath( "//*[contains(@text,'更多')]" )
# 执行 swipe 操作
driver.drag_and_drop( save_btn,more_btn )
注意:
? 01 swipe() 操作默认具有滑动惯性,只能接收绝对坐标,元点是相对于屏幕左上角为准,不能接收元素对象
? 02 scroll() 在安卓设备上是无法使用的,如果能执行其实和 swipe 是一样的
? 03 drap_and_drop() 操作没有惯性,并且它记录的位置是当前元素被获取时的位置
上述的三个方法只能满足于单一的手势应用场景,但是在实际的APP使用过程中我们还会遇到其它更多的手势需求,例如左右滑动、轻点屏幕、常按屏幕.......等。因此在 webdriver 库中又提供了一个 TouchAction 类,在该类下定义了几种单一的动作,可以独立使用,同时也可以组合使用,这样我们就可以在使用的时候去自定义自已需要的手势。
TouchAction( driver ).press( arg ).perform()
# arg 是按下的对象,可以是一个具本的元素对象 ,也可以是一个元素对象绝对坐标位置
# 同时传入元素对象和 元素对象的位置时 ,press 识别的是元素对象
# 如果传入的是绝对坐标值那么需要设置的是关键字参数
# 执行前置的连接代码
# 获取要操作的无素
save_btn = driver.find_element_by_xpath( "//*[contains(@text,'存储')]" )
more_btn = driver.find_element_by_xpath( "//*[contains(@text,'更多')]" )
WLAN_btn = driver.find_element_by_xpath( "//*[contains(@text,'WLAN')]" )
# 点击 wlan 按钮进入到对应的界面
WLAN_btn.click()
# 进入到 wlan 界面之后常按 wireless 按钮
wireless_btn = driver.find_element_by_xpath( "//*[contains(@text,'wireless')]" )
TouchAction(driver).long_press( wireless_btn ).perform() # long_press 的参数规则和 press 完全一样
# 执行前置的连接代码
# 获取要操作的无素
save_btn = driver.find_element_by_xpath( "//*[contains(@text,'存储')]" )
more_btn = driver.find_element_by_xpath( "//*[contains(@text,'更多')]" )
WLAN_btn = driver.find_element_by_xpath( "//*[contains(@text,'WLAN')]" )
# 点击 wlan 按钮进入到对应的界面
TouchAction( driver ).tap().perform() # tap的参数规则和 press 完全一样
TouchAction( driver ).press( obj ).release().perform() # release 就相当于离开动作
TouchAction( driver ).press( obj ).wait(500).release().perform() # 可以在某一个动作发生之后设置延时
move_to() 是一个常用的单一动作,我们可以通过它来执行多点之前的滑动操作
语法格式:TouchAction(driver).press(起点).move_to(位置1).move_to(位置2).release.perform()
其中在执行 move_to 之前我们需要先通过 press 来确定一个按下的起点,后续在 move_to 里设置我们具体的位置点,每个位置都使用坐标来表示,这个坐标是否为绝对坐标,我们可以依据 appium 服务器的版本来确定,换句话说不需要去死记 move_to() 里用的是相对还是绝对坐标,当我们发现是某种规则的时候我们只需要按着对应的规则去设置即可。
需求:使用 move_to() 去绘制手机设置里的图案解锁功能
示例代码
# -*- coding=utf-8 -*-
# 导入python需要的 webdriver 库
import time
from appium import webdriver
# 定义一个空字典来存放具体的配置参数
from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.support.wait import WebDriverWait
desired_caps = dict()
# 书写具体的参数
desired_caps["platformName"] = "android" # 当前的系统平台名称
desired_caps["platformVersion"] = "5.1.1" # 当前连接设备的 android 版本
desired_caps["deviceName"] = "emulator-5554" # 当前已连接设备的名称
desired_caps["appPackage"] = "com.android.settings" # 被测试 APP 的包名
desired_caps["appActivity"] = ".Settings" # 被测试 APP 的启动名
desired_caps["resetKeyboard"] = True # 重置设备的输入键盘
desired_caps["unicodeKeyboard"] = True # 采用unicode码输入
# 获取对应的连接
driver = webdriver.Remote( "http://localhost:4723/wd/hub",desired_caps )
# 获取 swipe() 操作的起点和终点
save_btn = driver.find_element_by_xpath( "//*[contains(@text,'存储')]" )
more_btn = driver.find_element_by_xpath( "//*[contains(@text,'更多')]" )
WLAN_btn = driver.find_element_by_xpath( "//*[contains(@text,'WLAN')]" )
# 滑动当前的屏幕,直到 安全 按钮出现,然后点击 安全按钮
for i in range(2):
driver.drag_and_drop( save_btn,more_btn )
# 获取 安全 按钮,并点击
safe_btn = driver.find_element_by_xpath( "//*[contains(@text,'安全')]" ).click()
# 进入到解锁绘制界面
driver.find_element_by_xpath( "//*[contains(@text,'屏幕锁定方式')]" ).click()
driver.find_element_by_xpath( "//*[contains(@text,'图案')]" ).click()
time.sleep( 2 )
# 执行绘制操作
TouchAction(driver).press(x=120,y=425).move_to( x=360,y=425 ).wait(200).move_to(x=360,y=665).wait(200).release().perform()