在上篇Appium+iOS+Mac 环境搭建的基础上,复制翻译了Appium Python Client以作为后续的使用手册。
Appium Python客户端完全符合Selenium 3.0规范草案,其中一些帮助者可以更轻松地在Python中进行移动测试。 大多数用法仍然与Selenium 2(WebDriver)一样,并且随着官方Selenium Python bindings开始实现将在下面使用实现的新规范,因此可以编写可用于两种绑定的测试代码。
要立即使用新功能,并使用函数的超集,而不是在测试代码中包含Selenium webdriver模块,请改用Appium中的模块。
from appium import webdriver
从那里你的大部分测试代码将无需任何改变。
作为以下代码示例的基础,以下设置UnitTest环境:
# Android environment
import unittest
from appium import webdriver
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '8.1'
desired_caps['automationName'] = 'uiautomator2'
desired_caps['deviceName'] = 'Android Emulator'
desired_caps['app'] = PATH('../../../apps/selendroid-test-app.apk')
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
import unittest
from appium import webdriver
desired_caps = {}
desired_caps['platformName'] = 'iOS'
desired_caps['platformVersion'] = '11.4'
desired_caps['automationName'] = 'xcuitest'
desired_caps['deviceName'] = 'iPhone Simulator'
desired_caps['app'] = PATH('../../apps/UICatalog.app.zip')
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
更改或添加功能
- 在原生和Webview之间切换
对于移动测试,之前用于在窗口之间切换的Selenium方法被用于在本机应用程序和webview上下文之间切换。 为此明确的方法已添加到Selenium 3规范中,因此将使用这些“上下文”方法。
要获取当前上下文,而不是调用driver.current_window_handle
current = driver.current_context
使用driver.window_handles不会检索可用的上下文
driver.contexts
最后,要切换到新的上下文而不是driver.switch_to.window(name),请使用可比较的上下文方法
context_name = "WEBVIEW_1"
driver.switch_to.context(context_name)
通过iOS UIAutomation搜索查找元素
这允许使用UIAutomation库使用递归元素搜索找到iOS应用程序中的元素。 仍然支持UIAutomation的iOS设备支持此方法,即早于XCUITEST的版本.
添加方法driver.find_element_by_ios_uiautomation和driver.find_elements_by_ios_uiautomation。
el = self.driver.find_element_by_ios_uiautomation('.elements()[0]')
self.assertEqual('UICatalog', el.get_attribute('name'))
els = self.driver.find_elements_by_ios_uiautomation('.elements()')
self.assertIsInstance(els, list)
Finding elements by Android UIAutomator search
这允许使用UIAutomator库使用递归元素搜索找到Android应用程序中的元素。 添加方法driver.find_element_by_android_uiautomator和driver.find_elements_by_android_uiautomator。
el = self.driver.find_element_by_android_uiautomator('new UiSelector().description("Animation")')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_android_uiautomator('new UiSelector().clickable(true)')
self.assertIsInstance(els, list)
通过Android视图标签搜索查找元素
此方法允许使用View#标记查找元素。 此方法适用于Espresso驱动程序。
添加方法driver.find_element_by_android_viewtag和driver.find_elements_by_android_viewtag。
el = self.driver.find_element_by_android_viewtag('a tag name')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_android_viewtag('a tag name')
self.assertIsInstance(els, list)
通过iOS谓词查找元素
此方法允许使用iOS谓词查找元素。 这些方法采用谓词格式的字符串,包括元素类型和字段值。
添加方法driver.find_element_by_ios_predicate和find_elements_by_ios_predicate。
el = self.driver.find_element_by_ios_predicate('wdName == "Buttons"')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_ios_predicate('wdValue == "SearchBar" AND isWDDivisible == 1')
self.assertIsInstance(els, list)
Finding elements by iOS class chain
此方法仅适用于XCUITest驱动程序
此方法允许使用iOS类链查找元素。 这些方法采用类链的格式的字符串,包括元素类型。
添加方法driver.find_element_by_ios_class_chain和find_elements_by_ios_class_chain。
el = self.driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/XCUIElementTypeButton[3]')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/XCUIElementTypeButton')
self.assertIsInstance(els, list)
Finding elements by Accessibility ID
允许使用“辅助功能ID”找到元素。 这些方法采用表示附加到给定元素的可访问性标识或标签的字符串,例如,对于iOS,可访问性标识符,对于Android,采用内容描述。 添加方法driver.find_element_by_accessibility_id和find_elements_by_accessibility_id。
el = self.driver.find_element_by_accessibility_id('Animation')
self.assertIsNotNone(el)
els = self.driver.find_elements_by_accessibility_id('Animation')
self.assertIsInstance(els, list)
触摸动作
为了适应移动触摸动作以及涉及多个指针的触摸动作,Selenium 3.0草案指定了“触摸手势”和“多动作”,它们构建在触摸动作之上。
move_to:请注意,如果没有元素,请使用关键字参数
API围绕TouchAction对象构建,TouchAction对象是要按顺序执行的一个或多个动作的链。 行动是:
- perform
perform方法将链发送到服务器以便生效。 它还清空了动作链,因此可以重用该对象。 它将在所有单一动作链的末尾,但在编写多动作链时未被使用。
- tap
tap方法独立,无法与其他方法链接。 如果您需要类似水龙头的动作来启动更长的链条,请使用按下。
它可以是带有可选x-y偏移的元素,也可以是tap的绝对x-y坐标,以及可选的计数。
el = self.driver.find_element_by_accessibility_id('Animation')
action = TouchAction(self.driver)
action.tap(el).perform()
el = self.driver.find_element_by_accessibility_id('Bouncing Balls')
self.assertIsNotNone(el)
- press
- long_press
- release
- move_to
- wait
- cancel
多点触控动作
除了在单个手势内执行的动作链之外,还可以同时执行多个链,以模拟多手指动作。 这是通过构建一个MultiAction对象来完成的,该对象包含许多单独的TouchAction对象,每个“手指”对应一个。
给定两个彼此相邻的列表,我们可以独立滚动它们,但同时:
els = self.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(self.driver, els[0])
ma.add(a1, a2)
ma.perform();
特定于Appium的触摸动作
移动测试人员需要做很少的操作,使用Touch和Multi-touch Action API进行构建可能相对复杂。 为此,我们在Appium客户端中提供了一些便捷方法。
- driver.tap
在WebDriver对象上,此方法允许使用多个手指轻敲,只需传入x-y坐标数组即可。
el = self.driver.find_element_by_name('Touch Paint')
action.tap(el).perform()
# set up array of two coordinates
positions = []
positions.append((100, 200))
positions.append((100, 400))
self.driver.tap(positions)
- driver.swipe
从一个点滑动到另一个点。
- driver.zoom
放大元素,进行捏合操作。
- driver.pinch
缩小元素,进行操作捏合。
应用管理方法
在测试中,有时您希望管理正在运行的应用程序,例如安装或删除应用程序等。
背景应用程序
方法driver.background_app将正在运行的应用程序发送到后台指定的时间(以秒为单位)。 在此之后,应用程序将返回到前台。
driver.background_app(1)
sleep(2)
el = driver.find_element_by_name('Animation')
assertIsNotNone(el)
检查是否安装了应用程序
要检查设备上当前是否安装了应用程序,请使用device.is_app_installed方法。 此方法获取应用程序的bundle id并返回True或False。
assertFalse(self.driver.is_app_installed('sdfsdf'))
assertTrue(self.driver.is_app_installed('com.example.android.apis'))
安装应用程序
要在设备上安装卸载的应用程序,请使用device.install_app,发送应用程序文件或存档的路径。
assertFalse(driver.is_app_installed('io.selendroid.testapp'))
driver.install_app('/Users/isaac/code/python-client/test/apps/selendroid-test-app.apk')
assertTrue(driver.is_app_installed('io.selendroid.testapp'))
删除应用程序
如果需要从设备中删除应用程序,请使用device.remove_app,传入应用程序ID。
assertTrue(driver.is_app_installed('com.example.android.apis'))
driver.remove_app('com.example.android.apis')
assertFalse(driver.is_app_installed('com.example.android.apis'))
关闭并启动应用程序
要启动所需功能中指定的应用程序,请调用driver.launch_app。 关闭该应用程序由driver.close_app启动
assertIsNotNone(el)
driver.close_app();
try:
driver.find_element_by_name('Animation')
except Exception as e:
pass # should not exist
driver.launch_app()
el = driver.find_element_by_name('Animation')
assertIsNotNone(el)
重置应用程序
要重置正在运行的应用程序,请使用driver.reset。
el = driver.find_element_by_name('App')
el.click()
driver.reset()
sleep(5)
el = driver.find_element_by_name('App')
assertIsNotNone(el)
其他方法
开始任意活动
driver.start_activity方法打开设备上的任意活动。 如果活动不是被测试应用程序的一部分,它还将启动活动的应用程序。
driver.start_activity('com.foo.app', '.MyActivity')
检索应用程序字符串
属性方法driver.app_strings从设备上的应用程序返回应用程序字符串。
strings = driver.app_strings
将关键事件发送到Android设备
driver.keyevent方法将密钥代码发送到设备。 密钥代码可以在这里找到。 仅适用于Android。
# sending 'Home' key event
driver.press_keycode(3)
在iOS中隐藏键盘
要在iOS中隐藏键盘,请使用driver.hide_keyboard。 如果发送了密钥名称,则将按下具有该名称的键盘密钥。 如果没有传入参数,则通过点击文本字段外的屏幕来隐藏键盘,从而从中移除焦点。
# get focus on text field, so keyboard comes up
el = driver.find_element_by_class_name('android.widget.TextView')
el.set_value('Testing')
el = driver.find_element_by_class_name('keyboard')
assertTrue(el.is_displayed())
driver.hide_keyboard('Done')
assertFalse(el.is_displayed())
# get focus on text field, so keyboard comes up
el = driver.find_element_by_class_name('android.widget.TextView')
el.set_value('Testing')
el = driver.find_element_by__name('keyboard')
assertTrue(el.is_displayed())
driver.hide_keyboard()
assertFalse(el.is_displayed())
检索当前正在运行的包和活动
属性方法driver.current_package返回设备上运行的当前包的名称。
package = driver.current_package
assertEquals('com.example.android.apis', package)
属性方法driver.current_activity返回设备上运行的当前活动的名称。
activity = driver.current_activity
assertEquals('.ApiDemos', activity)
直接在元素上设置值
有时需要直接在设备上设置元素的值。 为此,调用方法driver.set_value或element.set_value。
el = driver.find_element_by_class_name('android.widget.EditText')
driver.set_value(el, 'Testing')
text = el.get_attribute('text')
assertEqual('Testing', text)
el.set_value('More testing')
text = el.get_attribute('text')
assertEqual('More testing', text)
从设备中检索文件
要从设备检索文件的内容,请使用driver.pull_file,它返回在Base64中编码的指定文件的内容。
# pulling the strings file for our application
data = driver.pull_file('data/local/tmp/strings.json')
strings = json.loads(data.decode('base64', 'strict'))
assertEqual('You can\'t wipe my data, you are a monkey!', strings[u'monkey_wipe_data'])
将文件放在设备上
要将文件放在特定位置的设备上,请使用driver.push_file方法,该方法将路径和编码为Base64的数据写入文件。
path = 'data/local/tmp/test_push_file.txt'
data = 'This is the contents of the file to push to the device.'
driver.push_file(path, data.encode('base64'))
data_ret = driver.pull_file('data/local/tmp/test_push_file.txt').decode('base64')
self.assertEqual(data, data_ret)
结束测试覆盖率
Android模拟器中有一些功能可用于检测某些活动。 有关此信息,请参阅Appium文档。 要结束此覆盖并检索数据,请使用driver.end_test_coverage,传入正在进行工具化的intent,以及设备上的coverage.ec文件的路径。
coverage_ec_file = driver.end_test_coverage(intent='android.intent.action.MAIN', path='')
锁定设备
要在iOS上锁定设备一段时间,请使用driver.lock。 参数是解锁前等待的秒数。
摇晃设备
要摇动设备,请使用driver.shake。
Appium设置
设置是appium引入的新概念。 它们目前不是Mobile JSON Wire Protocol或Webdriver规范的一部分。
设置是指定appium服务器行为的一种方法。
设置是:
可变,它们可以在会话期间更改仅在会话期间相关它们被应用。 它们会针对每个新会话重置。 控制appium服务器在测试自动化期间的行为方式。 它们不适用于控制受测试的应用程序或设备。
有关更多信息,请参阅文档。
要获得设置:
settings = driver.get_settings()
要设置:
driver.update_settings({"some setting": "the value"})