经常我们会觉得App自动化不靠谱,不稳定,其中很大的两个原因是:
- App启动加载时间较久(可能App本身加载慢,可能移动设备本身加载应用速度慢,也可能首页广告时间较长)。
- 各种弹框的出现;广告弹框,升级弹框,评价弹框等。
例如如下雪球App出现的几种弹框:
在框架中若不对上述情况做处理就可能出现元素定位超时找不到的报错,自动化也就被打断终止了,使我们刚燃起的自动化热情瞬间被扑灭,从入门到完全放弃就这样造成了。
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); //最后调用自身完成递归,防止多弹框同时出现造成定位失败
}
}
按照上面的方法,看似已经很好的解决了弹框的处理,但是可以注意到的是:
而我们实际中最想要的也是最有效率的方法应该是:
PageSource
了。1)appium
的driver
提供了一个getPageSource
方法,此方法可以在当前页面可以得到一个文本字符串,也可以理解为当前页面的xml
,我们利用这种xml
文本来进行判断,就比用appium
一一定位的方式要快速和精准的多了
String pageSource = driver.getPageSource();
2)设置黑名单,黑名单要使用元素的xpath
,用来和PageSource
文本做匹配,判断此弹框是否存在当前页面
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
中
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
,存在即根据弹框处理方式进行点击或其他操作(如上述中的新功能提示弹框,点击弹框自身无法消除,需点击页面其余部分方可消除)处理
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)最后有一个小技巧,临时修改隐式等待时间,防止查找黑名单中元素过久,在弹框处理结束后再讲隐式等待时间复原;完整的方法实现如下:
//很多弹框的话,最好的是直接定位到到底哪个弹框在界面上,元素的判断使用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
方法即可
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;
});