前言:
用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