• 干货 | 解决 App 自动化测试的常见痛点(弹框及首页启动加载完成判断处理)


    App 自动化测试中有些常见痛点问题,如果框架不能很好的处理,就可能出现元素定位超时找不到的情况,自动化也就被打断终止了。很容易打消做自动化的热情,导致从入门到放弃。比如下面的两个问题:

    一是 App 启动加载时间较久(可能 App 本身加载慢,可能移动设备本身加载应用速度慢,也可能首页广告时间较长)。

    另一个是各种弹框的出现,广告弹框,升级弹框,评价弹框等。在框架中如果不能处理好上面的情况,

    以雪球 App 出现的几种弹框举例:

    弹框一:

    弹框二:

    弹框三:

    • 弹框的影响范围

    • 弹框对我们自动化的影响主要是用例执行的打断,而至于弹框中广告内容的跳转或评价信息填写等属于另外的测试,因此我们主要是要将弹框处理消失,使应用回到用例执行的 PO;

    • 弹框的消失方式

    • 观察弹框,我们会发现一般为了保证用户体验,弹框都会方便用户进行一键消除,例如上述中雪球的各种弹框,可能点击一个叉号,可能任意点击其他地方,或者评价框这种直接点击“下次再说”等。

    • 弹框的处理效果

    • 自动化执行的任何时候,任意的弹框都可能出现,在这个时候用例不能失败,需要将对应的弹框正确处理后继续执行原用例,原用例的执行过程不受影响。

    1. 将需要处理的弹框元素加入到一个黑名单List中,遍历List,通过findElements方法得到的List大小来判断弹框元素是否存在,存在即点击处理
    1. public static void handleAlert(){
    2. List<By> alertBox = new ArrayList<>();
    3. alertBox.add(By.id("ib_close")); //广告弹框
    4. alertBox.add(By.id("md_buttonDefaultNegative")); //评价弹框
    5. alertBox.forEach(alert->{
    6. By adsLocator = alert;
    7. List<WebElement> ads = driver.findElements(adsLocator);
    8. if (ads.size() >= 1) {
    9. ads.get(0).click();
    10. }
    11. });
    12. }
    1. 将handleAlert()方法加到driver.findElement方法之前,使定位前先判断弹框的存在与否并进行处理
    1. public static WebElement findElement(By by) {
    2. System.out.println(by);
    3. handleAlert();
    4. return driver.findElement(by);
    5. }

    上述方法初步解决了弹框问题,但是缺点也很明显。

    缺点:每次定位元素前都需要处理弹框,影响执行效率,速度较慢 因此我们引入try catch来解决此问题

    我们利用try catch的异常捕获处理的机制,让元素仅在定位失败时才进入弹框处理handleAlert()方法,处理完毕后重新返回driver.findElement(by),对原case元素继续进行定位执行;这样就大大提升了处理效率,使处理更为精准。

    1. public static WebElement findElement(By by) {
    2. try {
    3. System.out.println(by);
    4. return driver.findElement(by);
    5. } catch (Exception e) {
    6. System.out.println("进入弹框处理");
    7. handleAlert();
    8. return driver.findElement(by);
    9. }
    10. }

    递归处理:
    一般情况下我们一次只会出现一个弹框,但是例外的是可能有一个以上的弹框同时出现,这样的话虽然处理了其中一个弹框,但是剩下的弹框依然会阻断用例的正常执行,这个时候就可以使用递归的方法,在处理完弹框后返回findElement方法自身,继续进行try catch,使之进入弹框处理逻辑

    1. public static WebElement findElement(By by) {
    2. try {
    3. System.out.println(by);
    4. return driver.findElement(by);
    5. } catch (Exception e) {
    6. System.out.println("进入弹框处理");
    7. handleAlert();
    8. return findElement(by);
    9. }
    10. }

    注意:
    使用递归方法后有一个问题,就是假如并不是因为某个弹框的出现而导致的定位失败,而这个时候通过try catch进入到弹框处理逻辑后,由于并未匹配到弹框元素,所以递归就会进入一个死循环,不断重复着弹框处理的逻辑,所以使用递归时我们也需要对其次数进行限制;一般两个弹框同时出现已经算多的了,所以建议可以将递归的次数限制到最多两次便退出。

    1. static int i = 1;
    2. public static WebElement findElement(By by) {
    3. try {
    4. System.out.println(by);
    5. return driver.findElement(by);
    6. } catch (Exception e) {
    7. if (i > 2){ //设置最多递归两次
    8. i = 1;
    9. return driver.findElement(by);
    10. }
    11. System.out.println("进入弹框处理第"+i+"次");
    12. handleAlert();
    13. i++;
    14. return findElement(by); //最后调用自身完成递归,防止多弹框同时出现造成定位失败
    15. }
    16. }

    按照上面的方法,看似已经很好的解决了弹框的处理,但是可以注意到的是:

    • 在检查弹框的时候依然使用的是appium的定位,在当前页面中根据元素的属性去一一查找定位
    • 所有的黑名单中的弹框都会被定位查找一遍

    而我们实际中最想要的也是最有效率的方法应该是:

    • 只有在当前页面中存在的弹框才对其进行定位、操作、处理。为了达到我们想要的效果,就需要借助于PageSource了。

    1)appium的driver提供了一个getPageSource方法,此方法可以在当前页面可以得到一个文本字符串,也可以理解为当前页面的xml,我们利用这种xml文本来进行判断,就比用appium一一定位的方式要快速和精准的多了

    1. String pageSource = driver.getPageSource();

    2)设置黑名单,黑名单要使用元素的xpath,用来和PageSource文本做匹配,判断此弹框是否存在当前页面

    1. String adBox = "com.xueqiu.android:id/ib_close";
    2. String gesturePromptBox = "com.xueqiu.android:id/snb_tip_text";
    3. String evaluateBox = "com.xueqiu.android:id/md_buttonDefaultNegative";
    1. HashMap<String,By> map = new HashMap<>();
    2. map.put(adBox,By.id("ib_close"));
    3. map.put(gesturePromptBox,By.id("snb_tip_text"));
    4. map.put(evaluateBox,By.id("md_buttonDefaultNegative"));

    4)遍历map,判断黑名单弹框元素是否存在于当前pageSource,存在即根据弹框处理方式进行点击或其他操作(如上述中的新功能提示弹框,点击弹框自身无法消除,需点击页面其余部分方可消除)处理

    1. map.entrySet().forEach(entry ->{
    2. if (pageSource.contains(entry.getKey())){
    3. if (entry.getKey().equals("com.xueqiu.android:id/snb_tip_text")){
    4. System.out.println("gesturePromptBox found");
    5. Dimension size = driver.manage().window().getSize();
    6. //点击屏幕的中心位置,消除新功能提示弹框
    7. new TouchAction<>(driver).tap(PointOption.point(size.width/2,size.height/2)).perform();
    8. }else {
    9. //其余弹框直接点击消除
    10. driver.findElement(entry.getValue()).click();
    11. }
    12. }
    13. });
    1. //很多弹框的话,最好的是直接定位到到底哪个弹框在界面上,元素的判断使用xpath
    2. public static void handleAlertByPageSource(){
    3. String pageSource = driver.getPageSource();//可以得到一个文本字符串,也可以理解为当前页面的xml
    4. //黑名单
    5. String adBox = "com.xueqiu.android:id/ib_close";
    6. String gesturePromptBox = "com.xueqiu.android:id/snb_tip_text";
    7. String evaluateBox = "com.xueqiu.android:id/md_buttonDefaultNegative";
    8. //将标记和定位符存入map
    9. HashMap<String,By> map = new HashMap<>();
    10. map.put(adBox,By.id("ib_close"));
    11. map.put(gesturePromptBox,By.id("snb_tip_text"));
    12. map.put(evaluateBox,By.id("md_buttonDefaultNegative"));
    13. //临时修改隐式等待时间,防止查找黑名单中元素过久
    14. driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
    15. //遍历map,判断黑名单弹框元素是否存在于当前pageSource,存在即点击处理
    16. map.entrySet().forEach(entry ->{
    17. if (pageSource.contains(entry.getKey())){
    18. if (entry.getKey().equals("com.xueqiu.android:id/snb_tip_text")){
    19. System.out.println("gesturePromptBox found");
    20. Dimension size = driver.manage().window().getSize();
    21. new TouchAction<>(driver).tap(PointOption.point(size.width/2,size.height/2)).perform();
    22. }else {
    23. driver.findElement(entry.getValue()).click();
    24. }
    25. }
    26. });
    27. //判断完成后将隐式等待时间恢复
    28. driver.manage().timeouts().implicitlyWait(8,TimeUnit.SECONDS);
    29. }

    6)最后将findElement方法中的handleAlert方法替换为handleAlertByPageSource方法即可

    1. static int i = 1;
    2. public static WebElement findElement(By by) {
    3. try {
    4. System.out.println(by);
    5. return driver.findElement(by);
    6. } catch (Exception e) {
    7. if (i > 2){ //设置最多递归两次
    8. i = 1;
    9. return driver.findElement(by);
    10. }
    11. System.out.println("进入弹框处理第"+i+"次");
    12. handleAlertByPageSource();
    13. i++;
    14. return findElement(by); //最后调用自身完成递归,防止多弹框同时出现造成定位失败
    15. }
    16. }

    再来解决首页加载时可能出现的坑。

    App 启动加载时间较久(可能 App 本身加载慢,也可能是移动设备本身加载应用速度慢,也可能首页广告时间较长),导致定位超时,用例失败。对此我们又如下两步解决办法。

    如标题所述,对首页进入使用显示等待,利用搜索控件的出现来判断是否进入了首页,这样不影响其他元素隐式等待的时间,也解决了首页初始化加载时间过长的问题。

    例如雪球仅在进入首页后会出现 id为user_profile_container的用户信息控件,那么我们就可以以此为依据来判断应用是否加载完成进入了首页。

    在启动方法中加入显示等待上述首页控件 30 秒,到控件可被定位时确认进入首页。

    1. new WebDriverWait(driver,30)
    2. .until(ExpectedConditions.visibilityOfElementLocated(By.id("user_profile_container")));

    缺点:
    但是这样有个情况不能解决:若加载完成后有弹框出现,可能就一直无法定位到首页元素,但是实际上已经加载完成,比如下图的首页广告弹框 。

    文章第二部分介绍了利用 PageSource 来判断弹框是否存在的方法,在这里依然适用,还是熟悉的味道,还是同样的套路,将弹框元素 xpath 也加入 PageSource 判断,这样无论首页控件和首页弹框哪一个被发现,就都可以判断应用已经加载完成,成功进入首页,剩下的就可以交给用例和其他处理逻辑了

    1. new WebDriverWait(driver,30)
    2. .until(x ->{
    3. String xml = driver.getPageSource();
    4. Boolean checkResult = xml.contains("user_profile_container") || xml.contains("com.xueqiu.android:id/ib_close");
    5. System.out.println("主页元素查找的结果是:" + checkResult);
    6. return checkResult;
    7. });

    好了,经过上面的分析之后,我们终于搞定了入门 APP 自动化测试时的老大难问题。搞定了弹框及首页启动时加载完成如何判断处理。

    技能学习,站在高手/过来人的肩膀上,才能高效进阶。

    对于想系统进阶提升测试开发技能的同学,推荐霍格沃兹测试学院出品的 《测试开发从入门到高级实战》系统进阶班课程。

    5 个月由浅入深,强化集训,测试大咖思寒领衔亲授,通过 8+ 企业级项目实战演练,带你一站式掌握 BAT 测试开发工程师必备核心技能(对标阿里巴巴P6+,挑战年薪50W+)!学员直推 BAT 名企测试经理,普遍涨薪 50%+!

  • 相关阅读:
    经纬高到北东天的坐标相互转换matlab
    【java】五道经典算法题
    C++的流和文件介绍
    JVM(十二) —— 对象的实例化
    Git 学习笔记 | 安装 Git 及环境配置
    JMM模型与并发三大特性
    【Python脚本进阶】2.2、构建一个SSH僵尸网络(下):利用SSH中的弱私钥
    从三个案例看数字化转型需求下存储架构的选择和应用
    手机备忘录可以设置密码吗 能锁屏加密的备忘录
    Nginx的进程结构
  • 原文地址:https://blog.csdn.net/ceshiren456/article/details/126930798