前言:
用appium做UI自動化,測試APP里面的H5和測試手機瀏覽器打開的H5的操作流程上是有所區別的。比如要測試APP內嵌的H5需要先操作appium啟動APP,然后通過context切到webview模式,才能操作H5頁面,但是如果測試手機網頁的話就比較簡單了,設置好瀏覽器比如選擇Chrome,直接訪問網址就好了。
------------------------------------------------# 增加知識簡介
原生應用程序:(Native App)
原生APP是什么?原生APP就是利用Android、iOS平台官方的開發語言、開發類庫、工具進行開發。比如安卓的java語言,iOS的object-c 語言。在應用性能上和交互體驗上應該是最好的。
優點:
1、可訪問手機所有功能、可實現功能最齊全;
2、運行速度快、性能高,絕佳的用戶體驗;
3、支持大量圖形和動畫。不卡,反應快。
4、比較快捷地使用設備端提供的接口,處理速度上有優勢。
缺點:
1.在過去主要是成本高、周期長,Android和iOS都需要單獨開發。
2.更新版本需要重新下載安裝包。
混合應用程序(Hybrid App)
Hybrid APP指的是半原生半Web的混合類App。需要下載安裝,看上去類似Native App,但只有很少的UI Web View,訪問的內容是 Web ,即利用了原生APP的開發技術還應用了HTML5開發技術,是原生和HTML5技術的混合應用。混合比例不限。
例如Store里的新聞類APP,視頻類APP普遍采取的是Native的框架,Web的內容。
Hybrid App 極力去打造類似於Native App 的體驗,但仍受限於技術,網速,等等很多因素。尚不完美。
優點:
1、開發周期短;
2、功能更新發布快;
缺點:
1、用戶體驗不如本地應用;
2、性能稍慢(需要連接網絡);
Web版APP (Web App)
本質就是瀏覽器功能的疊加,用普通Web開發語言開發的,通過瀏覽器運行。
優勢:
1、支持范圍廣;
2、開發成本低、周期短。
缺點:
1、對聯網要求高,離線不能做任何操作;
2、功能有限;
3、運行速度慢,頁面不能承載太多東西;
4、圖片和動畫支持性不高;
5、如果用戶使用更多的新型瀏覽器,那么就會出現運行問題。
Web App、Hybrid App、Native App 技術特性
APP內嵌H5:
如何判斷APP里面是否有H5呢, 使用uiautomaterview的時候發現頁面元素含有webview,這就說明這個APP是混合應用,那么如何測試這個APP內部的H5頁呢?
准備工作:
移動端
在eclipse/IDEA 中配置AndroidSDK環境(Android6.0、ADT23.0)
將手機與PC通過USB連接,開啟USB調試模式;
使用360手機助手或在dos窗口輸入adb devices查看手機驅動連接是否成功;
PC端
在Chrome瀏覽器地址欄輸入chrome://inspect,進入調試模式;
此時頁面顯示了手機型號、驅動名稱、APP要調試的WebView名稱;
點擊inspect,若成功加載與APP端相同界面的調試頁面,則配置成功;
若獲取不到WebView或者調試頁面預覽框顯示空白,則需要進行VPN破解–安裝翻牆軟件(由於默認的DevTools使用的是appspot服務器,這在國內是需要翻越GWF)
原理:
Appium通過 chromedriver-port 9515端口進行通信,驅動安卓手機上的WebView;
查看手機系統應用Android System WebView顯示的Chrome版本,下載與之對應的chromedriver並添加到Appium安裝目錄下的chromedriver目錄,保證驅動程序版本對應,Appium后台啟動時會自動重啟chromedriver,此時后台不會出現等待chromedriver啟動現象;
//殺掉chromedriver進程並重啟,要先切換到NATIVE_APP(包括微信端) public static void RestartChromedriver() throws Exception{ Runtime.getRuntime().exec("taskkill /F /im chromedriver.exe"); System.setProperty("webdriver.chrome.driver", "D:\\Appium\\node_modules\\appium\\node_modules\\appium-chromedriver\\chromedriver\\win\\chromedriver.exe"); }
如果端口被占用了,先停掉端口應用,再啟動chromedriver
Appium切換context、切換webview

import java.util.Set; import java.util.concurrent.TimeUnit; import org.openqa.selenium.WebElement; import com.tms.app.itms.logs.Log; import io.appium.java_client.android.AndroidDriver; public class ITMS_GetElement{ public static void getContextHandle(AndroidDriver<WebElement> driver) { Set<String> context = null ; for(int i=1;i<=20;i++){ context = driver.getContextHandles(); for(String contextName : context) { System.out.println(contextName);//打印當前上下文 if(contextName!=null && contextName.contains("WEBVIEW_com.quantum.Tmsp7")||contextName.contains("WEBVIEW_com.tencent.mm:tools")){ switchTo_WEBVIEW(driver); driver.getPageSource(); return; } if(i==20) assert false; } Log.goSleep(1); } } public static void switchTo_WEBVIEW(AndroidDriver<WebElement> driver) { String str = driver.currentActivity();//檢查當前APP for(int k=0;k<30;k++){ try { if(str.equals(".MainActivity")){ driver.context("WEBVIEW_com.quantum.Tmsp7"); return; }else if(str.equals(".plugin.webview.ui.tools.WebViewUI")){ driver.context("WEBVIEW_com.tencent.mm:tools"); return; } } catch (Exception e) { if(k<10){ Log.info("switch..."); }if(k==30){ Log.fatal(driver, "switch fail!", e); } } finally{ driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS); } } } }
切換到webdriver之后,測試腳本類似Selenium,后面就可以像測試WEBUI一樣寫腳本了(注意: 此時的appium方法將無法使用!!!)
//loginSubmit ITMS_GetElement.getContextHandle(driver); driver.findElementById("username").sendKeys("15029200344"); driver.findElementById("password").sendKeys("111111"); driver.findElementByCssSelector("#loginSubmit").click(); // switchTo_NATIVE 獲取當前地理位置——檢查[允許]按鈕 ITMS_GetElement.getAlertTitleNewThread(driver);//小米、華為 Thread.sleep(3000); ITMS_GetElement.switchTo_WEBVIEW(driver); driver.quit();
項目實戰中我發現各位可能遇到如下問題:
1. 從1個webview頁面跳轉到另外一個webview頁面,此時即便你切換到了WEBVIEW也無法操作頁面元素
解決方法: 切換WEBVIEW的標題 ,通過如下方法可以確保你識別當前的webview頁面元素
public static void switchToWindowTitle(String windowTitle) { try { String currentHandle = driver.getWindowHandle(); Set<String> handles = driver.getWindowHandles(); for (String s : handles) { if (s.equals(currentHandle)) continue; else { driver.switchTo().window(s); if (driver.getTitle().contains(windowTitle)) { Log.logInfo("Switch to window: " + windowTitle + " successfully!"); break; } else continue; } } } catch (NoSuchWindowException e) { Log.logInfo("Window: " + windowTitle + " cound not found!" + e.fillInStackTrace()); } }
直接測試瀏覽器:
這一塊沒什么難度,主要是通過appium連接真機,然后啟動你要測試的瀏覽器,后面的測試用例編寫就和Selenium 一樣了
模塊的主要作用就是連接真機,所需的參數我沒有直接寫死在代碼里面而是通過配置文件讀取到程序里面,這個是為了后面部署到jenkins上面方便配置,具體可以參見工程里面的MyConfig.prop文件。

package testdriver; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.ios.IOSDriver; import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.DesiredCapabilities; import testdata.GlobalVars; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Properties; public class BaseDriver { public static WebDriver driver; /** * 啟動driver * @return */ public void startDriver() throws MalformedURLException { /** * 初始化appium webdriver */ initializeTestData(); DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability("deviceName", GlobalVars.DEVICE_NAME); capabilities.setCapability("browserName", GlobalVars.BROWER_NAME); capabilities.setCapability("platformVersion", GlobalVars.PLATFORM_VERSION); capabilities.setCapability("platformName", GlobalVars.PLATFORM_NAME ); capabilities.setCapability("noReset", GlobalVars.NO_RESET); capabilities.setCapability("unicodeKeyboard", true); capabilities.setCapability("resetKeyboard", true); if (GlobalVars.PLATFORM_NAME.contains("iOS")){ capabilities.setCapability("udid", GlobalVars.UDID); driver = new IOSDriver(new URL(GlobalVars.APPIUM_SERVER), capabilities); }else{ driver = new AndroidDriver(new URL(GlobalVars.APPIUM_SERVER), capabilities); } driver.get(GlobalVars.TEST_URL); } /** * 退出driver */ public void stopDriver(){ driver.quit(); } /** * 初始化測試環境數據 */ private void initializeTestData(){ Properties prop = new Properties(); try{ //讀取屬性文件a.properties InputStream in = new BufferedInputStream(new FileInputStream("MyConfig.prop")); ///加載屬性列表 prop.load(in); GlobalVars.DEVICE_NAME = prop.getProperty("device_name"); GlobalVars.PLATFORM_NAME = prop.getProperty("platform_name"); GlobalVars.PLATFORM_VERSION = prop.getProperty("platform_version"); GlobalVars.UDID = prop.getProperty("udid"); GlobalVars.BROWER_NAME = prop.getProperty("brower_name"); GlobalVars.APPIUM_SERVER = prop.getProperty("appium_server"); GlobalVars.NO_RESET = prop.getProperty("no_reset").equals("true")?true:false; GlobalVars.TEST_URL = prop.getProperty("test_url"); in.close(); } catch(Exception e){ System.out.println(e); } } }
配置文件:
device_name=i platform_name=Android platform_version=7.0 brower_name=chrome udid=827dc51fd4adcc5234164e581f63bcba11547923 appium_server=http://127.0.0.1:4723/wd/hub no_reset=true test_url=http://m.baidu.com
PageHomeElms主要功能是對頁面元素的初始化並提供頁面元素的對象。 初始化元素用到了兩種方式,
首先該類初始化的時候回通過PageFactory模式定位到當前頁面已經加載出來並且被@FindBy標記的元素,
如果元素是后加載的或者PageFactory沒有定位到的話后面還可以通過webdriver的findElement方法定位

package pageobjects.pages; import pageobjects.baseclass.PageObjectBase; import pageobjects.pageselements.PageHomeElms; public class PageHome extends PageObjectBase { private PageHomeElms homePageElms = new PageHomeElms(); /** * home頁面的無參構造函數,初始化一下頁面元素 */ public PageHome(){ //頁面元素初始化,只訂單當前頁面已經加載完的元素 homePageElms = homePageElms.initElements(); } public PageHome inputSearchWord(String keyword){ input(homePageElms.getSearchBox(),keyword); return this; } public PageHome tapSearchBtn(){ tap(homePageElms.getIndexBtn()); return this; } }
PageHome的主要功能是向testcase提供頁面的各種操作,比如頁面的滑動,元素的點擊,是否可見等。

package pageobjects.pageselements; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import pageobjects.baseclass.ElementsBase; /** * Home頁面的元素 */ public class PageHomeElms extends ElementsBase { //搜索輸入框 public static final String xpathSearchBox = "//*[@id=\"index-kw\"]"; public static final By locatorSearchBox = new By.ByXPath(xpathSearchBox); @FindBy(xpath = xpathSearchBox) private WebElement searchBox; //"百度一下"button,以下列舉了三種xpath方式獲取元素 //public static final String xpathIndexBtn = "//*[@class=\"se-bn\"]"; //public static final String xpathIndexBtn = "//*[@id=\"index-bn\"]"; public static final String xpathIndexBtn = "//button[contains(text(),'百度一下')]"; public static final By locatorIndexBtn = new By.ByXPath(xpathIndexBtn); @FindBy(xpath = xpathIndexBtn) private WebElement indexBtn; /** * 初始化元素 * @return 當前頁面對象 */ public PageHomeElms initElements(){ //通過PageFactory工具加載當前頁面已有元素,未加載的元素后續可以通過定位器單獨找到 return locatePgElms(PageHomeElms.class); } /** * 獲取元素 SearchBox, 如果PageFactory方式沒有定位到該元素, * 則通過傳統的方式獲取 * @return */ public WebElement getSearchBox(){ if (searchBox == null){ return locateElement(locatorSearchBox); }else{ return searchBox; } } /** * 獲取元素 IndexBtn, 如果PageFactory方式沒有定位到該元素, * 則通過傳統的方式獲取 * @return */ public WebElement getIndexBtn(){ if (indexBtn == null){ return locateElement(locatorIndexBtn); }else{ return indexBtn; } } }
測試用例:
package testcases; import org.testng.annotations.*; import pageobjects.baseclass.PgNavigator; import testdriver.BaseDriver; import java.net.MalformedURLException; public class TestCaseBase { public BaseDriver baseDriver = new BaseDriver(); public PgNavigator pgNavigator; @BeforeSuite public void beforeSuit() throws MalformedURLException { //整個測試庫運行期間只啟動一次driver baseDriver.startDriver(); } @BeforeClass public void beforeClass(){ } @BeforeMethod public void beforeTest(){ //每個測試方法運行前清空一下頁面池 pgNavigator = new PgNavigator(); } @AfterMethod public void afterTest(){ } @AfterClass public void afterClass(){ } @AfterSuite public void afterSuit(){ baseDriver.stopDriver(); } }
詳細可參考:https://blog.csdn.net/armarm9/article/details/82111278