上一章节已经介绍了Appium的环境搭建,其实只要掌握了Appium的工作原理,前期的准备工作和安装过程是比较简单的。那么当我们搭建好Appium环境后接下来做些什么呢?通常思路是开始appium的第一个helloworld的例子,但笔者认为现在开始写代码并不能算好,这就犹如在武侠小说里但凡武功达到大臻境界的绝世高手都不会在意一招半式的招式,而内功修炼尤为重要。在网上搜索了一下,并没有一个大而全的api文档集合,所以笔者决定先对Python语言可以使用到的Appium API一一进行介绍。
常用函数
一、获得信息类API
(1)获取当前页面的activity名,比如: (.ui.login.ViewPage)
1 def current_activity(self):
2 """Retrieves the current activity on the device.
3 """
4 return self.execute(Command.GET_CURRENT_ACTIVITY)['value']
current_activity()
比如我们需要实现这个登录的功能时,主要思路为如果当前界面为登录页面时,就进行登录行为,否则就跳转到登录页面。其伪代码为:
1 if driver.current_activity == ".ui.login.ViewPage":
2 // To login_action
3 else:
4 // Trun to loginPage
(2)获取当前页面的树形结构源代码,与uiautomatorviewer截屏所展示出来的结构是相同的
1 def page_source(self):
2 """
3 获得当前页面的源码。
4 :Usage:
5 driver.page_source
6 """
7 return self.execute(Command.GET_PAGE_SOURCE)['value']
page_source()
例如当我们完成登录流程之后,要判断登录是否成功,则可以判断登录后的页面有没有出现特定的内容(比如:运动圈、发现、运动、商城、我的),其伪代码实现如下:
driver \
.page_source.find(u"运动圈") != -1 and
.page_source.find(u"发现") != -1 and
.page_source.find(u"运动") != -1 and
.page_source.find(u"商城") != -1 and
.page_source.find(u"我的") != -1 and
page_source()的返回数据类型为str。python中,str的find(context)方法,如果str存在context返回值为context在str的index,如果不存在,则返回值为-1。因此只需要判断以上代码块返回的布尔值是True or False,就可以判断是否登录成功。
(3)获取到当前窗口的所有context的名称
1 def contexts(self):
2 """
3 Returns the contexts within the current session.
4 :Usage:
5 driver.contexts
6 """
7 return self.execute(Command.CONTEXTS)['value']
contexts()
在native和html5混合页面测试时,需要在native层和H5层切换,所以首先需要得到context层级的名称
print driver.contexts
>>> ['NATIVE_APP', 'WEBVIEW_com.codoon.gps']
由此可见,我们知道App的H5层名称为"WEBVIEW_com.codoon.gps"后,使用driver.switch_to.context("WEBVIEW_com.codoon.gps")就可以实现NATIVE和H5层的切换了。
二、获取控件类API
(1)通过元素id查找当前页面的一个目标元素
1 def find_element_by_id(self, id_):
2 """ Finds an element by id.
3 :Args:
4 - id\_ - The id of the element to be found.
5 :Usage:
6 driver.find_element_by_id('foo')
7 ******************************
8 Finds element within this element's children by ID.
9 :Args:
10 - id_ - ID of child element to locate.
11 """
12 return self.find_element(by=By.ID, value=id_)
find_element_by_id()
通过源码注释可以得到find_element_by_id这一类的api主要有两个使用途径:
driver.find_element_by_id("com.codoon.gps:id/tv_login") // from webdriver.py
在driver下通过id查找一个元素,此用法通常适用于当前界面的driver有且仅有一个唯一的id元素标示,通过调用find_element_by_id可以准确到找到目标元素;另一种使用途径主要如下:
driver_element = driver.find_element_by_xpath("//android.widget.ListView/android.widget.LinearLayout")
// from webdriverelement.py
driver_element.find_element_by_id("com.codoon.gps:id/activity_active_state")
在driver.find_element_by_xpath返回了driverElement类型,调用find_element_by_id在driverElement下的子元素以id匹配目标元素。
上图为uiautomatorviewer对id,name,class的图示说明。特别说明:若id、name、xpath等在当前driver或者driverElement查找的目标元素不是唯一元素,此时调用find_element_by_id(name\xpath)时,会返回查找匹配到的第一个元素。
(2)通过元素id查找当前页面的多个目标元素
1 def find_elements_by_id(self, id_):
2 """
3 Finds multiple elements by id.
4
5 :Args:
6 - id\_ - The id of the elements to be found.
7
8 :Usage:
9 driver.find_elements_by_id('foo')
10 """
11 return self.find_elements(by=By.ID, value=id_)
find_elements_by_id()
在driver下通过id查找多个目标元素,其返回值类型为list。此用法通常适用于当前driver下查询listView、LinearLayout、 RelativeLayout等有相同布局结构的Item;同样除了driver之外,在driverElement下页可以跳用find_elements_by_id来匹配listView、LinearLayout、 RelativeLayout。
driver.find_elements_by_id("com.codoon.gps:id/tv_name") // from webdriver.py
driver.find_element_by_id("com.codoon.gps:id/webbase_btn_share") \
.find_elements_by_id("com.codoon.gps:id/ll_layout") // from driverelement.py
Tips: 带有find_elements关键字的方法函数的返回类型都是list数据类型,只有driver与driverelement的实例化有find_element(s)等一系列方法,list类型是不能用find_element(s)方法定位数据的。在实际的项目中可能会遇到这样的问题,只有遍历list,取出每一个element实例化对象再进行查找定位元素。
(3) 通过元素name查找当前页面的一个元素
1 def find_element_by_name(self, name):
2 """
3 Finds an element by name.
4
5 :Args:
6 - name: The name of the element to find.
7
8 :Usage:
9 driver.find_element_by_name('foo')
10 """
11 return self.find_element(by=By.NAME, value=name)
find_element_by_name()
使用方式与find_element_by_id相同,只是把匹配条件由id变为name。请参考find_element_by_id的调用方式
driver.find_element_by_name("foo")
driver.find_element_by_id("com.codoon.gps:id/tv_name").find_element_by_name("foo")
>>> return the driverElement(obj)
(4) 通过元素name查找当前页面的多个目标元素
1 def find_elements_by_name(self, name):
2 """
3 Finds elements by name.
4
5 :Args:
6 - name: The name of the elements to find.
7
8 :Usage:
9 driver.find_elements_by_name('foo')
10 """
11 return self.find_elements(by=By.NAME, value=name)
find_elements_by_name()
使用方式与find_elements_by_id相同,只是把匹配条件由id变为name。请参考find_elements_by_id的调用方式,注意其返回数据类型是List,并不是driverElement。
driver.find_elements_by_name("foo")
driver.find_element_by_id("com.codoon.gps:id/tv_name").find_elements_by_name("foo")
### return the List<driverElement>
>>> ['driverElement1', 'driverElement2', 'driverElement3', ....]
(5)通过元素xpath查找当前页面的一个目标元素
1 def find_element_by_xpath(self, xpath):
2 """
3 Finds an element by xpath.
4
5 :Args:
6 - xpath - The xpath locator of the element to find.
7
8 :Usage:
9 driver.find_element_by_xpath('//div/td[1]')
10 """
11 return self.find_element(by=By.XPATH, value=xpath)
find_element_by_xpath()
关于find_element_by_xpath的调用方法与通过id、name略有不同,有关Xpath的相关知识点在本章节暂且不表,后续在项目实践中若有需求再另起专题介绍。
driver.find_element_by_xpath("//android.widget.TextView[contains(@text, '开始')]")
driver.find_element_by_xpath("//android.widget.LinearLayout/android.widget.TextView")
在Appium中,xpath所需相关的lib库并没有完全支持,所以使用方法是以上两种(即仅支持在driver下的xpath匹配)。目前的Appium版本无法支持driverelement下的xpath查找,如
driver.find_element_by_xpath("//android.widget.LinearLayout/android.widget.TextView") \
.find_element_by_xpath("//android.widget.TextView[contains(@text, '开始')]") // This is the Error!
按上面的写法Appium就会报错,原因是“.find_element_by_xpath("//android.widget.TextView[contains(@text, '开始')]")”不能在Element下查找子元素。
(6) 通过元素xpath查找当前页面的多个目标元素
1 def find_elements_by_xpath(self, xpath):
2 """
3 Finds multiple elements by xpath.
4
5 :Args:
6 - xpath - The xpath locator of the elements to be found.
7
8 :Usage:
9 driver.find_elements_by_xpath("//div[contains(@class, 'foo')]")
10 """
11 return self.find_elements(by=By.XPATH, value=xpath)
find_elements_by_xpath()
参照find_element_by_xpath的调用方式,需注意返回类型为List,用法参考find_elements_by_name()的例子
(7) 通过元素class name查找当前页面的的一个元素
1 def find_element_by_class_name(self, name):
2 """
3 Finds an element by class name.
4
5 :Args:
6 - name: The class name of the element to find.
7
8 :Usage:
9 driver.find_element_by_class_name('foo')
10 """
11 return self.find_element(by=By.CLASS_NAME, value=name)
find_element_by_class_name()
在实际项目中,测试app中class name并不能做为界面的唯一标示定位,所以在实际中几乎没有使用class name在driver查看元素,在driverelement下查找子元素用class name才是正确的使用方式。
(8) 通过元素accessibility_id (content-desc)查找当前页面的一个元素
1 def find_element_by_accessibility_id(self, id):
2 """Finds an element by accessibility id.
3
4 :Args:
5 - id - a string corresponding to a recursive element search using the
6 Id/Name that the native Accessibility options utilize
7
8 :Usage:
9 driver.find_element_by_accessibility_id()
10 """
11 return self.find_element(by=By.ACCESSIBILITY_ID, value=id)
find_element_by_accessibility_id()
在uiautomatorviewer中,content-desc内容即为accessibility_id,在selenium库里可以用find_element_by_name()来匹配content-desc的内容;在Appium库里则用find_element_by_accessibility_id()来匹配content-desc的内容。因为Appium继承了Selenium类,所以如果find_element_by_name无法准确定位时,请试试看find_element_by_accessibility_id。
常用的获取控件类API就是以上这些。其他的查找和匹配的api还有find_element_by_link_text、find_elements_by_link_text、find_element_by_tag_name、find_elements_by_tag_name、find_element_by_css_selector、find_elements_by_css_selector等,用法都与上述类似。
三、元素操作类API
我们在实现PC端浏览器Webdriver自动化时,对于网页上的目标的操作主要有:点击(click)、 双击(double_click)、滚动(scroll)、输入(send_keys),而移动端特有的辅助类api:轻击(tap)--支持多点触控,滑动(swipe),放大元素(pinch),缩小元素(zoom)
(1)点击事件
def click(self):
"""Clicks the element."""
self._execute(Command.CLICK_ELEMENT)
click()
1 def tap(self, positions, duration=None):
2 """Taps on an particular place with up to five fingers, holding for a
3 certain time
4
5 :Args:
6 - positions - an array of tuples representing the x/y coordinates of
7 the fingers to tap. Length can be up to five.
8 - duration - (optional) length of time to tap, in ms
9
10 :Usage:
11 driver.tap([(100, 20), (100, 60), (100, 100)], 500)
12 """
13 if len(positions) == 1:
14 action = TouchAction(self)
15 x = positions[0][0]
16 y = positions[0][1]
17 if duration:
18 action.long_press(x=x, y=y, duration=duration).release()
19 else:
20 action.tap(x=x, y=y)
21 action.perform()
22 else:
23 ma = MultiAction(self)
24 for position in positions:
25 x = position[0]
26 y = position[1]
27 action = TouchAction(self)
28 if duration:
29 action.long_press(x=x, y=y, duration=duration).release()
30 else:
31 action.press(x=x, y=y).release()
32 ma.add(action)
33
34 ma.perform()
35 return self
tap()
click和tap都能实现单击的效果。其区别在于click是作用于driverelement的实例化对象,而tap是对屏幕上的坐标位置进行点击。前者对元素的位置变化并不敏感,而后者是针对具体的像素坐标点击,受分辨率和元素位置影响较大。
(2)输入事件
1 def send_keys(self, *value):
2 """Simulates typing into the element.
3
4 :Args:
5 - value - A string for typing, or setting form fields. For setting
6 file inputs, this could be a local file path.
7
8 Use this to send simple key events or to fill out form fields::
9
10 form_textfield = driver.find_element_by_name('username')
11 form_textfield.send_keys("admin")
12
13 This can also be used to set file inputs.
14
15 ::
16
17 file_input = driver.find_element_by_name('profilePic')
18 file_input.send_keys("path/to/profilepic.gif")
19 # Generally it's better to wrap the file path in one of the methods
20 # in os.path to return the actual path to support cross OS testing.
21 # file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
22
23 """
24 # transfer file to another machine only if remote driver is used
25 # the same behaviour as for java binding
26 if self.parent._is_remote:
27 local_file = self.parent.file_detector.is_local_file(*value)
28 if local_file is not None:
29 value = self._upload(local_file)
30
31 self._execute(Command.SEND_KEYS_TO_ELEMENT, {'value': keys_to_typing(value)})
send_keys()
1 def set_text(self, keys=''):
2 """Sends text to the element. Previous text is removed.
3 Android only.
4
5 :Args:
6 - keys - the text to be sent to the element.
7
8 :Usage:
9 element.set_text('some text')
10 """
11 data = {
12 'id': self._id,
13 'value': [keys]
14 }
15 self._execute(Command.REPLACE_KEYS, data)
16 return self
set_text()
send_keys和set_text也都能满足输入文本内容的操作。其区别在于send_keys会调用设备当前系统输入法键盘,而set_text直接对目标元素设置文本。由此可推,send_keys的输入内容往往和预期内容不一致,而set_text的输入则是直接赋值,并不是键盘事件。
(3)滑动(翻屏)事件
1 def swipe(self, start_x, start_y, end_x, end_y, duration=None):
2 """Swipe from one point to another point, for an optional duration.
3
4 :Args:
5 - start_x - x-coordinate at which to start
6 - start_y - y-coordinate at which to start
7 - end_x - x-coordinate at which to stop
8 - end_y - y-coordinate at which to stop
9 - duration - (optional) time to take the swipe, in ms.
10
11 :Usage:
12 driver.swipe(100, 100, 100, 400)
13 """
14 # `swipe` is something like press-wait-move_to-release, which the server
15 # will translate into the correct action
16 action = TouchAction(self)
17 action \
18 .press(x=start_x, y=start_y) \
19 .wait(ms=duration) \
20 .move_to(x=end_x, y=end_y) \
21 .release()
22 action.perform()
23 return self
swipe()
1 def flick(self, start_x, start_y, end_x, end_y):
2 """Flick from one point to another point.
3
4 :Args:
5 - start_x - x-coordinate at which to start
6 - start_y - y-coordinate at which to start
7 - end_x - x-coordinate at which to stop
8 - end_y - y-coordinate at which to stop
9
10 :Usage:
11 driver.flick(100, 100, 100, 400)
12 """
13 action = TouchAction(self)
14 action \
15 .press(x=start_x, y=start_y) \
16 .move_to(x=end_x, y=end_y) \
17 .release()
18 action.perform()
19 return self
flick()
swipe和flick都是滑动操作,它们都是从[start_x, start_y]划到[end_x, end_y]的过程,唯一不同的是swipe比flick多了一个duration参数,有了这个参数就可以自定义从start到end动作的作用时间,以达到快速滑动或者慢速滑动的效果。
(4)缩放事件
1 def pinch(self, element=None, percent=200, steps=50):
2 """Pinch on an element a certain amount
3
4 :Args:
5 - element - the element to pinch
6 - percent - (optional) amount to pinch. Defaults to 200%
7 - steps - (optional) number of steps in the pinch action
8
9 :Usage:
10 driver.pinch(element)
11 """
12 if element:
13 element = element.id
14
15 opts = {
16 'element': element,
17 'percent': percent,
18 'steps': steps,
19 }
20 self.execute_script('mobile: pinchClose', opts)
21 return self
pinch()
1 def zoom(self, element=None, percent=200, steps=50):
2 """Zooms in on an element a certain amount
3
4 :Args:
5 - element - the element to zoom
6 - percent - (optional) amount to zoom. Defaults to 200%
7 - steps - (optional) number of steps in the zoom action
8
9 :Usage:
10 driver.zoom(element)
11 """
12 if element:
13 element = element.id
14
15 opts = {
16 'element': element,
17 'percent': percent,
18 'steps': steps,
19 }
20 self.execute_script('mobile: pinchOpen', opts)
21 return self
zoom()
默认会对目标元素进行放大一倍或者缩小一半的操作,此api方法适合于在测试运动地图的缩放时的变化。
(5)长按事件
1 def long_press(self, el=None, x=None, y=None, duration=1000):
2 """Begin a chain with a press down that lasts `duration` milliseconds
3 """
4 self._add_action('longPress', self._get_opts(el, x, y, duration))
5
6 return self
long_press()
长按方法是在TouchAction类中,所以在使用时需要先import TouchAction。在删除运动历史记录时,在记录列表长按删除,
action1 = TouchAction(self.driver)
driver_element = driver.find_element_by_xpath("sport_history_item_xpath")
action1.long_press(driver_element).wait(i * 1000).perform() // i为长按控件的时间,单位秒
(6)keyevent事件(android only)
在Android keyevent事件中,不同的值代表了不同的含义和功能,比如手机的返回键:keyevent(4); 手机的HOME键: keyevent(3)等等,具体keyevent与对应值关系请参考
四、元素事件类API
(1) reset
def reset(self):
"""Resets the current application on the device.
"""
self.execute(Command.RESET
return self
reset()
用法:driver.reset(),重置应用(类似删除应用数据),如首次登录app时出现的引导页,则可以用reset来实现需求。
(2) is_app_installed
1 def is_app_installed(self, bundle_id):
2 """Checks whether the application specified by `bundle_id` is installed
3 on the device.
4
5 :Args:
6 - bundle_id - the id of the application to query
7 """
8 data = {
9 'bundleId': bundle_id,
10 }
11 return self.execute(Command.IS_APP_INSTALLED, data)['value']
is_app_installed()
检查app是否有安装 返回 True or False。例如:在微信登录时,选择登录方式时会判断是否已安装微信,若未安装则有dialog弹框,已安装则跳转到微信登录页面,
driver.find_element_by_id("weixin_login_button").click()
if driver.is_app_installed("weixin.apk"):
// To do input User, Passwd
else:
// show dialog
(3)install_app
def install_app(self, app_path):
"""Install the application found at `app_path` on the device.
:Args:
- app_path - the local or remote path to the application to install
"""
data = {
'appPath': app_path,
}
self.execute(Command.INSTALL_APP, data)
return self
install_app()
接上个例子,若未安装微信出现dialog弹框,检查完dialog后再安装微信app。特别说明:例子中的"weixin.apk"是指app_path + package_name,
driver.find_element_by_id("weixin_login_button").click()
if driver.is_app_installed("weixin.apk"):
// To do input User, Passwd
else:
check_dialog()
driver.install_app("weixin.apk")
(4) remove_app
1 def remove_app(self, app_id):
2 """Remove the specified application from the device.
3
4 :Args:
5 - app_id - the application id to be removed
6 """
7 data = {
8 'appId': app_id,
9 }
10 self.execute(Command.REMOVE_APP, data)
11 return self
remove_app()
在测试老版本兼容用例时,用老版本替换新版本时,需要卸载新版本,再安装老版本,所以需要调用到此方法,
driver.remove_app("new_app.apk") # 卸载
driver.install_app("old_app.apk") # 安装
(5) launch_app
1 def launch_app(self):
2 """Start on the device the application specified in the desired capabilities.
3 """
4 self.execute(Command.LAUNCH_APP)
5 return self
launch_app()
打开一个capabilities配置的设备应用。此方法目前并没有使用。待以后用到时再来做更新。
(6) close_app
1 def close_app(self):
2 """Stop the running application, specified in the desired capabilities, on
3 the device.
4 """
5 self.execute(Command.CLOSE_APP)
6 return self
close_app()
关闭app应用程序。此方法常用在代码末尾,在清理和释放对象时使用。结合unittest框架常见tearDown()里使用,
import unittest
class demo(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
driver.close_app()
driver.quit()
(7) start_activity
def start_activity(self, app_package, app_activity, **opts):
"""Opens an arbitrary activity during a test. If the activity belongs to
another application, that application is started and the activity is opened.
This is an Android-only method.
:Args:
- app_package - The package containing the activity to start.
- app_activity - The activity to start.
- app_wait_package - Begin automation after this package starts (optional).
- app_wait_activity - Begin automation after this activity starts (optional).
- intent_action - Intent to start (optional).
- intent_category - Intent category to start (optional).
- intent_flags - Flags to send to the intent (optional).
- optional_intent_arguments - Optional arguments to the intent (optional).
- stop_app_on_reset - Should the app be stopped on reset (optional)?
"""
data = {
'appPackage': app_package,
'appActivity': app_activity
}
arguments = {
'app_wait_package': 'appWaitPackage',
'app_wait_activity': 'appWaitActivity',
'intent_action': 'intentAction',
'intent_category': 'intentCategory',
'intent_flags': 'intentFlags',
'optional_intent_arguments': 'optionalIntentArguments',
'stop_app_on_reset': 'stopAppOnReset'
}
for key, value in arguments.items():
if key in opts:
data[value] = opts[key]
self.execute(Command.START_ACTIVITY, data)
return self
start_activity()
此方法适用于测试中需使用两个及以上的app程序。例如,在运动相关的测试时,首先需要打开Gps模拟打点工具,开始打点。然后打开咕咚选择运动类型开始运动,那么可以在启动capabilities配置时打开Gps工具,开启配速打点后再打开咕咚app,
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps={'platformName': 'Android',
'deviceName': 'Android Mechine',
'appPackage': ' Package of GpsTools',
'unicodeKeyboard':True,
'resetKeyboard':True,
'noReset':True,
'appActivity': 'activity of GpsTools'})
# TO DO Gps Mock action
driver.start_activity("com.codoon.gps", "ui.login.welcomeActivity")
(8) wait_activity
1 def wait_activity(self, activity, timeout, interval=1):
2 """Wait for an activity: block until target activity presents
3 or time out.
4
5 This is an Android-only method.
6
7 :Agrs:
8 - activity - target activity
9 - timeout - max wait time, in seconds
10 - interval - sleep interval between retries, in seconds
11 """
12 try:
13 WebDriverWait(self, timeout, interval).until(
14 lambda d: d.current_activity == activity)
15 return True
16 except TimeoutException:
17 return False
wait_activity()
此方法适属于appium等待方法的一种。不论是webdriver还是appium,等待方法分为三种类型:显式等待、隐式等待,time.sleep;从wait_activity的源码可以看出,是属于隐式等待。有关等待方式以后可以另开专题详细说明,这里不做赘述。
此方法主要使用在需要网络加载时的等待,比如在用户登录作为前提条件时,wait_activity接受三个参数: 需要等待加载的activity的名称,timeout超时时间(秒),检测间隔时间(秒),
driver.login_action()
driver.wait_activity("homepage.activity", 30, 1)
driver.find_element_by_id("我的").click()
其含义是,等待加载app的homepage的activity出现,等待最长时间30秒,每隔1秒检测一次当前的activity是否等于homepage的activity。若是,则推出等待,执行点击我的tab的action;若否,则继续等待,30秒后提示超时抛出异常。
四、其他(此类别下主要对上述没有提到的api方法的补充)
(1) 截屏
1 def get_screenshot_as_file(self, filename):
2 """
3 Gets the screenshot of the current window. Returns False if there is
4 any IOError, else returns True. Use full paths in your filename.
5
6 :Args:
7 - filename: The full path you wish to save your screenshot to.
8
9 :Usage:
10 driver.get_screenshot_as_file('/Screenshots/foo.png')
11 """
12 png = self.get_screenshot_as_png()
13 try:
14 with open(filename, 'wb') as f:
15 f.write(png)
16 except IOError:
17 return False
18 finally:
19 del png
20 return True
get_screenshot_as_file()
用法:driver.get_screenshot_as_file('../screenshot/foo.png'),接受参数为保存的图片路径和名称
(2)size 和 location
1 def size(self):
2 """The size of the element."""
3 size = {}
4 if self._w3c:
5 size = self._execute(Command.GET_ELEMENT_RECT)
6 else:
7 size = self._execute(Command.GET_ELEMENT_SIZE)['value']
8 new_size = {"height": size["height"],
9 "width": size["width"]}
10 return new_size
size()
1 def location(self):
2 """The location of the element in the renderable canvas."""
3 if self._w3c:
4 old_loc = self._execute(Command.GET_ELEMENT_RECT)
5 else:
6 old_loc = self._execute(Command.GET_ELEMENT_LOCATION)['value']
7 new_loc = {"x": round(old_loc['x']),
8 "y": round(old_loc['y'])}
9 return new_loc
location()
size 和 location是对element位置和尺寸的获取,这两个属性主要运用在有可划动的控件,如完善个人资料页,生日、身高和体重都需要划动。之前我们提到的swipe和flick是针对设备屏幕进行划动,显然在这里不适用。而且没有一个特定的方法,所以需要我们自己针对可划动控件进行划动,
1 def swipe_control(self, by, value, heading):
2 """
3 :Usage: 实现界面某些控件元素的上下左右的滑动取值
4 :param driver: Appium驱动
5 :param by: 元素的定位方式,如By.ID、By.XPATH等...
6 :param value: 元素的定位值,根据不同的定位方式表达不同
7 :param heading: 滑动的方位,包括'UP','DOWN','LEFT','RIGHT'
8 """
9 # "获取控件开始位置的坐标轴"
10 start = self.driver.find_element(by=by, value=value).location
11 startX = start.get('x')
12 startY = start.get('y')
13 # "获取控件坐标轴差"
14 q = self.driver.find_element(by=by, value=value).size
15 x = q.get('width')
16 y = q.get('height')
17 # "计算出控件结束坐标"
18 if startX < 0:
19 startX = 0
20 endX = x + startX
21 endY = y + startY
22 # "计算中间点坐标"
23 if endX > 720:
24 endX = 720
25 centreX = (endX + startX) / 2
26 centreY = (endY + startY) / 2
27
28 # "定义swipe的方向"
29 actions = {
30 'UP': self.driver.swipe(centreX, centreY + 65, centreX, centreY - 65, 450),
31 'DOWN': self.driver.swipe(centreX, centreY - 65, centreX, centreY + 65, 450),
32 'LEFT': self.driver.swipe(centreX + 65, centreY, centreX - 65, centreY, 450),
33 'RIGHT': self.driver.swipe(centreX - 65, centreY, centreX + 65, centreY, 450),
34 }
35 # "Take a action"
36 actions.get(heading)
swipe_control()
(3)获取控件各种属性
def get_attribute(self, name):
"""
:Args:
- name - Name of the attribute/property to retrieve.
Example::
# Check if the "active" CSS class is applied to an element.
is_active = "active" in target_element.get_attribute("class")
"""
View Code
用法: driver.find_element_by_id().get_attribute(name),name即是左侧的标志(class,package,checkable,checked....),返回值为str类型,即便是true or false,但是其实是"true" or "false"
(4)pull_file
1 def pull_file(self, path):
2 """Retrieves the file at `path`. Returns the file's content encoded as
3 Base64.
4
5 :Args:
6 - path - the path to the file on the device
7 """
8 data = {
9 'path': path,
10 }
11 return self.execute(Command.PULL_FILE, data)['value']
pull_file()
将设备上的文件pull到本地硬盘上,在手机号注册时需要获取手机验证码,此时的实现方式是用另一个apk提取到验证码存在手机内存中,再用pull_file获取到验证码内容,使得appium可以将正确的验证码填入。
本章节Appium常用的一些API函数,日后有需要会慢慢地进行补充。虽然不能面面俱到,但在实际项目中涉及到的都已经差不多提到了。当我们对某个功能进行测试的时候,首先要对其进行操作,这个时候就要考虑到找到相应的对象元素,调用具体的事件类函数,验证结果的时候,我们要检测操作产生的结果是不是与我们预期的一致?那这就要去考虑相应的Assert函数(即断言)了。所以记住这六字箴言:对象-事件-断言,便可以使你可以着手对任何一个App编写对应的自动化测试用例了。