1.安裝夜神安裝模擬器、UiAutomator、android sdk、javasdk環(huán)境;
https://pan.baidu.com/s/1Z70sPJagQG1EDRnVfzaOFA
2.配置環(huán)境變量,啟動模擬器、運行UiAutomator viewer.bat,在模擬器中安裝測試應用;
https://pan.baidu.com/s/1a2Bs1D8MsmDK8eakrUN5CA
也可以使用APPium desktop :https://github.com/appium/appium-desktop
包含inspector 可以來解析定位元素;
進行配置如下:
{
"deviceName": "Redmi 6",
"platformName": "Android",
"appPackage": "com.china.moa",
"appActivity": "com.ecology.view.WelcomeActivity",
"platformVersion": "8.1.0",
"automationName": "UiAutomator2",
"udid": "99001190304762"
}
手機開啟開發(fā)者模式,打開USB調試;向云手機圖庫發(fā)送圖片:
1、上傳圖片,路徑不同手機有差異:
adb push {file path} /sdcard/DCIM/Camera/{file name}
2、廣播推至相冊:
adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Camera/{file name}修改云手機定位:
adb -s ip:port shell "echo 'longitude=114.055939:latitude=22.657501' > /data/gps/fifo" #其中ip:port是ADB方式(公網(wǎng))中記錄的ip和port。
以慈壽寺地鐵為例:
adb -s 127.0.0.1:5504 shell "echo 'longitude=116.2871874100:latitude=39.9328901600' > /data/gps/fifo" |
經緯度獲?。篽ttp://www.gpsspg.com/maps.htm
- 下載安裝APPIUM推薦官網(wǎng);
- appium -g /tmp/run.log #appium log輸出
- android-sdk中的avd manger.exe 可以創(chuàng)建模擬器或使用AndroidStudio創(chuàng)建模擬器;
adb基礎
app信息
adb shell dumpsys activity top #獲取當前界面元素
adb shell dumpsys activity activities #獲取任務列表
#app入口
adb logcat |grep -i displayed
aapt dump badging mobike.apk | grep launchable-activity
apkanalyzer 最新版本的sdk中才有
#啟動應用
adb shell am start -W -n com.xueqiu.android/.view.WelcomeActivityAlias -S
$adb devices #設備可用列表
$adb -s 127.0.0.1:5510 install apk絕對路徑 #安裝apk
$adb shell dumpsys activity |find "mFocusedActivity" #查看前臺應用包名;
$adb kill-server #終止adb服務
$adb start-server #啟動adb服務;
$adb pull [手機路徑] [本地路徑] #將手機文件拉取到PC;
$adb push [本地路徑] [手機路徑] # 將PC文件放到手機;
$adb shell am start -n 包名/入口 #啟動app
$adb shell pm clean 包名 #清除應用的數(shù)據(jù)和緩存;
$adb shell input tap x y #坐標點擊;
$adb shell pm list packages #列出所有包名 -s 列出系統(tǒng)apk路徑及包名, -3 列出用戶apk路徑以及包名;
$adb logcat > logcat.log #打印APP日志
$aapt dump badging apk包路徑 ##查看指定apk的相關信息-找app入口 launchable-activity;
deviceName值的獲取:
deviceName=192.168.137.150:5555 ip:手機ip地址,端口,通過如下命令開啟
$ adb devices //查看當前連接設備
$ adb tcpip 5555 //開啟5555端口
$ adb connect 192.168.137.150 //連接手機看是否能連接
$ adb devices //再查看當前連接設備
adb usb #拔取數(shù)據(jù)線后執(zhí)行,回復usb調試模式,重新插線;
jar依賴包
<!--Java client for Appium Mobile Webdriver -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>7.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8</version>
<scope>test</scope>
</dependency>
添加配置[Appium Desired Capabilities]
capabilities設置
? app apk地址
? appPackage 包名
? appActivity Activity名字
? automationName 默認使?uiautomator
? noReset fullReset 是否在測試前后重置相關環(huán)境
? unicodeKeyBoard resetKeyBoard 是否需要輸??英?
之外的語?并在測試完成后重置輸?法
#(https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md)
desiredCapabilities.setCapability("deviceName", "06f8794b7d29");
desiredCapabilities.setCapability("platformName", Platform.ANDROID);
desiredCapabilities.setCapability("appPackage", "com.xx.xx");
desiredCapabilities.setCapability("appActivity","com.xx.xx.xx" );
//uiautomator2自動化引擎,解決輸入框輸入不了數(shù)據(jù)的問題,
desiredCapabilities.setCapability("automationName","uiautomator2" );
//noReset 不清除應用啟動數(shù)據(jù);默認清理false;
desiredCapabilities.setCapability("noReset",true);
#創(chuàng)建驅動
AndroidDriver<WebElement> androidDriver;
#找到頁面元素并操作頁面元素來模擬用戶操作
androidDriver.findElementByXPath("");
androidDriver.findElementById("");
#通過斷言和日志查看測試結果
Assert
元素定位
ID定位 resource-id
將相同ID值的元素放在集合匯總,再去通過集合的索引訪問;
androidDriver.findElementById(“”);
text定位
androidDriver.fiindElementByAndroidUIAutomator(“new UiSelector().text(\"長沙\")”);
MobileBy.AndroidUIAutomator("new UiSelector().className(\"android.widget.Button\").textMatches(\".*允許.*\")");
這里使用了原生AndroidUIAutomator;
XPath定位
xpath定位符
? 絕對定位: 不推薦
? 相對定位:
? //*
? //[contains(@resource-id, ‘login’)]
? //[@text=‘登錄’]
? //[contains(@resource-id, ‘login’) and contains(@text, ‘登錄’)]
? //[contains(@text, ‘登錄’) or contains(@label, ‘登錄’)]
? //[contains(@text, '看點')]/ancestor:://[contains(name(), ‘EditText’)]
? //[@clickable="true"]//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20]
?//android.view.View[@content-desc="您的職務"]/preceding-sibling::android.widget.EditText[1] # preceding-sibling 是找當前的前面的兄弟節(jié)點;
androidDriver.findeElementByXpath("相對路徑");
androidDriver.findeElementByXpath("http://android.widget.TextView[@text='我發(fā)起的']");
通過class屬性元素定位的話class在頁面有較多,通常不適用;可以通過結合Xpath定位,80%元素可以使用,APPIUM對XPath定位有了一定的優(yōu)化性能不用擔心;
accessibility id定位
在UIAutomator中么有這個屬性, 對應是content-desc屬性;

self.driver.find_element(MobileBy.ACCESSIBILITY_ID,'輸入11位手機號').click()
坐標定位
受屏幕尺寸/分辨率/DPPI影響,萬不得已不要用;
元素等待
元素加載時間不一致,會導致元素無法定位超時報錯;靈活定制定位元素等待時間;
強制等待
固定的等待時間
Thread.sleep();
隱式等待
針對全局元素設置等待時間
androidDriver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);
顯示等待
針對特定的某個元素,不可以對非元素;
WebDriverWait 與隱式等待不同的是不會一直等到元素出現(xiàn),顯示等待會在超過設定時間后拋出異常。
WebDriverWait webDriverWait = new WebDriverWait(androidDriver, 10);
WebElement webElement = webDriverWait.until(new ExpectedCondition<WebElement>() {
@NullableDecl
public WebElement apply(@NullableDecl WebDriver input) {
return androidDriver.findElementById("com.chinat.moa:id/sdl__negative_button");
}
});
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element =
wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));
手勢操作
? press : TouchAction().press(el0).moveTo(el1).release()
? release
? moveTo
? tap wait
? longPress
? cancel
? perform
手勢操作-滑動
java-client5.0之前提供了滑動API,單次滑動(下拉刷新)
public void refresh() {
//java-client 4.1.2
//void swipe(int startx, int starty, int endx, int endy, int duration) //duration滑動的時間;
androidDriver.swipe(356,594,356,794,800)
//java-client 6.1.0
TouchAction touchAction = new TouchAction(androidDriver);
//把原始的坐標轉換成PointOption類型的;
PointOption startPointOption = PointOption.point(356,594);
PointOption endPointOption = PointOption.point(356,794);
//把原始的時間轉換成Duration類型的;
Duration duration = Duration.ofMillis(800);
WaitOptions waitOptions = new WaitOptions().withDuration(duration);
touchAction.press(startPointOption).waitAction(waitOptions).press(endPointOption).release();
touchAction.perform();
}
手勢操作-九宮格解鎖
連續(xù)多次滑動(九宮格解鎖)
@Test
public void MultiSwipe() throws InterruptedException{
Thread.sleep(100);
//實例化TouchAction對象
TouchAction touchAction = new TouchAction(androidDriver);
//把原始的坐標轉換成PointOption類型的;
PointOption pointOption1 = PointOption.point(150,427);
PointOption pointOption2 = PointOption.point(362,427);
PointOption pointOption3 = PointOption.point(569,427);
PointOption pointOption4 = PointOption.point(356,625);
PointOption pointOption5 = PointOption.point(356,850);
PointOption pointOption6 = PointOption.point(356,850);
PointOption pointOption7 = PointOption.point(356,850);
//把原始的時間轉換成Duration類型的;
Duration duration = Duration.ofMillis(800);
WaitOptions waitOptions = new WaitOptions().withDuration(duration);
touchAction.press(pointOption1).moveTo(pointOption2).moveTo(pointOption3).moveTo(pointOption4).moveTo(pointOption5).moveTo(pointOption6).moveTo(pointOption7).release();
touchAction.perform();
}
手勢操作-多點觸摸
MultiTouchAction類可以模擬用戶多點觸摸操作;
主要包含add()/perform()兩個方法;
可以結合TouchAction類模擬多根手指的滑動效果;
原理介紹:
B->A同時C->D是放大效果,反之是縮小;

@Test
public void testMultiTouch() throws InterruptedException {
Thread.sleep(6000);
//1.實例化MultiTouchAction對象
MultiTouchAction multiTouchAction = new MultiTouchAction(androidDriver);
//2.實例化兩個TouchAction
TouchAction touchAction1 = new TouchAction<>(androidDriver);
TouchAction touchAction2 = new TouchAction<>(androidDriver);
//獲得當前屏幕的高寬;
int x = androidDriver.manage().window().getSize().getWidth();
int y = androidDriver.manage().window().getSize().getHeight();
//第一根手指的動作從B點滑動到A點;
touchAction1.press(PointOption.point(x * 4 / 10, y * 4 / 10)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(100)))
.moveTo(PointOption.point(x * 2 / 10, y * 2 / 10)).release();
//第一根手指的動作從B點滑動到A點;
touchAction2.press(PointOption.point(x * 6 / 10, y * 6 / 10)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(100)))
.moveTo(PointOption.point(x * 8 / 10, y * 8 / 10)).release();
//添加觸摸動作到MultiTouchAction
multiTouchAction.add(touchAction1).add(touchAction2);
multiTouchAction.perform();
}
APPIUM常用API
1.startActivity 實現(xiàn)頁面跳轉(包括APP內部頁面及APP相互跳轉)
//開啟某一個activity實現(xiàn)跳轉;
//首先我們創(chuàng)建activitiy對象,用Activity構建方法初始化,參數(shù)為對應的包名和類名;
//1.app內部跳轉;
Activity activity = new Activity("com.chinatower.moa", "com.ecology.view.MainActivity");
androidDriver.startActivity(activity);
//2.app相互跳轉,必須要是跳轉app的啟動入口;
Activity activityApp = new Activity("com.android.browser", "com.android.view.browser.BrowserView");
androidDriver.startActivity(activity);
2.getPageSource 得到當前頁面的dom結構;
可以用于斷言當前頁面是否有某個元素,或者判斷當前頁面有沒有產生變化:如上下滾動判斷是否已經到了底端/頂端;
String pageSource = androidDriver.getPageSource();
System.out.println(pageSource);
3.currentActivity() 獲得當前頁的類名;
String actual = androidDriver.currentActivity();
4.resetApp重置應用的數(shù)據(jù);
有些場景需要清除應用的數(shù)據(jù),相當于第一次安裝時候的狀態(tài),比如第一次啟動APP的引導頁、登錄等;
//重置應用數(shù)據(jù);
androidDriver.resetApp();
5.isAppInstalled判斷App是否安裝;
//獲取到應用是否安裝
androidDriver.isAppInstalled("com.android.browser");
6.pressKey 安卓平臺獨有,向系統(tǒng)發(fā)送鍵值事件,不同的鍵值對應不同的功能,如keyevent(4)標識手機的HOME按鍵;
//pressKey
KeyEvent keyEvent = new KeyEvent();
keyEvent.withKey(AndroidKey.HOME);
androidDriver.pressKey(keyEvent);
7.getScreenshotAs截圖功能,當測試用例執(zhí)行失敗之后進行屏幕截圖,保存到本地為了更好的查找問題;
//getScreenshotAs截圖功能
File file = androidDriver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(file,new File("D:\\test.png" ));
8.getDeviceTime獲取設備當前時間
//獲取設備當前時間
androidDriver.getDeviceTime();
- getDisplayDensity獲取設備DPI,不是分辨率
//獲取設備DPI
androidDriver.getDisplayDensity();
10.getAutomationName獲取當前自動化引擎
//獲取當前自動化引擎
androidDriver.getAutomationName();
11.getOrientation獲取設備的橫豎屏狀態(tài);
//獲取設備的橫豎屏狀態(tài);
androidDriver.getOrientation();
Toast元素獲取
- 獲取要求:Java-client 5.0+;使用UIAntomator2自動化引起;Android系統(tǒng)版本5.0+;
必須使?xpath查找
//*[@class='android.widget.Toast']
//*[contains(@text, "xxxxx")]
#獲取方式
By.xpath(“//*[contains(@text,'toast部分信息‘)]”)
常?功能
https://github.com/appium/appium/blob/master/docs/en/
writing-running-appium/appium-bindings.md
? 系統(tǒng)操作
? lock background hideKeyBoard openNotifications shake
? startActivity currentActivity getCurrentPackage
? app操作
? installApp removeApp isInstalled closeApp launchApp reset
getAppStrings
? getContextHandles getContext context
Hybrid自動化準備 俗稱H5
如何區(qū)分H5和原生頁面,定位中類為webview的是H5,打開開發(fā)者調試-UI布局原生頁面會有框框;
Hybrid自動化準備
Appium提供的解決方案基于UIAutomator + ChromeDriver;
準備:
- 準備android4.4+版本以上的手機、模擬器;
- 在app源碼中將webview調試模式打開;
- webview.setWebContentsDebuggingEnabled(true);
- 安裝UC開發(fā)者工具;uc-devtools工具
設置為本地資源,鏈接上手機活模擬器后,打開一個包含H5頁面的APP,HOME中即可有inspect檢測到;
image.png
image.png
5.開啟手機開發(fā)者模式-打開布局,若是原生頁面會有很多框;
原生頁面:image.png
H5頁面:image.png
線上App開啟WebView調試(root)
如果是第三方線上APP,一般webview debug開關都是關閉的,這個就需要借助第三方工具才能打開;
解決方案:
Xposed+WebviewDebugHook
Xposed是一個框架,能夠集成很多功能模塊,這些模塊能夠在不修改APK的情況下,修改APP的運行方式,將Xposed安裝到手機,下載對應的x86活arm框架;
然后安裝WebviewDebugHook,勾選Xposed模塊中WebviewDebugHook激活,通過模塊來開啟APP的WebView debug模式;

上下文
- 獲取所有的contexts
driver.getContextHandles(); - 切換到webview視圖
driver.context(webview視圖) - 定位webview中的元素,并執(zhí)行操作
web網(wǎng)頁元素定位和操作 - 切換回默認的視圖
driver.context(nativer視圖) -
示例
image.png
- 常見錯誤chromedriver和Chrome 版本不符,下載對應的版本chromedriver 替換appium的目錄下的即可;
-
報錯截圖:
image.png -
可在git查詢chromedriver和Chrome 版本,淘寶查詢支持版本;
image.png

-
替換appium目錄下的chromedriver:
image.png
非root設備開啟線上APP的Webview調試
安卓VitualXposed+WebviewDebugHook后,選擇對應應用;
adb install C:\Users\Tower\Documents\VirtualXposed_AOSP_0.17.3.apk
adb install C:\Users\Tower\Documents\WebViewDebugHook.apk
APPIUM自動化原理解析

Appium Android?動化流程分析
appium -g <log file path>
- Appium Log
? 清晰記錄了所有的請求和結果 - getPageSource
? 界?的完整dom結構. xml?件 - 腳本內調試
? 利?xpath獲取所有匹配的元素
? driver.findElementsByXPath(“//*")







