常常咱们会以为App自动化不靠谱,不稳定,其中很大的两个缘由是:java
- App启动加载时间较久(可能App自己加载慢,可能移动设备自己加载应用速度慢,也可能首页广告时间较长)。
- 各类弹框的出现;广告弹框,升级弹框,评价弹框等。
例如以下雪球App出现的几种弹框: android
List
中,遍历List
,经过findElements
方法获得的List
大小来判断弹框元素是否存在,存在即点击处理public static void handleAlert(){
List<By> alertBox = new ArrayList<>();
alertBox.add(By.id("ib_close")); //广告弹框
alertBox.add(By.id("md_buttonDefaultNegative")); //评价弹框
alertBox.forEach(alert->{
By adsLocator = alert;
List<WebElement> ads = driver.findElements(adsLocator);
if (ads.size() >= 1) {
ads.get(0).click();
}
});
}
复制代码
handleAlert()
方法加到driver.findElement
方法以前,使定位前先判断弹框的存在与否并进行处理public static WebElement findElement(By by) {
System.out.println(by);
handleAlert();
return driver.findElement(by);
}
复制代码
上述方法初步解决了弹框问题,可是缺点也很明显: 缺点:每次定位元素前都须要处理弹框,影响执行效率,速度较慢 所以咱们引入try catch
来解决此问题try catch
的异常捕获处理的机制,让元素仅在定位失败时才进入弹框处理handleAlert()
方法,处理完毕后从新返回driver.findElement(by)
,对原case
元素继续进行定位执行;这样就大大提高了处理效率,使处理更为精准public static WebElement findElement(By by) {
try {
System.out.println(by);
return driver.findElement(by);
} catch (Exception e) {
System.out.println("进入弹框处理");
handleAlert();
return driver.findElement(by);
}
}
复制代码
findElement
方法自身,继续进行try catch
,使之进入弹框处理逻辑public static WebElement findElement(By by) {
try {
System.out.println(by);
return driver.findElement(by);
} catch (Exception e) {
System.out.println("进入弹框处理");
handleAlert();
return findElement(by);
}
}
复制代码
注意: 使用递归方法后有一个问题,就是假如并非由于某个弹框的出现而致使的定位失败,而这个时候经过try catch
进入到弹框处理逻辑后,因为并未匹配到弹框元素,因此递归就会进入一个死循环,不断重复着弹框处理的逻辑,因此使用递归时咱们也须要对其次数进行限制;通常两个弹框同时出现已经算多的了,因此建议能够将递归的次数限制到最多两次便退出。static int i = 1;
public static WebElement findElement(By by) {
try {
System.out.println(by);
return driver.findElement(by);
} catch (Exception e) {
if (i > 2){ //设置最多递归两次
i = 1;
return driver.findElement(by);
}
System.out.println("进入弹框处理第"+i+"次");
handleAlert();
i++;
return findElement(by); //最后调用自身完成递归,防止多弹框同时出现形成定位失败
}
}
复制代码
按照上面的方法,看似已经很好的解决了弹框的处理,可是能够注意到的是:app
而咱们实际中最想要的也是最有效率的方法应该是:框架
PageSource
了。1)appium
的driver
提供了一个getPageSource
方法,此方法能够在当前页面能够获得一个文本字符串,也能够理解为当前页面的xml
,咱们利用这种xml
文原本进行判断,就比用appium
一必定位的方式要快速和精准的多了测试
String pageSource = driver.getPageSource();
复制代码
2)设置黑名单,黑名单要使用元素的xpath
,用来和PageSource
文本作匹配,判断此弹框是否存在当前页面lua
String adBox = "com.xueqiu.android:id/ib_close";
String gesturePromptBox = "com.xueqiu.android:id/snb_tip_text";
String evaluateBox = "com.xueqiu.android:id/md_buttonDefaultNegative";
复制代码
3)咱们将黑名单中的xpath
做为弹框查找的标记符
,另外再定义一个定位符
,用来找到弹框后作定位处理使用;这里能够将标记符
和定位符
存入HashMap
中spa
HashMap<String,By> map = new HashMap<>();
map.put(adBox,By.id("ib_close"));
map.put(gesturePromptBox,By.id("snb_tip_text"));
map.put(evaluateBox,By.id("md_buttonDefaultNegative"));
复制代码
4)遍历map
,判断黑名单弹框元素是否存在于当前pageSource
,存在即根据弹框处理方式进行点击或其余操做(如上述中的新功能提示弹框,点击弹框自身没法消除,需点击页面其他部分方可消除)处理 code
map.entrySet().forEach(entry ->{
if (pageSource.contains(entry.getKey())){
if (entry.getKey().equals("com.xueqiu.android:id/snb_tip_text")){
System.out.println("gesturePromptBox found");
Dimension size = driver.manage().window().getSize();
//点击屏幕的中心位置,消除新功能提示弹框
new TouchAction<>(driver).tap(PointOption.point(size.width/2,size.height/2)).perform();
}else {
//其他弹框直接点击消除
driver.findElement(entry.getValue()).click();
}
}
});
复制代码
5)最后有一个小技巧,临时修改隐式等待时间,防止查找黑名单中元素太久,在弹框处理结束后再讲隐式等待时间复原;完整的方法实现以下:orm
//不少弹框的话,最好的是直接定位到到底哪一个弹框在界面上,元素的判断使用xpath
public static void handleAlertByPageSource(){
String pageSource = driver.getPageSource();//能够获得一个文本字符串,也能够理解为当前页面的xml
//黑名单
String adBox = "com.xueqiu.android:id/ib_close";
String gesturePromptBox = "com.xueqiu.android:id/snb_tip_text";
String evaluateBox = "com.xueqiu.android:id/md_buttonDefaultNegative";
//将标记和定位符存入map
HashMap<String,By> map = new HashMap<>();
map.put(adBox,By.id("ib_close"));
map.put(gesturePromptBox,By.id("snb_tip_text"));
map.put(evaluateBox,By.id("md_buttonDefaultNegative"));
//临时修改隐式等待时间,防止查找黑名单中元素太久
driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
//遍历map,判断黑名单弹框元素是否存在于当前pageSource,存在即点击处理
map.entrySet().forEach(entry ->{
if (pageSource.contains(entry.getKey())){
if (entry.getKey().equals("com.xueqiu.android:id/snb_tip_text")){
System.out.println("gesturePromptBox found");
Dimension size = driver.manage().window().getSize();
new TouchAction<>(driver).tap(PointOption.point(size.width/2,size.height/2)).perform();
}else {
driver.findElement(entry.getValue()).click();
}
}
});
//判断完成后将隐式等待时间恢复
driver.manage().timeouts().implicitlyWait(8,TimeUnit.SECONDS);
}
复制代码
6)最后将findElement
方法中的handleAlert
方法替换为handleAlertByPageSource
方法便可cdn
static int i = 1;
public static WebElement findElement(By by) {
try {
System.out.println(by);
return driver.findElement(by);
} catch (Exception e) {
if (i > 2){ //设置最多递归两次
i = 1;
return driver.findElement(by);
}
System.out.println("进入弹框处理第"+i+"次");
handleAlertByPageSource();
i++;
return findElement(by); //最后调用自身完成递归,防止多弹框同时出现形成定位失败
}
}
复制代码
在第一部分中介绍的首页加载时候可能出现的坑: App启动加载时间较久(可能App自己加载慢,可能移动设备自己加载应用速度慢,也可能首页广告时间较长);致使定位超时,用例失败。 对此咱们又以下两步解决办法
如标题所述,对首页进入使用显示等待,利用搜索控件的出现来判断是否进入了首页,这样不影响其余元素隐式等待的时间,也解决了首页初始化加载时间过长的问题;例如雪球仅在进入首页后会出现id
为user_profile_container
的用户信息控件,那么咱们就能够以此为依据来判断应用是否加载完成进入了首页。
new WebDriverWait(driver,30)
.until(ExpectedConditions.visibilityOfElementLocated(By.id("user_profile_container")));
复制代码
缺点: 可是这样有个状况不能解决:若加载完成后有弹框出现,可能就一直没法定位到首页元素,可是实际上已经加载完成,好比下图的首页广告弹框
文章第二部分介绍了利用PageSource
来判断弹框是否存在的方法,在这里依然适用,仍是熟悉的味道,仍是一样的套路,将弹框元素xpath也加入PageSource判断,这样不管首页控件和首页弹框哪个被发现,就均可以判断应用已经加载完成,成功进入首页,剩下的就能够交给用例和其余处理逻辑了
new WebDriverWait(driver,30)
.until(x ->{
String xml = driver.getPageSource();
Boolean checkResult = xml.contains("user_profile_container") || xml.contains("com.xueqiu.android:id/ib_close");
System.out.println("主页元素查找的结果是:" + checkResult);
return checkResult;
});
复制代码