https://github.com/appium/python-client
Appium Python Client
一个添加了 Selenium 3.0 draft 和 Mobile JSON Wire Protocol Specification draft 功能到 Python 语言绑定的拓展库, 和移动测试框架 Appium 一块使用.
获取 Appium Python client
真理有三种方法安装并使用 Appium Python client.
-
以 'Appium-Python-Client' 从 PyPi 安装
pip install Appium-Python-Client
你可以从这里查看历史记录 here
-
Install from source, via PyPi. From 'Appium-Python-Client', download and unarchive the source tarball (Appium-Python-Client-X.X.tar.gz).
tar -xvf Appium-Python-Client-X.X.tar.gz cd Appium-Python-Client-X.X python setup.py install
-
由 GitHub 从源(source)安装
git clone git@github.com:appium/python-client.git cd python-client python setup.py install
Development
- 格式指南 : https://www.python.org/dev/peps/pep-0008/
autopep8
帮助自动格式化代码$ python -m autopep8 -r --global-config .config-pep8 -i .
isort
帮助自动选定引入$ python -m isort -rc .
- You can customise
CHANGELOG.rst
with commit messages following .gitchangelog.rc- It generates readable changelog
- Setup
pip install --user pipenv
python -m pipenv lock --clear
- If you experience the below error, then refer pypa/pipenv#187 to solve it.
Locking Failed! unknown locale: UTF-8
- If you experience the below error, then refer pypa/pipenv#187 to solve it.
python -m pipenv install --dev --system
pre-commit install
运行测试集
你可以在本地通过 tox 在 CI 上运行所有的测试
$ tox
你也可以像接下来那样运行特定的测试
Unit
$ py.test test/unit
运行 pytest-xdist
$ py.test -n 2 test/unit
函数
$ py.test test/functional/ios/find_by_ios_class_chain_tests.py
适用于iOS
- 创建命名为 'iPhone 6s - 8100' 和'iPhone 6s - 8101' 的模拟器
- 通过 pip 安装测试库
$ pip install pytest pytest-xdist
- 运行测试集
$ py.test -n 2 test/functional/ios/find_by_ios_class_chain_tests.py
发布
按照以下步骤。
$ pip install twine $ pip install git+git://github.com/vaab/gitchangelog.git # Getting via GitHub repository is necessary for Python 3.7 # Type the new version number and 'yes' if you can publish it # You can test the command with DRY_RUN $ DRY_RUN=1 ./release.sh $ ./release.sh # release
用法
Appium Python Client 完全符合 Selenium 3.0 规范草案, 其中一些工具包使得在Python中进行移动测试更简单. 大多数用法仍然与 Selenium 2 (WebDriver) 一样, and as the official Selenium Python bindings begins to implement the new specification that implementation will be used underneath, so test code can be written that is utilizable with both bindings.
要立即使用新功能,并使用函数的超集,不再在你的测试代码中引入 Selenium webdriver
模块,而是从 Appium 引用这个(Selenium webdriver
)模块来代替
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)
# iOS environment 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)
更改或添加功能
改变的方法是......
直接连接网址
如果你的 Selenium/Appium 服务器使用以下键名(keys)修饰新的会话功能响应:
directConnectProtocol
directConnectHost
directConnectPort
directConnectPath
然后 python client 客户端将其端点切换到由这些键的值指定的端点
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, direct_connection=True)
在 'Native(原生)' 和 'Webview' 之间切换
对于移动测试,之前用于在窗口之间切换的Selenium方法被用于在本机应用程序(native applications)和webview上下文(webview contexts)之间切换。为此明确的方法已添加到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 search 查找元素
This allows elements in iOS applications to be found using recursive element search using the UIAutomation library. This method is supported on iOS devices that still support UIAutomation, that is, versions which predate XCUITEST.
Adds the methods driver.find_element_by_ios_uiautomation
and 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)
通过 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 viewtag(视图标签) search 查找元素
这种方法允许使用 View#tags 发现元素. 此方法适用于 Espresso Driver.
添加了方法 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 predicates 查找元素
This method allows finding elements using iOS predicates. The methods take a string in the format of a predicate, including element type and the value of fields.
Adds the methods driver.find_element_by_ios_predicate
and 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)
通过 iOS class chain 查找元素
This method is only for XCUITest driver
This method allows finding elements using iOS class chain. The methods take a string in the format of a class chain, including element type.
Adds the methods driver.find_element_by_ios_class_chain
and 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)
通过 Accessibility ID 查找元素
允许使用 "Accessibility(可访问的) 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 草案指定了 "touch gestures" (触摸手势)和 "multi actions"(多点触控), 它们构建在触摸动作之上。
move_to: 请注意,如果没有元素,请使用关键字参数
API 围绕 TouchAction
对象构建, 这个对象是按顺序执行的一个或多个动作的链 ,动作是
perform
该perform
方法将链发送到服务器以便生效。它还清空了动作链,因此可以重用该对象。它将在所有单一动作链的末尾,但在编写多动作链时不被使用。
tap
该tap
方法独立,无法与其他方法链接。如果你需要一个类似tap
的动作来启动更长的链,请使用press
。
它可以是带有可选xy偏移的元素,也可以是tap的绝对xy坐标,以及可选的计数。
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对象上,此方法允许使用多个手指轻敲(tap),只需传入一个xy坐标数组即可。
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
从一个点滑动到另一个点。
应用管理方法
在测试中,有时您希望管理正在运行的应用程序,例如安装或删除应用程序等。
背景应用程序
The method driver.background_app
sends the running application to the background for the specified amount of time, in seconds. After that time, the application is brought back to the foreground.
该方法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 方法
el = driver.find_element_by_name('Animation') 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')
检索应用程序字符串
property方法driver.app_strings
从设备上的应用程序返回应用程序字符串。
strings = driver.app_strings
将按键事件发送到Android设备
该 driver.keyevent
方法将按钮代码发送到设备. 键码可以在 here 这里找到. 仅适用于Android.
# sending 'Home' key event driver.press_keycode(3)
在iOS中隐藏键盘
To hide the keyboard from view in iOS, use driver.hide_keyboard
. If a key name is sent, the keyboard key with that name will be pressed. If no arguments are passed in, the keyboard will be hidden by tapping on the screen outside the text field, thus removing focus from it.
# 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())
检索当前正在运行的包和活动
property方法driver.current_package
返回设备上运行的当前包的名称。
package = driver.current_package assertEquals('com.example.android.apis', package)
property方法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 docs. 要结束此覆盖并检索数据,请使用 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"})