天天看點

移動端UI自動化之appium的使用(一)

一、 擷取app的資訊

1.1、擷取App啟動的appPackage和appActivity

方式一:通過 logcat 日志擷取

# linux/mac啟動日志列印指令,每次操作裝置上的app都會列印package和activity資訊。
adb logcat ActivityManager:I | grep "cmp"
# windows
adb logcat ActivityManager:I | findstr "cmp" 
           

打開App第一次擷取的cmp中的内容,就是所需要的啟動appPackage和appActivity

移動端UI自動化之appium的使用(一)

方式二:通過 aapt 擷取

# mac/Linux
aapt dump badging wework.apk  | grep launchable-activity
# windows
aapt dump badging wework.apk  | findstr launchable-activity
           

驗證擷取的appPackage和appActivity是否正确

# 啟動應用指令
adb shell am start -W -n <package-name>/<activity-name> -S
           

示例:啟動企業微信app

C:\Users\18367>adb shell am start -W -n com.tencent.wework/.launch.LaunchSplashActivity -S
Stopping: com.tencent.wework
Starting: Intent { cmp=com.tencent.wework/.launch.LaunchSplashActivity }
Status: timeout
Activity: com.tencent.wework/.launch.LaunchSplashActivity
WaitTime: 10217
Complete
           

1.2、App資訊

app資訊

# 擷取目前界面元素
adb shell dumpsys activity top
# 擷取任務清單
adb shell dumpsys  activity activities
           

二、appium元素定位與三種等待

2.1、desirecapability介紹

Capability設定

常用參數

更多詳細功能參考官網中文文檔

app:apk位址

appPackage:包名

appActivity:Activity名稱

automationName:android預設使用uiautomator2,ios預設使用XCUITest

noReset/fullReset:是否在測試前後重置相關環境

unicodeKeyboard和resetKeyboard: 是否需要輸入非英文之外的語言并在測試完成後重置輸入法

dontStopAppOnReset:首次啟動的時候,不停止app(可以調試或者運作的時候提升運作速度)

skipDeviceInitialization:跳過安裝,權限設定等操作(可以調試或者運作的時候提升運作速度)

2.2、appium元素定位

常用的兩種定位方式id,accessibility_id

driver.find_element_by_id(resource-id)
driver.find_element_by_accessibility_id(content-desc)
           

2.3、三種等待

強制等待

也叫做程序等待,通常通過sleep等待固定的時間,一般不推薦

隐式等待(全局性)

設定一個逾時時間,服務端appium會在給定的時間内,不停的查找,預設值是0

用法:

self.driver.implicitly_wait(5)

在服務端等待

顯式等待(等待某個元素)

在用戶端等待

用法:

WebDriverWait(driver, 10).until(lamba x: x.find_element(By.ID, ''))

三、app控件定位

3.1、android/ios基礎知識

android基礎知識

Android 是通過容器的布局屬性來管理子控件的位置關系,布局過程就是把界面上的所有控件,根據他們的間距的大小,擺放在正确的位置。

Android七大布局:

  1. LinearLayout(線性布局)
  2. RelativeLayout(相對布局)
  3. FrameLayout(幀布局)
  4. AbsoluteLayout(絕對布局)
  5. TableLayout(表格布局)
  6. GridLayout(網格布局)
  7. ConstraintLayout(限制布局)

Android四大元件:

  1. activity:與使用者互動的可視化界面
  2. service:實作程式背景運作的解決方案
  3. content provider:内容提供者,提供程式所需要的資料
  4. broadcast receiver:廣播接收器,監聽外部事件的到來(比如來電)

常用的控件:

  • TextView:文本控件
  • EditText:可編輯文本控件
  • Button:按鈕
  • ImageButton:圖檔按鈕
  • ToggleButton:開關按鈕
  • ImageView:圖檔控件
  • CheckBox:複選框控件
  • RadioButton:單選框控件

布局是一種可用于放置很多控件的容器,它可以按照一定的規律調整内部控件的位置,進而編寫出精美的控件。當然布局的内部除了放置控件外,也可以放置布局,通過多層布局的嵌套,我們就能夠完成一些比較複雜的界面。

ios基礎知識

ios去掉了布局的概念,直接用變量之間的相對關系來完成位置的計算。

注意:使用Appium測試ios應用需要使用MacOS作業系統

3.2、dom結構解讀

控件基礎知識

  • dom:Document Object Model 文檔對象模型
  • dom應用:最早應用于html和js的互動。用于表示界面的控件層級,界面的結構化描述,常見的格式為html、xml。核心元素為節點和屬性。
  • xpath:xml路徑語言,用于xml中的節點定位。

Android 應用的層級結構與html不一樣,是一個定制的xml。app source 類似于dom,表示app的層級,代表了界面裡面所有的控件樹的結構。每個控件都有它的屬性(resourceid,xpath,aid),沒有css屬性。

ios與Android的差別

  • dom屬性和節點結構類似
  • 名字和屬性的名稱不同(比如:android:resourceid,ios: name; android: content-desc ios: accessibility-id)

3.3、id、aid、xpath定位方法

測試步驟三要素:定位、互動、斷言

元素定位,實際上就是定位控件。要想一個腳本同時支援android/ios兩個系統,就得保證元素屬性(id、aid、xpath等)一緻。

id定位

driver.find_element_by_id(resource-id)
driver.find_element(MobileBy.ID, "resource-id")
           

accessibility-id 定位

driver.find_element_by_accessibility_id(content-desc)
driver.find_element(MobileBy.ACCESSIBILITY_ID, "content-desc")
           

xpath定位

driver.find_element_by_xpath(xpath屬性值)
driver.find_element(MobileBy.XPATH, xpath屬性值)
           

示例:

from appium import webdriver


class TestDW:

	def setup(self):
		des_caps = {
			'platformName': 'Android',
			'platformVersion': '7.1.2',
			'deviceName': '127.0.0.1:62001',
			'appPackage': 'com.xueqiu.android',
			'appActivity': '.common.MainActivity',
			'unicodeKeyboard': True,
			'resetKeyboard': True,
			'noReset': True,
			'dontStopAppOnReset': True,
			'skipDeviceInitialization': True
		}

		self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', des_caps)
		self.driver.implicitly_wait(10)

	def teardown(self):
		self.driver.back()
		self.driver.back()
		self.driver.quit()

	def test_search(self):
		"""
		1、打開 雪球 app
		2、點選搜尋輸入框
		3、向搜尋框裡面輸入“阿裡巴巴”
		4、在搜尋結果裡面選擇“阿裡巴巴”,然後進行點選
		5、擷取這隻阿裡巴巴的股價,并判斷這隻股價的價格>200
		:return:
		"""
		self.driver.find_element_by_id('com.xueqiu.android:id/tv_search').click()
		self.driver.find_element_by_id('com.xueqiu.android:id/search_input_text').send_keys('阿裡巴巴')
		self.driver.find_element_by_xpath('//*[contains(@text, "-SW")]').click()
		current_price = self.driver.find_element_by_id('com.xueqiu.android:id/current_price').text
		print(current_price)
		assert float(current_price) > 200
           

3.4、uiautomatorviewer定位工具使用

定位工具

  1. uiautomatorviewer工具(android):輕便推薦使用,在sdk路徑下的工具

    所在路徑:D:\android-sdk\tools\uiautomatorviewer.bat,由于已經把tools路徑加入到了環境變量,可以直接在指令行中輸入"uiautomatorviewer"啟動

  2. appium inspector工具(ios,android)

四、app控件互動

4.1、元素的常用方法

常用方法

  • 點選方法:element.click()
  • 輸入操作:element.send_keys(‘appium’)
  • 設定元素的值:element.set_value(‘appium’)
  • 清除操作:element.clear()
  • 是否可見:element.is_displayed(),傳回True/False
  • 是否可用:element.is_enalbed(),傳回True/False
  • 是否被選中:element.is_selected(),傳回True/False
  • 擷取屬性值:element.get_attribute(name)

4.2、元素的常用屬性

常用屬性

  • 擷取元素文本:element.text
  • 擷取元素坐标(左上角):element.location,傳回結果:{‘x’: 498, ‘y’: 19}
  • 擷取元素尺寸:element.size,傳回結果:{‘width’: 500, ‘height’: 22}

示例

def test_attribute(self):
	"""
	1、打開【雪球】應用首頁
	2、定位首頁的搜尋框
	3、判斷搜尋框是否可用,并檢視搜尋框name屬性值
	4、列印搜尋框這個元素的左上角坐标和塔的寬高
	5、向搜尋框輸入:alibaba
	6、判斷【阿裡巴巴】是否可見
	7、如果可見,列印“搜尋成功”,如果不可見,列印“搜尋失敗”
	:return:
	"""
	search_ele = self.driver.find_element_by_id('com.xueqiu.android:id/tv_search')
	print(search_ele.text)
	print(f'搜尋框左上角坐标:{search_ele.location}')
	print(f'搜尋框尺寸:{search_ele.size}')
	if search_ele.is_enabled():
		search_ele.click()
		self.driver.find_element_by_id('com.xueqiu.android:id/search_input_text').send_keys('alibaba')
		alibaba_ele = self.driver.find_element_by_xpath('//*[contains(@text, "-SW")]')
		alibaba_displayed = alibaba_ele.get_attribute("displayed")
		print(f'阿裡巴巴是否可見:{alibaba_displayed}')
		if alibaba_displayed == 'true':
			print('搜尋成功')
		else:
			print('搜尋失敗')
           

五、觸屏操作自動化

TouchAction: 官方文檔

touchaction.apk下載下傳,提取碼:v005

雪球app下載下傳,提取碼:cd8v

def test_touch_action(self):
	"""雪球滑動"""
	size = self.driver.get_window_size()
	w = size['width']  # 手機螢幕的寬度
	h = size['height']  # 手機螢幕的高度
	action = TouchAction(self.driver)
	action.press(x=1 / 2 * w, y=3 / 4 * h).wait(200).move_to(
		x=1 / 2 * w, y=1 / 4 * h).release().perform()
	sleep(3)
           

六、進階定位技巧

6.1、xpath定位

def test_get_current_price(self):
     """擷取目前股價"""
     self.driver.find_element_by_id('com.xueqiu.android:id/tv_search').click()
     self.driver.find_element_by_id('com.xueqiu.android:id/search_input_text').send_keys('阿裡巴巴')
     self.driver.find_element_by_xpath('//*[@resource-id="com.xueqiu.android:id/name" and @text="阿裡巴巴"]').click()
     current_price = float(self.driver.find_element_by_xpath(
         '//*[@text="09988"]/../../..//*[@resource-id="com.xueqiu.android:id/current_price"]').text)
     assert current_price >= 200
           

6.2、uiautomator定位表達式

一般主要使用文本定位text

移動端UI自動化之appium的使用(一)
def test_my_info(self):
     """雪球app:使用者登入"""
     self.driver.find_element_by_android_uiautomator('new UiSelector().text("我的")').click()
     self.driver.find_element_by_android_uiautomator('new UiSelector().textContains("帳号密碼登入")').click()
     self.driver.find_element_by_android_uiautomator(
         'new UiSelector().resourceId("com.xueqiu.android:id/login_account")').send_keys('123456')
     self.driver.find_element_by_android_uiautomator(
         'new UiSelector().resourceId("com.xueqiu.android:id/login_password")').send_keys('123456')
     self.driver.find_element_by_android_uiautomator(
         'new UiSelector().resourceId("com.xueqiu.android:id/button_next")').click()
     sleep(2)
           

組合定位

id_text = 'new UiSelector().resourceId("com.baidu.yuedu:id/webbooktitle").text("小說")'
driver.find_element_by_android_uiautomator(id_text).click()
           

父子關系定位:childSelector

有時候不能直接定位某個元素,但是他的父元素很好定位,這時候就先定位父元素,通過父元素找兒子

son_text = 'new UiSelector().resourceId("com.baidu.yuedu:id/rl_tabs").childSelector(text("股票"))'
driver.find_element_by_android_uiautomator(son_text).click()
           

兄弟定位:fromParent

有時候父元素不好定位,但是跟它相鄰的兄弟元素很好定位,這時候就可以通過兄弟元素,找到同一父級元素下的子元素

borther_text = 'new UiSelector().resourceId("com.baidu.yuedu:id/lefttitle").fromParent(text("使用者"))'
driver.find_element_by_android_uiautomator(borther_text ).click()
           

6.3、滑動定位

def test_scroll_find_element(self):
    self.driver.find_element_by_android_uiautomator('new UiSelector().text("關注")').click()
    # 滑動查找,有逾時時間
    self.driver.find_element_by_android_uiautomator('new UiScrollable(new UiSelector().'
                                                    'scrollable(true).instance(0)).'
                                                    'scrollIntoView(new UiSelector().'
                                                    'text("南極電商(SZ002127)").instance(0));').click()
           

七、appium顯式等待機制

顯式等待與隐式等待相對,顯式等待必須在每個需要等待的元素前面進行聲明。是針對于某個特定的元素設定的等待時間,在設定時間内,預設每隔一段時間檢測一次目前頁面某個元素是否存在。

如果在規定的時間内找到了元素,則直接執行,即找到元素就執行相關操作。

如果超過設定時間檢測不到則抛出異常,預設檢測頻率為0.5s,預設抛出異常為:NoSuchElementException

顯式等待用到的兩個類:WebDriverWait和expected_conditions兩個類。

顯式等待可以等待動态加載的ajax元素,顯式等待需要使ExpectedConditions來檢查條件。

一般頁面上元素的顯式順序:

  1. title出現,首先出現title
  2. dom樹出現(隐式等待的執行時期, presence), 還不完整
  3. css出現(可見visibility)
  4. js出現,js特性執行(可點選clickable)

html文檔是自上而下加載的,js檔案加載會阻塞html内容的加載,有些js異步加載的方式來完成js的加載。

樣式表下載下傳完成之後會跟之前的樣式表一起進行解析,會對之前的元素重新渲染。

7.1、WebDriverWait 用法

WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)

  • driver: 浏覽器驅動
  • timeout:最長逾時時間,預設以秒為機關
  • poll_frequency:檢測的間隔步長,預設為0.5s
  • ignored_exceptions:忽略的異常資訊,預設是忽略NoSuchElementException

WebDriverWait的until()和until_not()方法:

  • method:在等待期間,每隔一段時間(__init__中的poll_frequency)調用這個傳入方法(必須要有一個位置參數,用于傳遞driver),直到傳回值不是False。
  • message:如果逾時,抛出TimeoutException,将message傳入異常。

until_not于until相反,until是當某元素出現或什麼條件成立則繼續執行,until_not是當某元素消失或什麼條件不成立則繼續執行,參數相同。

7.2、總結三種等待方式

隐式等待: 盡量預設都加上,時間限定在3-6s,不要太長,為了所有的find_element方法都有一個很好的緩沖。

顯示等待: 用來處理隐式等待無法解決的一些問題,比如:檔案上傳(可以設定長一點),檔案上傳需要20秒以上,但是如果設定以上等待,它會在每個find方法都設定這麼長時間,一旦發現沒有找到元素,就會等20s以後才抛出異常,影響case的執行效率,這時候就需要用顯示等待,顯示等待可以設定的長一點。

強制等待: 一般不推薦,前兩種基本能解決大部分問題,如果某個控件沒有任何特征,隻能強制等待,這種情況比較少。

八、toast控件識别

測試app下載下傳,提取碼:8b6t

移動端UI自動化之appium的使用(一)

8.1、toast介紹

Toast,簡易的消息提示框

  • 為了給目前視圖顯示一個浮動的顯示塊,與dialog不同它永遠不會獲得焦點
  • Toast類的思想:盡可能不引人注意,同時還向使用者顯示資訊希望他們看到
  • Toast顯示的時間有限,Toast會根據使用者設定的顯示時間後自動消失
  • Toast本身是個系統級别的控件,它歸屬于系統settings,當一個app發送消息的時候,不是自己造出來的這個彈框,它是發給系統,由系統統一進行彈框,這類的控件不在app内,需要特殊的控件識别方法。

8.2、toast定位

appium使用uiautomator底層的機制來分析抓取toast,并且把toast放到控件樹裡面,但本身并不屬于控件。automationName:uiautomator2(預設的工作引擎,可以不加這個參數)。driver.page_source屬性無法擷取,必須使用xpath查找。

xpath定位toast的兩種方式:

  1. //*[@class=“android.widget.Toast”]
  2. //*[contains(., “xxx”)]
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy


class TestToast:

	def setup(self):
		des_caps = {
			'platformName': 'Android',
			'platformVersion': '7.1.2',
			'automationName': 'uiautomator2',
			'deviceName': '127.0.0.1:62001',
			'appPackage': 'io.appium.android.apis',
			'appActivity': '.view.PopupMenu1',
			'unicodeKeyboard': True,
			'resetKeyboard': True,
			'noReset': True,
			'dontStopAppOnReset': True,
			'skipDeviceInitialization': True
		}

		self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', des_caps)
		self.driver.implicitly_wait(5)

	def teardown(self):
		self.driver.quit()

	def test_toast(self):
		"""擷取toast資訊"""
		self.driver.find_element(MobileBy.ACCESSIBILITY_ID, 'Make a Popup!').click()
		self.driver.find_element(MobileBy.XPATH, '//*[@resource-id="android:id/title" and @text="Search"]').click()
		toast_text = self.driver.find_element(MobileBy.XPATH, '//*[@class="android.widget.Toast"]').text
		print(toast_text)