一、 擷取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
方式二:通過 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七大布局:
- LinearLayout(線性布局)
- RelativeLayout(相對布局)
- FrameLayout(幀布局)
- AbsoluteLayout(絕對布局)
- TableLayout(表格布局)
- GridLayout(網格布局)
- ConstraintLayout(限制布局)
Android四大元件:
- activity:與使用者互動的可視化界面
- service:實作程式背景運作的解決方案
- content provider:内容提供者,提供程式所需要的資料
- 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定位工具使用
定位工具
-
uiautomatorviewer工具(android):輕便推薦使用,在sdk路徑下的工具
所在路徑:D:\android-sdk\tools\uiautomatorviewer.bat,由于已經把tools路徑加入到了環境變量,可以直接在指令行中輸入"uiautomatorviewer"啟動
- 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
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來檢查條件。
一般頁面上元素的顯式順序:
- title出現,首先出現title
- dom樹出現(隐式等待的執行時期, presence), 還不完整
- css出現(可見visibility)
- 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
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的兩種方式:
- //*[@class=“android.widget.Toast”]
- //*[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)