51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2013|回复: 3
打印 上一主题 下一主题

[Appium] Appium IOS 自动化测试速度优化--稳定高效的方法 (Appium 1.53 +IOS 9.1)

[复制链接]
  • TA的每日心情
    无聊
    2024-7-12 13:16
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]测试小兵

    跳转到指定楼层
    1#
    发表于 2017-6-27 10:43:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    用Appium 做过IOS自动化的都知道,ios下的自动化是特别慢的,有时候查找一个控件居然能耗时两三分钟,试问这样的速度谁能忍受的了,有些公司为此干脆就做IOS自动化了。难道IOS的自动化就真的是这么慢么?非也,慢是因为appium的某些方法没有实现好,经过优化之后是可以摆脱这个问题的,下面给大家讲解下appium ios的优化策略。
    关于appium ios 速度策略优化,网上能够搜索到的好像就是只有一篇,https://testerhome.com/topics/3352,百度很多网站也是转发的这一篇文章。这篇文章说了大概的原因却没有根本的解决问题。
    我们知道影响ios的自动化速度慢的原因主要就是2个方面:
    1、输入操作 ,这个跟android是一样的情况
    2、XPATH查找 特别耗时。
    跟页面控件的多少有关,控件多的页面比如首页,就出现查找元素耗费两三分钟的情况。这主要是因为appium的xpath实现方式不好造成的,appium 实现xpath查找是先通过苹果的 UIAutomation框架 instrument JS  UIATarget.localTarget().logElementTree();获取到控件树结构,再把它格式化成XML,然后再通过xpath遍历查找到控件,最后又转换为底层的instrument JS去执行操作。这其中的获取跟解析都特别耗时。
    针对这两个问题,之前文章提到的方法是,尽量少用xpath定位方式,尽量减少通信,尝试使用通过driver.getPageSource()后自己重新查找方法。输入操作用setvalue代替。但是你会发现ios下的app根本现在根本没有id属性,更可恶的是有大部分的控件name,label等属性都是空的(虽然界面有文字)只能通过又丑又长的xpath表达式定位,例如:
    //UIAApplication[1]/UIAWindow[1]/UIATableView[1]/UIATableCell[1]/UIAButton[2]。这种xpath查找效率是很低的。所以虽然xpath可恨但我们还是不可避免。对于重新查找方法更不可取,一是对编程要求高,二是不知道有多少坑。对于减少通信的做法,只能适应于本地模式,如果是这种方式,不如直接用UIAutomation,用啥appium。用appium就是为了解决不同设备端的整合,分布式并发执行问题的,而这免不了通信,所以前面的文章并没有把问题根本解决。
    下面给大家讲解下我的解决思路:
    通过分析,我发现appium解析耗费大量时间解析xpath最终又是转换为instrument JS,为什么我们不能绕过解析xpath过程,直接把xpath翻译成instrument JS?答案是肯定的,庆幸appium提供了直接执行instrument JS的定位方式ByIosUiautomaiton.
    经过优化后,ios自动化的执行速度得到了大幅提升,原来操作一个控件需要两三分钟的,也只需要2秒左右了!
    具体代码实现如下:(有兴趣的可以自己去查看下instrument  JS语法)
    1、xpath定位替换代码
    1. /**
    2. * //UIAApplication[1]/UIAWindow[1]/UIATableView[1]/UIATableCell[1]/UIAButton[3]
    3. * @param str
    4. * @return
    5. */
    6. private  String xpathToIosLocatorJs(String str){
    7.     String[] array=str.trim().replace("//","/").split("/");
    8.     for (int i=1;i<array.length;i++){
    9.         String tag=array[i];
    10.         String[] tagArray=tag.split("\\[");
    11.         String tagName=tagArray[0];
    12.         if (tagArray.length==2){
    13.             if (!tagName.contains("UIAApplication")&&!tagName.contains("UIAWindow")){
    14.                 String indexStr=tagArray[1].replace("]","");
    15.                 if (checkStringIsInt(indexStr)){
    16.                     int  index=Integer.valueOf(indexStr)-1;
    17.                     indexStr=String.valueOf(index);
    18.                     tag=tag.replace("["+tagArray[1],"["+indexStr+"]");
    19.                 }else {
    20.                     indexStr="\""+indexStr.split("=")[1].replace("\"","").replace("'","")+"\"";
    21.                     tag=tag.replace("["+tagArray[1],"["+indexStr+"]");
    22.                 }
    23.             }
    24.         }
    25.         switch (tagName){
    26.             case "UIAApplication":
    27.                 tagName="frontMostApp";
    28.                 break;
    29.             case "UIAWindow":
    30.                 tagName="mainWindow";
    31.                 break;
    32.             case "UIATableCell":
    33.                 tagName="cells";
    34.                 break;
    35.             case "*":
    36.                 tagName="frontMostApp().mainWindow().elements";
    37.                 break;
    38.             default:
    39.                 tagName=tagName.replace("UIA","")+"s";
    40.                 break;
    41.         }
    42.         tagName=tagName+"()";
    43.         StringBuffer sb=new StringBuffer();
    44.         if (!tagName.equals("")){
    45.             sb.append(tagName.charAt(0));
    46.             tagName=tagName.replace(tagName.charAt(0),sb.toString().toLowerCase().charAt(0));
    47.         }
    48.         tag=tag.replace(tagArray[0],tagName);
    49.         if (tagName.contains("frontMostApp")||tagName.contains("mainWindow")){
    50.             tag=tag.replace("["+tagArray[1],"");
    51.         }
    52.         str=str.replace(array[i],tag);
    53.     }
    54.     str=" var webElement="+str.replace("//","UIATarget.localTarget().").replace("/",".")+";";
    55.     return str;

    56. }


    57. private String xpathToUiautomatorJs(String str){
    58.     str=xpathToIosLocatorJs(str);
    59.     str=str.replace("//","target.").replace("/",".").replace("var webElement=","");
    60.     String[] array=str.split("\\.");
    61.     for (int i=0;i<array.length;i++){
    62.         String tagName=array[i];
    63.         if (tagName.contains("UIATarget")||tagName.contains("localTarget")||tagName.contains("frontMostApp")||tagName.contains("mainWindow")){
    64.             str=str.replace(array[i],"");
    65.         }else {
    66.             break;
    67.         }
    68.     }
    69.     str=str.substring(3,str.length()).replace(";","");
    70.     return str;
    71. }
    72. //获取元素
    73. public WebElement getWebelement(Locator locator,WebDriver driver)
    74. {
    75.     WebElement webElement;
    76.     switch (locator.getLocatorType())
    77.     {
    78.         case "xpath" :
    79.             if (driver instanceof IOSDriver){
    80.                 IOSDriver iosDriver=(IOSDriver) driver;
    81.                 String uiautomatorJs=xpathToUiautomatorJs(locator.getLocatorValue());
    82.                 log.info("将xpath转换成uiautomatorJs"+uiautomatorJs);
    83.                 webElement=iosDriver.findElementByIosUIAutomation(uiautomatorJs);
    84.             }else if (driver instanceof AndroidDriver){
    85.                 String locatorValue=locator.getLocatorValue();
    86.                 if (locatorValue.contains("//*[@text")){
    87.                     String text=locatorValue.split("=")[1].replace("'","").replace("]","").replace("\"","");
    88.                     String uiautomatorExpress="new UiSelector().text(\""+text+"\")";
    89.                     webElement=((AndroidDriver) driver).findElementByAndroidUIAutomator(uiautomatorExpress);
    90.                 }else if (locatorValue.contains("//*[contains(@text")){
    91.                     String text=locatorValue.split(",")[1].replace("'","").replace("]","").replace("\"","").replace(")","");
    92.                     String uiautomatorExpress="new UiSelector().textContains(\""+text+"\")";
    93.                     webElement=((AndroidDriver) driver).findElementByAndroidUIAutomator(uiautomatorExpress);
    94.                 }else {
    95.                     webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
    96.                 }
    97.             }else {
    98.                 webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
    99.             }
    100.             break;
    101.         case "id":
    102.             webElement=driver.findElement(By.id(locator.getLocatorValue()));
    103.             break;
    104.         case "cssSelector":
    105.             webElement=driver.findElement(By.cssSelector(locator.getLocatorValue()));
    106.             break;
    107.         case "name":
    108.             webElement=driver.findElement(By.name(locator.getLocatorValue()));
    109.             break;
    110.         case "className":
    111.             webElement=driver.findElement(By.className(locator.getLocatorValue()));
    112.             break;
    113.         case "linkText":
    114.             webElement=driver.findElement(By.linkText(locator.getLocatorValue()));
    115.             break;
    116.         case "partialLinkText":
    117.             webElement=driver.findElement(By.partialLinkText(locator.getLocatorValue()));
    118.             break;
    119.         case "tagName":
    120.             webElement=driver.findElement(By.tagName(locator.getLocatorValue()));
    121.             break;
    122.         case "iosUIAutomation":
    123.             if (driver instanceof IOSDriver){
    124.                 webElement=((IOSDriver) driver).findElementByIosUIAutomation(locator.getLocatorValue());
    125.             }else {
    126.                 webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
    127.             }
    128.             break;
    129.         default :
    130.             webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
    131.             break;

    132.     }
    133.     return webElement;
    134. }
    复制代码

    2、input输入替代方法代码
    1. /**
    2. * 文本框输入操作
    3. * @param locator  元素locator
    4. * @param value 输入值
    5. */
    6. public void type(Locator locator,String value,WebDriver driver)
    7. {
    8.     try {
    9.         WebElement webElement=findElement(locator,driver);
    10.         if (driver instanceof AppiumDriver){
    11.             if (driver instanceof  AndroidDriver){
    12.                 AppiumDriver androidDriver=(AppiumDriver) driver;
    13.                 if (androidDriver.getContext().contains("NATIVE_APP")){
    14.                     MobileElement mobileElement= (MobileElement)webElement;
    15.                     mobileElement.setValue(value);
    16.                 }else {
    17.                     webElement.sendKeys(value);
    18.                 }
    19.             }else {
    20.                 MobileElement mobileElement= (MobileElement)webElement;
    21.                 mobileElement.setValue(value);
    22.             }
    23.         }else {
    24.             webElement.sendKeys(value);
    25.         }
    26.         log.info("input输入:"+locator.getLocatorName()+"["+"By."+locator.getLocatorType()+":"+locator.getLocatorValue()+"value:"+value+"]");
    27.     } catch (Exception e) {
    28.         log.error("找不到元素,input输入失败:"+locator.getLocatorName()+"["+"By."+locator.getLocatorType()+":"+locator.getLocatorValue()+"]");
    29.         e.addSuppressed(new Exception(""));
    30.         e.addSuppressed(new Exception("找不到元素,input输入失败:"+getLocatorInfo(locator)));
    31.         e.printStackTrace();
    32.         throw e;
    33.     }
    34. }
    复制代码
    原创文章,转载请注明出处

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2024-7-12 13:16
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]测试小兵

    2#
     楼主| 发表于 2017-6-27 10:43:44 | 只看该作者
    1. 附:locator对象

    2. /**
    3. * Created by zhengshuheng on 2017/2/16 0016.
    4. */
    5. public class Locator  implements Serializable{
    6.     @ApiModelProperty(value = "元素Id")
    7.     private Integer locatorId;
    8.     @ApiModelProperty(value = "元素编码")
    9.     private String  locatorNum;
    10.     @ApiModelProperty(value = "元素名称")
    11.     private String  locatorName;
    12.     @ApiModelProperty(value = "定位方式")
    13.     private String  locatorType;
    14.     @ApiModelProperty(value = "定位内容")
    15.     private String  locatorValue;
    16.     @ApiModelProperty(value = "页面ID")
    17.     private Integer pageId;
    18.     //是否唯一
    19.     private Integer isOnlyOne;
    20.     public Integer getLocatorId() {
    21.         return locatorId;
    22.     }
    23.     public void setLocatorId(Integer locatorId) {
    24.         this.locatorId = locatorId;
    25.     }
    26.     public String getLocatorNum() {
    27.         return locatorNum;
    28.     }
    29.     public void setLocatorNum(String locatorNum) {
    30.         this.locatorNum = locatorNum;
    31.     }
    32.     public String getLocatorName() {
    33.         return locatorName;
    34.     }
    35.     public void setLocatorName(String locatorName) {
    36.         this.locatorName = locatorName;
    37.     }
    38.     public String getLocatorType() {
    39.         return locatorType;
    40.     }
    41.     public void setLocatorType(String locatorType) {
    42.         this.locatorType = locatorType;
    43.     }
    44.     public String getLocatorValue() {
    45.         return locatorValue;
    46.     }
    47.     public void setLocatorValue(String locatorValue) {
    48.         this.locatorValue = locatorValue;
    49.     }
    50.     public Integer getPageId() {
    51.         return pageId;
    52.     }
    53.     public void setPageId(Integer pageId) {
    54.         this.pageId = pageId;
    55.     }
    56.     public Integer getIsOnlyOne() {
    57.         return isOnlyOne
    复制代码
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    3#
    发表于 2017-6-27 11:02:27 | 只看该作者
    我最近就是在用instrument JS 来写IOS ui自动化,说说感受吧
    1、确实快了不少,但是也没有非常非常快,和Android还是有差距,不过和直接用ID、Xpath也算欣慰了。
    2、因为是跨平台的语言,所以没法获取到返回的元素对象,代码发过去以后就没以后了,也没办法try catch,或者书写更加复杂的instrument JS来控制查找到元素。appium提供的所有等待方法算是崩了。
    3、我的处理方法是进入一个页面以后先用appium的方法确定所有元素就位,然后再使用instrument JS来操作元素。稳定性当然是没有appium的方法高。
    4、提供一个小技巧,instrument JS也有等待的方法,但是是类似休眠的定时等待(当然还有更高级的我不会搞)UIATarget.localTarget().delay(2);如果确定能够找到页面的各项元素,可以结合等待一次性发送大量语句,效率提高很多。(稳定性你懂的)
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    2024-7-12 13:16
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]测试小兵

    4#
     楼主| 发表于 2017-6-27 11:15:26 | 只看该作者
    巴黎的灯光下 发表于 2017-6-27 11:02
    我最近就是在用instrument JS 来写IOS ui自动化,说说感受吧
    1、确实快了不少,但是也没有非常非常快,和A ...

    稳定性这一块我是用了webdrvier的webdriverwait机制,智能等待,不过即便这样ios还有一个坑,就是有时候很快找到了控件,但是文本还没有出现获取到的文本是空的,所以再这种情况下,又加了一次webdriverwait。跟Android的差距(目前用的uiautomator模式,2.0调通一次后,真机一直启动不了uiautomator2 server服务,而虚拟机却没问题,很是郁闷,也没具体对比出两者的速度)的话,感觉没什么差距用这种方法后感觉比它那个快一点,唯独就是不知道appium 审查出来的的元素,为毛ios的大部分控件是没有属性值的。
    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-14 14:29 , Processed in 0.064921 second(s), 22 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表