基于 Python 语言的 appium API 的使用。
1. Desired Capabilities
appium 在启动 session 时,需要提供 Desired Capabilities。Desired Capabilities 本质上是字典对象,由客户端生成并发送给服务器(appium Desktop),告诉服务器 App 运行的环境。
Desired Capabilities 的基本配置:
from appium import webdriver
desired_caps = {
'deviceName': 'Android Emulator',
'automationName': 'appium',
'platformName': 'Android',
'platformVersion': '7.0',
'appPackage': 'com.android.calculator2',
'appActivity': '.Calculator',
'noReset': True,
}
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_capabilities=desired_caps)
...
各配置含义如下:
- deviceName:启动的设备、真机或模拟器,如 iPhone Simulator、Android Emulator、Galaxy S4 等。
- automationName:使用的自动化引擎,如 appium(默认)或 Selendroid(兼容 Android API 17 以下)。
- platformName:使用的移动平台,如 iOS 或 Android。
- platformVersion:指定平台的系统版本,如 Android 平台,版本为 7.0。
- appPackage:被测试 App 的 Package 名,如 com.android.settings 等。
- appActivity:被测试 App 的 Activity 名,如 Calculator、MainActivity、.Settings 等。
- noReset:在会话前重置应用状态。当设置为 True 时,会跳过安装指引;默认为 false。
更多配置说明可参考官方文档:https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md
获取 appPackage 和 appActivity 的方法 appium 在启动 App 时必须要设置 appPackage 和 appActivity 两个参数。最简单的方式是直接询问开发人员~还可以通过以下两种方式得到这两个参数。
a.通过 adb 工具抓取日志进行分析 命令行窗口运行命令:adb logcat > D:/log.txt ,将 adb 抓取的日志写入指定文件中。 在 Android 模拟器设备打开要测试的 App,做一些操作,命令行窗口快捷键 Ctrl+c 结束日志的捕捉。 打开 log.txt 文件,搜索 Displayed 关键字,查找 App 的 Package 和 Activity。此处 Package 是 com.android.calculator2 ,Activity 是 .Calculator 。 b. 通过 appt 查看信息 aapt 即 Android Asset Packaging Tool,在 SDK 的 build-tools 目录下。需要将该目录加入到系统变量 Path 中。该工具既可以查看、创建、更新 zip 格式的文档附件(zip、jar、apk),也可以将资源文件编译成二进制文件。 命令行窗口执行命令:aapt dump badging D:\ChromeDownload\qqlite_4.0.2.1075_537067759.apk 此处 Package 是 com.tencent.qqlite ,Activity 是 com.tencent.mobileqq.activity.SplashActivity 。
2. 控件定位
对 UI 自动化测试来说,关键就是定位元素或控件。
appium 继承了 Selenium 的定位方法,并在其基础上进行了扩展,以适应移动端控件的定位。
appium 扩展的定位方法如下:
- ios_uiautomation:find_element_by_ios_uiautomation()
- ios_predicate:ind_element_by_ios_predicate()
- ios_class_chain:find_element_by_ios_class_chain()
- android_uiautomator:find_element_by_android_uiautomator()
- android_viewtag:find_element_by_android_viewtag()
- android_datamatcher:find_element_by_android_data_matcher()
- accessibility_id:find_element_by_accessibility_id()
- image:find_element_by_image()
- custom:find_element_by_custom()
可以借助 Android SDK 自带的 UI Automator Viewer 工具对 Android 设备式模拟器中的控件进行定位。该工具在 SDK安装目录下的 tools/bin 下,直接双击 uiautomatorviewer.bat 文件启动,或者将该目录加入到系统变量 Path 中,命令行执行命令 uiautomatorviewer 启动。
UI Automator Viewer 界面,单击 Device Screenshot 按钮之后,进入 Android 设备或模拟器的当前界面。UI Automator Viewer 捕获到该界面,并显示相应信息。
(1)id 定位
id 定位是使用控件的 resource-id 进行定位的。当 Android 设备或模拟器的 Android 系统 API 版本低于 18 (也就是 Android 系统低于4.3)时,UI Automator Viewer 工具无法获取对应的 resource-id。 resource-id 使用方法:
driver.find_element_by_id("com.android.calculator2:id/op_mul")
(2)Class Name 定位
Class Name 定位是使用控件的 class 属性进行定位的。 计算器界面上的所有按钮的 class 属性都为 android.widget.Button。属性的重复性很高。我们可以先定位一组控件,再进一步筛选。除非其他方法都无法定位到控件,否则不考虑使用这种方法,效率低。
from appium import webdriver
desired_caps = {
'deviceName': 'Android Emulator',
'automationName': 'appium',
'platformName': 'Android',
'platformVersion': '7.0',
'appPackage': 'com.android.calculator2',
'appActivity': '.Calculator',
'noReset': True,
}
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_capabilities=desired_caps)
button_list = driver.find_elements_by_class_name("android.widget.Button")
print(len(button_list))
for button in button_list:
print(button.text)
button_list[8].click()
button_list[16].click()
button_list[2].click()
button_list[11].click()
(3)Xpath 定位
根据之前 WebDriver 上的 XPath 使用经验,查找标签名,但在这里,正确的方式是获取控件的 class 属性,来代替标签名,例如,定位数字7,driver.find_element_by_xpath("//android.widget.LinearLayout/android.widget.Button") 如果出现 class 属性相同的情况,则用控件的属性进一步区分。XPath 在 appium 上的用法很强大,不过,需要写更长的定位语法,App 上,class 属性本身就很长。
driver.find_element_by_xpath("//android.widget.Button[contains(@test,'7')]")
driver.find_element_by_xpath("//android.widget.Button[contains(@content-desc,'multiply')]")
(4)Accessibility id 定位
该方法属于 appium 扩展的定位方法,它采用一个字符串表示附加到给定元素的可访问 id 或标签,例如,iOS 中的可访问标识符,或 Android 中的内容描述等。它的核心是找到控件的 contentDescription 属性。
在 Android 中,Accessibility id 定位取控件的 content-desc 属性。在 iOS 中, label 和 name 属性都对应 Accessibility id 定位方式。 使用如下:
driver.find_element_by_accessibility_id("multiply")
(5)Android uiautomator 定位
该方法属于 appium 的扩展定位方法,并且只支持 Android 平台。
appium 在对 Android 原生应用进行自动化测试时,底层使用的是 UIAutomator2 测试库, 在 UIAutomator2 测试库中通过 UiSelector 对象去查找控件。
Android uiautomator 可以直接使用 UIAutomator2 的定位方法来查找控件,定位如图:(Node Detail 下的所有属性),也就是说,一个元素的任意属性都可以通过 Android uiautomator 方法来进行定位。 使用方法如下:
driver.find_element_by_android_uiautomator('new UiSelector().text("DEL")')
driver.find_element_by_android_uiautomator('new UiSelector().text("8")')
driver.find_element_by_android_uiautomator('new UiSelector().text("5")')
driver.find_element_by_android_uiautomator('new UiSelector().description("equals")')
driver.find_element_by_android_uiautomator('new UiSelector().description("plus")')
driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.android.calculator2:id/digit_1")')
driver.find_element_by_android_uiautomator('new UiSelector().className("android.widget.Button")')
(6)其他定位
在 appium 的 Native App 中没有使用的方法如下,这些方法在 Web App 下,或者 Hybrid App 的 WebView 组件下仍然可以使用,用法与 WebDriver 一致。
driver.find_element_by_name()
driver.find_element_by_tag_name()
driver.find_element_by_link_text()
driver.find_element_by_partial_link_text()
driver.find_element_by_css_selector()
在 appium 中没有详细介绍的定位方法如下,前三个定位方法是针对 iOS 平台控件的,后4个方法目前不常用。该书侧重介绍 Android 平台的控件定位。
WebView 组件 WebView 可直译为网页视图。Android 内置 WebKit 内核的高性能浏览器,而 WebView 是在此基础上进行封装后的一个组件。我们可以使用 WebView 在 App 中嵌套一个 Web 页面。
3. appium 的常用 API
移动端的交互比 Web 端更加丰富,如锁定/解锁、滑屏、摇晃、重启等,appium 在 WebDriver API 的基础上扩展了这些操作。appium 官方文档:http://appium.io/docs/en/about-appium/intro/
(1)应用操作
介绍对应用的操作,如应用的安装、卸载、关闭以及启动等。
a. 安装应用:install_app(),安装应用到设备上,需要指定 apk 包的路径。driver.install_app("D:\\ChromeDownload\\qqlite_4.0.2.1075_537067759.apk")
b. 卸载应用:remove_app(),从设备中卸载某个指定的应用,需要指定 App 包名。driver.remove_app("com.example.android.apis")
c. 关闭应用:close_app(),关闭打开的应用,默认关闭当前打开的应用。driver.close_app()
d. 启动应用:launch_app(),appium 在运行测试已经指定 App 的相关信息并启动。这个启动 App 的方法配合 close_app() 方法使用。driver.close_app() driver.launch_app()
e. 检查应用是否已经安装:is_app_installed(),检查应用是否已经安装,需要指定应用的包名。返回结果为 True 或 False。result = driver.is_app_installed("com.example.android.apis") print(result)
f. 将应用置于后台:background_app(),将当前应用置于后台,需要指定将应用置于后台的时间,默认时间单位为秒。driver.background_app(10) #将应用置于后台10s
g. 应用重置:reset(),类似于清除应用缓存。driver.reset()
(2)上下文操作
在软件工程中,上下文是一种属性的有序序列,它们为驻留在环境内的对象定义环境。在计算机技术中,对进程而言,上下文就是进程执行时的环境,具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件和内存信息等。
这里的上下文主要针对混合应用,它们与 App 原生控件和内嵌 Web 页面上的元素的定位方式不同,所以需要确认当前操作的元素或控件的上下文,以便使用不同的定位策略。
a. 可用上下文:contexts,获取当前所有可用上下文。driver.contexts
b. 当前上下文:current_context,获取当前可用上下文。driver.current_context
c. 切换上下文:switch_to.context(),切换到指定上下文。driver.switch_to.context('NATIVE_APP')
(3)键盘操作
a. 输入字符串:send_keys(),模拟输入字符串。driver.find_element_by_name("Name").send_keys("jack")
b. 模拟按键:keyevent(),发送一个键码的操作,即一次只能输入一个字符。将光标定位到某输入框,自动调出系统键盘,执行 keyevent()方法。另外,还需要知道每个字符的编号。
driver.keyevent(1)
driver.keyevent(15)
driver.keyevent(13)
driver.keyevent(36)
driver.keyevent(33)
(4)触摸操作
TouchAction 类提供了一组实现触摸动作的方法。
a. 单击控件:tap(),对一个控件或坐标点(x,y)执行单击操作。tap(self, element=None, x=None, y=None, count=1) ,count 参数,用于设置控件的单击次数,默认为 1 次。代码中 release() 结束的行动取消屏幕上的指针。perfrom() 方法通过将命令发送到要操作的服务器来执行操作。
from appium import webdriver
from appium.webdriver.common.touch_action import TouchAction
desired_caps = {
'deviceName': 'Android Emulator',
'automationName': 'appium',
'platformName': 'Android',
'platformVersion': '7.0',
'appPackage': 'com.android.calculator2',
'appActivity': '.Calculator',
'noReset': True,
}
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_capabilities=desired_caps)
el = driver.find_element_by_android_uiautomator('text("Name")')
TouchAction(driver).tap(el).release().perform()
TouchAction(driver).tap(x=0, y=300).perform()
TouchAction(driver).tap(el, count=2).perform()
b. 长按控件:long_press(),长按一个控件或坐标点(x,y)。long_press(self, el=None, x=None, y=None, duration=1000) , duration 参数,用来控制按压的时间。duration 以毫秒为单位。
el = driver.find_element_by_android_uiautomator('text("Name")')
TouchAction(driver).long_press(el).perform()
TouchAction(driver).long_press(x=0, y=300).perform()
TouchAction(driver).long_press(el, duration=2000).perform()
c. 移动:move_to(),将光标移动到新的元素或坐标点(x,y)。move_to(self, el=None, x=None, y=None)
el = driver.find_element_by_android_uiautomator('text("Name")')
TouchAction(driver).move_to(el).perform()
TouchAction(driver).move_to(x=0, y=300).perform()
d. 暂停:wait(),暂停脚本的执行。wait(self, ms=0) ,ms 参数表示时间,单位为毫秒。
TouchAction(driver).wait(1000).perform()
TouchAction(driver).wait(5000).perform()
appium 不仅可以执行一个动作,还可以同时执行多个动作,形成动作链,以模拟多指动作。appium 是通过构建一个 MultiAction 对象来实现执行多个动作的,该对象包含多个单独的 TouchAction 对象,每个“手指”对应一个。
from appium.webdriver.common.touch_action import TouchAction
from appium.webdriver.common.multi_action import MultiAction
...
els = driver.find_elements_by_class_name('listView')
a1 = TouchAction()
a1.press(els[0]).move_to(x=10, y=0).move_to(x=10, y=-75).move_to(x=10, y=-600).release()
a2 = TouchAction()
a2.press(els[1]).move_to(x=10, y=10).move_to(x=10, y=-300).move_to(x=10, y=-600).release()
ma = MultiAction(driver, els[0])
ma.add(a1, a2)
ma.perform()
(5)特有操作
a. 熄屏:lock(),单击电源键熄灭屏幕。lock(self, seconds=None) ,seconds 参数表示时间,单位为秒,默认为 None。 driver.lock(seconds=3) # 熄屏 3s
b. 获取当前 package:current_package,获取当前 App 的包名,仅支持 Android。 package = driver.current_package print(package)
c. 获取当前 activity:current_activity,获取当前 App 的 activity,仅支持 Android。 activity = driver.current_activity print(activity)
d. 收起虚拟键盘:hide_keyboard,输入操作完成后,可以使用该方法收起虚拟键盘。 driver.hide_keyboard()
e. 获取屏幕宽高:get_window_size(),当需要通过坐标(x,y)对屏幕进行操作时,就需要获取屏幕的宽高。
windows = driver.get_window_size()
print(windows["width"])
print(windows["height"])
f. 拉取文件:pull_file(),从真机或模拟器中拉取文件。pull_file(self, path) ,path 参数指定文件的路径。 driver.pull_file('Library/AddressBook.sqlitedb')
g. 推送文件:push_file(),推送文件到设备中。push_file(self, path, base64data) path 参数指定 PC 中的文件路径,base64data 参数指定写入文件的内容的编码为 base64。
data = "some data for the file"
path = "/data/local/tmp/file.txt"
driver.push_file(path, data.encode('base64'))
4. appium Desktop
appium Desktop 除作为 Server 角色外,还提供元素定位和脚本录制功能。
(1)准备工作
启动 Android 模拟器或真机。然后启动 appium Desktop。点击运行界面右上角的放大镜按钮,进入到创建 Session 界面。 创建 Session 界面,单击 Desired Capability 选项,左侧支持选项的填写,右侧支持 JSON 格式的填写。 填写完整后,单击 “Save As”,保存这一次 Desired Capability 的配置。下一次使用只需在 “Saved Capability Sets” 标签下选择即可。 保存好后,点击右下角的 Start Session 按钮,等待几秒,就可打开获取的 App 界面。
(2)控件定位
appium Desktop 可查看控件定位信息。 上一小节获取的 App 界面,整个界面分为三个部分:
- 左侧显示App 的当前界面,可以在上面单击需要定位的控件,使其处于选中状态。
- 中间显示 App 的布局结构,即根据左侧选择的控件,显示当前控件的布局结构。
- 右侧显示选中控件的定位建议(id、xpath),同时提供 Tap、Send Keys、Clear 操作,而且还列出了控件的所有属性。
(3)脚本录制
录制脚本操作步骤:
- 单击“Top By Coordinates”按钮,表示用坐标的方式来定位元素。
- 单击“Start Recording”按钮,表示处于录制状态。
- 在左侧的 App 界面上对控件进行单击操作,即可自动生成脚本。
appium Desktop 同时支持不同语言或框架的脚本生成,这里选择 Python,语言选择框右侧提供了三个按钮:Show/Hide Boilerplate Code、Copy Code to Clipboard、Clear Actions。
- Show/Hide Boilerplate Code:显示/隐藏样板代码,单击会显示完整的脚本代码。
- Copy Code to Clipboard:复制代码到剪切板。
- Clear Actions:清除录制的脚本。
单击 Show/Hide Boilerplate Code 按钮,再单击 Copy Code to Clipboard 按钮,复制代码,将代码粘贴到编辑器中运行。
通过 appium Desktop 或 App 提供的功能降低 appium 的学习门槛,它可以快速辅助生成定位脚本和测试脚本。该功能只能作为学习的工具,不能作为代码生成器。appium Desktop 生成的控件定位是坐标形式的,不可靠,它会受屏幕大小和分辨率的影响。
|