上一章節已經介紹了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']
比如我們需要實現這個登錄的功能時,主要思路為如果當前界面為登錄頁面時,就進行登錄行為,否則就跳轉到登錄頁面。其偽代碼為:
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']
例如當我們完成登錄流程之后,要判斷登錄是否成功,則可以判斷登錄后的頁面有沒有出現特定的內容(比如:運動圈、發現、運動、商城、我的),其偽代碼實現如下:
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']
在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這一類的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_)
在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_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_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的調用方法與通過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_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)
在實際項目中,測試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)
在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)

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
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)})

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
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

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
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

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
默認會對目標元素進行放大一倍或者縮小一半的操作,此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
長按方法是在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與對應值關系請參考http://blog.csdn.net/yun90/article/details/51036544
四、元素事件類API
(1) reset

def reset(self): """Resets the current application on the device. """ self.execute(Command.RESET return self
用法: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']
檢查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
接上個例子,若未安裝微信出現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
在測試老版本兼容用例時,用老版本替換新版本時,需要卸載新版本,再安裝老版本,所以需要調用到此方法,
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
打開一個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
關閉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
此方法適用於測試中需使用兩個及以上的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
此方法適屬於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
用法: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

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
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)
(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") """
用法: 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到本地硬盤上,在手機號注冊時需要獲取手機驗證碼,此時的實現方式是用另一個apk提取到驗證碼存在手機內存中,再用pull_file獲取到驗證碼內容,使得appium可以將正確的驗證碼填入。
本章節Appium常用的一些API函數,日后有需要會慢慢地進行補充。雖然不能面面俱到,但在實際項目中涉及到的都已經差不多提到了。當我們對某個功能進行測試的時候,首先要對其進行操作,這個時候就要考慮到找到相應的對象元素,調用具體的事件類函數,驗證結果的時候,我們要檢測操作產生的結果是不是與我們預期的一致?那這就要去考慮相應的Assert函數(即斷言)了。所以記住這六字箴言:對象-事件-斷言,便可以使你可以着手對任何一個App編寫對應的自動化測試用例了。