用 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 定位替换代码


/**
 * //UIAApplication[1]/UIAWindow[1]/UIATableView[1]/UIATableCell[1]/UIAButton[3]
 * @param str
 * @return
 */
private  String xpathToIosLocatorJs(String str){
    String[] array=str.trim().replace("//","/").split("/");
    for (int i=1;i<array.length;i++){
        String tag=array[i];
        String[] tagArray=tag.split("\\[");
        String tagName=tagArray[0];
        if (tagArray.length==2){
            if (!tagName.contains("UIAApplication")&&!tagName.contains("UIAWindow")){
                String indexStr=tagArray[1].replace("]","");
                if (checkStringIsInt(indexStr)){
                    int  index=Integer.valueOf(indexStr)-1;
                    indexStr=String.valueOf(index);
                    tag=tag.replace("["+tagArray[1],"["+indexStr+"]");
                }else {
                    indexStr="\""+indexStr.split("=")[1].replace("\"","").replace("'","")+"\"";
                    tag=tag.replace("["+tagArray[1],"["+indexStr+"]");
                }
            }
        }
        switch (tagName){
            case "UIAApplication":
                tagName="frontMostApp";
                break;
            case "UIAWindow":
                tagName="mainWindow";
                break;
            case "UIATableCell":
                tagName="cells";
                break;
            case "*":
                tagName="frontMostApp().mainWindow().elements";
                break;
            default:
                tagName=tagName.replace("UIA","")+"s";
                break;
        }
        tagName=tagName+"()";
        StringBuffer sb=new StringBuffer();
        if (!tagName.equals("")){
            sb.append(tagName.charAt(0));
            tagName=tagName.replace(tagName.charAt(0),sb.toString().toLowerCase().charAt(0));
        }
        tag=tag.replace(tagArray[0],tagName);
        if (tagName.contains("frontMostApp")||tagName.contains("mainWindow")){
            tag=tag.replace("["+tagArray[1],"");
        }
        str=str.replace(array[i],tag);
    }
    str=" var webElement="+str.replace("//","UIATarget.localTarget().").replace("/",".")+";";
    return str;

}


private String xpathToUiautomatorJs(String str){
    str=xpathToIosLocatorJs(str);
    str=str.replace("//","target.").replace("/",".").replace("var webElement=","");
    String[] array=str.split("\\.");
    for (int i=0;i<array.length;i++){
        String tagName=array[i];
        if (tagName.contains("UIATarget")||tagName.contains("localTarget")||tagName.contains("frontMostApp")||tagName.contains("mainWindow")){
            str=str.replace(array[i],"");
        }else {
            break;
        }
    }
    str=str.substring(3,str.length()).replace(";","");
    return str;
}
//获取元素
public WebElement getWebelement(Locator locator,WebDriver driver)
{
    WebElement webElement;
    switch (locator.getLocatorType())
    {
        case "xpath" :
            if (driver instanceof IOSDriver){
                IOSDriver iosDriver=(IOSDriver) driver;
                String uiautomatorJs=xpathToUiautomatorJs(locator.getLocatorValue());
                log.info("将xpath转换成uiautomatorJs"+uiautomatorJs);
                webElement=iosDriver.findElementByIosUIAutomation(uiautomatorJs);
            }else if (driver instanceof AndroidDriver){
                String locatorValue=locator.getLocatorValue();
                if (locatorValue.contains("//*[@text")){
                    String text=locatorValue.split("=")[1].replace("'","").replace("]","").replace("\"","");
                    String uiautomatorExpress="new UiSelector().text(\""+text+"\")";
                    webElement=((AndroidDriver) driver).findElementByAndroidUIAutomator(uiautomatorExpress);
                }else if (locatorValue.contains("//*[contains(@text")){
                    String text=locatorValue.split(",")[1].replace("'","").replace("]","").replace("\"","").replace(")","");
                    String uiautomatorExpress="new UiSelector().textContains(\""+text+"\")";
                    webElement=((AndroidDriver) driver).findElementByAndroidUIAutomator(uiautomatorExpress);
                }else {
                    webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
                }
            }else {
                webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
            }
            break;
        case "id":
            webElement=driver.findElement(By.id(locator.getLocatorValue()));
            break;
        case "cssSelector":
            webElement=driver.findElement(By.cssSelector(locator.getLocatorValue()));
            break;
        case "name":
            webElement=driver.findElement(By.name(locator.getLocatorValue()));
            break;
        case "className":
            webElement=driver.findElement(By.className(locator.getLocatorValue()));
            break;
        case "linkText":
            webElement=driver.findElement(By.linkText(locator.getLocatorValue()));
            break;
        case "partialLinkText":
            webElement=driver.findElement(By.partialLinkText(locator.getLocatorValue()));
            break;
        case "tagName":
            webElement=driver.findElement(By.tagName(locator.getLocatorValue()));
            break;
        case "iosUIAutomation":
            if (driver instanceof IOSDriver){
                webElement=((IOSDriver) driver).findElementByIosUIAutomation(locator.getLocatorValue());
            }else {
                webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
            }
            break;
        default :
            webElement=driver.findElement(By.xpath(locator.getLocatorValue()));
            break;

    }
    return webElement;
}

2、input 输入替代方法代码

/**
 * 文本框输入操作
 * @param locator  元素locator
 * @param value 输入值
 */
public void type(Locator locator,String value,WebDriver driver)
{
    try {
        WebElement webElement=findElement(locator,driver);
        if (driver instanceof AppiumDriver){
            if (driver instanceof  AndroidDriver){
                AppiumDriver androidDriver=(AppiumDriver) driver;
                if (androidDriver.getContext().contains("NATIVE_APP")){
                    MobileElement mobileElement= (MobileElement)webElement;
                    mobileElement.setValue(value);
                }else {
                    webElement.sendKeys(value);
                }
            }else {
                MobileElement mobileElement= (MobileElement)webElement;
                mobileElement.setValue(value);
            }
        }else {
            webElement.sendKeys(value);
        }
        log.info("input输入:"+locator.getLocatorName()+"["+"By."+locator.getLocatorType()+":"+locator.getLocatorValue()+"value:"+value+"]");
    } catch (Exception e) {
        log.error("找不到元素,input输入失败:"+locator.getLocatorName()+"["+"By."+locator.getLocatorType()+":"+locator.getLocatorValue()+"]");
        e.addSuppressed(new Exception(""));
        e.addSuppressed(new Exception("找不到元素,input输入失败:"+getLocatorInfo(locator)));
        e.printStackTrace();
        throw e;
    }

}

附:locator 对象


/**
 * Created by zhengshuheng on 2017/2/16 0016.
 */
public class Locator  implements Serializable{
    @ApiModelProperty(value = "元素Id")
    private Integer locatorId;
    @ApiModelProperty(value = "元素编码")
    private String  locatorNum;
    @ApiModelProperty(value = "元素名称")
    private String  locatorName;
    @ApiModelProperty(value = "定位方式")
    private String  locatorType;
    @ApiModelProperty(value = "定位内容")
    private String  locatorValue;
    @ApiModelProperty(value = "页面ID")
    private Integer pageId;
    //是否唯一
    private Integer isOnlyOne;

    public Integer getLocatorId() {
        return locatorId;
    }

    public void setLocatorId(Integer locatorId) {
        this.locatorId = locatorId;
    }

    public String getLocatorNum() {
        return locatorNum;
    }

    public void setLocatorNum(String locatorNum) {
        this.locatorNum = locatorNum;
    }

    public String getLocatorName() {
        return locatorName;
    }

    public void setLocatorName(String locatorName) {
        this.locatorName = locatorName;
    }

    public String getLocatorType() {
        return locatorType;
    }

    public void setLocatorType(String locatorType) {
        this.locatorType = locatorType;
    }

    public String getLocatorValue() {
        return locatorValue;
    }

    public void setLocatorValue(String locatorValue) {
        this.locatorValue = locatorValue;
    }

    public Integer getPageId() {
        return pageId;
    }

    public void setPageId(Integer pageId) {
        this.pageId = pageId;
    }

    public Integer getIsOnlyOne() {
        return isOnlyOne

原创文章,转载请注明出处

原文:http://www.webdriver.org/article-58-1.html


↙↙↙阅读原文可查看相关链接,并与作者交流