在上篇 Appium+iOS+Mac 环境搭建的基础上, 复制翻译了 Appium Python Client https://github.com/appium/python-client 以作为后续的使用手册.
Appium Python 客户端完全符合 Selenium 3.0 规范草案, 其中一些帮助者可以更轻松地在 Python 中进行移动测试. 大多数用法仍然与 Selenium 2(webDriver) 一样, 并且随着官方 Selenium Python bindings https://pypi.org/project/selenium/ 开始实现将在下面使用实现的新规范, 因此可以编写可用于两种绑定的测试代码.
要立即使用新功能, 并使用函数的超集, 而不是在测试代码中包含 Selenium webdriver 模块, 请改用 Appium 中的模块.
from appium import webdriver
从那里你的大部分测试代码将无需任何改变.
作为以下代码示例的基础, 以下设置 UnitTest https://docs.python.org/2/library/unittest.html 环境:
- # 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.Windows(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"})
来源: http://www.jianshu.com/p/3434f358aee0