Appium基础学习之 | Appium代码转换为UiAutomator代码Find

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ouyanggengcheng/article/details/85704113

    在Appium基础学习之 | Bootstrap源码分析留的尾巴,然后通过Appium基础学习之 | UiAutomator使用过渡简单介绍了一下UiAutomator、由于在Android设备中是由UiAutomator工具接管,所以接下来看看Appium代码是如何转化为UiAutomator代码的。

    在Appium基础学习之 | Bootstrap源码分析这篇文章的最后部分是map初始化的代码,这个map包含了全部对Android应用的操作,代码再重复贴一下

static {
    map.put("waitForIdle", new WaitForIdle());
    map.put("clear", new Clear());
    map.put("orientation", new Orientation());
    map.put("swipe", new Swipe());
    map.put("flick", new Flick());
    map.put("drag", new Drag());
    map.put("pinch", new Pinch());
    map.put("click", new Click());
    map.put("touchLongClick", new TouchLongClick());
    map.put("touchDown", new TouchDown());
    map.put("touchUp", new TouchUp());
    map.put("touchMove", new TouchMove());
    map.put("getText", new GetText());
    map.put("setText", new SetText());
    map.put("getName", new GetName());
    map.put("getAttribute", new GetAttribute());
    map.put("getDeviceSize", new GetDeviceSize());
    map.put("scrollTo", new ScrollTo());
    map.put("find", new Find());
    map.put("getLocation", new GetLocation());
    map.put("getSize", new GetSize());
    map.put("getRect", new GetRect());
    map.put("wake", new Wake());
    map.put("pressBack", new PressBack());
    map.put("pressKeyCode", new PressKeyCode());
    map.put("longPressKeyCode", new LongPressKeyCode());
    map.put("takeScreenshot", new TakeScreenshot());
    map.put("updateStrings", new UpdateStrings());
    map.put("getDataDir", new GetDataDir());
    map.put("performMultiPointerGesture", new MultiPointerGesture());
    map.put("openNotification", new OpenNotification());
    map.put("source", new Source());
    map.put("compressedLayoutHierarchy", new CompressedLayoutHierarchy());
    map.put("configurator", new ConfiguratorHandler());
  }

  /**
   * Gets the handler out of the map, and executes the command.
   *
   * @param command
   *          The {@link AndroidCommand}
   * @return {@link AndroidCommandResult}
   */
  public AndroidCommandResult execute(final AndroidCommand command) {
    try {
      Logger.debug("Got command action: " + command.action());

      if (map.containsKey(command.action())) {
        return map.get(command.action()).execute(command);
      } else {
        return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
            "Unknown command: " + command.action());
      }
    } catch (final JSONException e) {
      Logger.error("Could not decode action/params of command");
      return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,
          "Could not decode action/params of command, please check format!");
    }
  }

可以看到包含了很多比较常见的find、clear、click、setText(sendKeys)等等,不可能全部的操作都讲一遍,所以根据前面的示例中使用的find、click方法讲解。取出map中对应的实例化对象执行execute()方法。

一、Find

先讲解Find,顾名思义这个方法就是找元素,从上一篇文章对UiAutomator的了解,很容易就能理解实际Appium中使用Find方法去查找得到的对象,被转化的相对于UiAutomator来说就是UiSelector对象。

1.Bootstrap的Find类

下面看源代码中execute()方法代码如下

 @Override
  public AndroidCommandResult execute(final AndroidCommand command)
      throws JSONException {
    return execute(command, false);
  }

扫描二维码关注公众号,回复: 5037997 查看本文章

  /**
   * execute implementation.
   *
   * @see io.appium.android.bootstrap.handler.Find#execute(io.appium.android.
   *      bootstrap.AndroidCommand)
   *
   * @param command
   *          The {@link AndroidCommand} used for this handler.
   *
   * @param isRetry
   *          Is this invocation a second attempt?
   *
   * @return {@link AndroidCommandResult}
   * @throws JSONException
   */
  private AndroidCommandResult execute(final AndroidCommand command,
      final boolean isRetry) throws JSONException {
    final Hashtable<String, Object> params = command.params();

    // only makes sense on a device
    final Strategy strategy;
    try {
      strategy = Strategy.fromString((String) params.get("strategy"));
    } catch (final InvalidStrategyException e) {
      return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());
    }

    final String contextId = (String) params.get("context");
    final String text = (String) params.get("selector");
    final boolean multiple = (Boolean) params.get("multiple");

    Logger.debug("Finding '" + text + "' using '" + strategy.toString()
        + "' with the contextId: '" + contextId + "' multiple: " + multiple);
    boolean found = false;
    try {
      Object result = null;
      final List<UiSelector> selectors = getSelectors(strategy, text, multiple, contextId);
      if (!multiple) {
        for (int i = 0; i < selectors.size() && !found; i++) {
          try {
            Logger.debug("Using: " + selectors.get(i).toString());
            result = fetchElement(selectors.get(i), contextId);
            found = result != null;
          } catch (final ElementNotFoundException ignored) {
          }
        }
      } else {
        List<AndroidElement> foundElements = new ArrayList<AndroidElement>();
        for (final UiSelector sel : selectors) {
          // With multiple selectors, we expect that some elements may not
          // exist.
          try {
            Logger.debug("Using: " + sel.toString());
            final List<AndroidElement> elementsFromSelector = fetchElements(
                sel, contextId);
            foundElements.addAll(elementsFromSelector);
          } catch (final UiObjectNotFoundException ignored) {
          }
        }
        if (strategy == Strategy.ANDROID_UIAUTOMATOR) {
          foundElements = ElementHelpers.dedupe(foundElements);
        }
        found = foundElements.size() > 0;
        result = elementsToJSONArray(foundElements);
      }

      if (!found) {
        if (!isRetry) {
          Logger
              .debug("Failed to locate element. Clearing Accessibility cache and retrying.");
          // some control updates fail to trigger AccessibilityEvents, resulting
          // in stale AccessibilityNodeInfo instances. In these cases, UIAutomator
          // will fail to locate visible elements. As a work-around, force clear
          // the AccessibilityInteractionClient's cache and search again. This
          // technique also appears to make Appium's searches conclude more quickly.
          // See Appium issue #4200 https://github.com/appium/appium/issues/4200
          if (ReflectionUtils.clearAccessibilityCache()) {
            return execute(command, true);
          }
        }
        // JSONWP spec does not return NoSuchElement
        if (!multiple) {
          // If there are no results and we've already retried, return an error.
          return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
              "No element found");
        }
      }

      return getSuccessResult(result);
    } catch (final InvalidStrategyException e) {
      return getErrorResult(e.getMessage());
    } catch (final UiSelectorSyntaxException e) {
      return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND, e.getMessage());
    } catch (final ElementNotFoundException e) {
      return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage());
    } catch (final ParserConfigurationException e) {
      return getErrorResult("Error parsing xml hierarchy dump: "
          + e.getMessage());
    } catch (final InvalidSelectorException e) {
      return new AndroidCommandResult(WDStatus.INVALID_SELECTOR, e.getMessage());
    }
  }

可以看到AndroidCommandExecutor类中调用的是第一个execute()方法,然后自身再调用第二个execute()方法,所以重点是第二个execute()方法。

(1)首先看第一行代码

final Hashtable<String, Object> params = command.params();

得到一个Hashtable,从command.params()方法中返回,command是调用execute()方法入参的AndroidCommand对象,所以进入到AndroidCommand类的params()方法。

2.Bootstrap的AndroidCommand类

public AndroidCommand(final String jsonStr) throws JSONException,
      CommandTypeException {
    json = new JSONObject(jsonStr);
    setType(json.getString("cmd"));
  }

 ......

  /**
   * Return a hash table of name, value pairs as arguments to the handlers
   * executing this command.
   *
   * @return Hashtable<String, Object>
   * @throws JSONException
   */
  public Hashtable<String, Object> params() throws JSONException {
    final JSONObject paramsObj = json.getJSONObject("params");
    final Hashtable<String, Object> newParams = new Hashtable<String, Object>();
    final Iterator<?> keys = paramsObj.keys();

    while (keys.hasNext()) {
      final String param = (String) keys.next();
      newParams.put(param, paramsObj.get(param));
    }
    return newParams;
  }

上面代码主要列出了AndroidCommand构造函数以及params()方法,主要是因为需要用到AndroidCommand构造函数中初始化的JSON对象的数据。可以回看Appium基础学习之 | Bootstrap源码分析这篇文章中在SocketServer类中handleClientData()方法中调用getCommand()方法中入参了一个定位需要的String字符串。params()方法是取出其中的params的值,下面看看Appium的log中params是一些什么内容,如下图:

可以看到params是一个json串,里面是4个参数strategy、selector、context、multiple,前面3个都很好猜到,分别用于定位策略、定位位置、定位内容,multiple字段目前来说还不知道做什么的,在方法注释中暂时也没看到,后面用到的时候再看看。回到上面params()方法,把params的值取出加入到了Hashtable中,现在可以很清楚的知道Hashtable是什么内容了。

3.回到Bootstrap的Find类

从execute()方法第二行继续往下,分别是从Hashtable取出strategy、selector、context、multiple4个值。其中strategy还是Bootstrap中定义的对象,它具体是什么内容暂时不贴代码看了,猜也知道它应该是一些定位方法的内容。得到值后继续往下定义了一个布尔值found,一个Object对象;然后调用了getSelectors()方法。

/**
   * Create and return a UiSelector based on the strategy, text, and how many
   * you want returned.
   *
   * @param strategy
   *          The {@link Strategy} used to search for the element.
   * @param text
   *          Any text used in the search (i.e. match, regex, etc.)
   * @param many
   *          Boolean that is either only one element (false), or many (true)
   * @return UiSelector
   * @throws InvalidStrategyException
   * @throws ElementNotFoundException
   */
  private List<UiSelector> getSelectors(final Strategy strategy,
      final String text, final boolean many, final String contextId) throws InvalidStrategyException,
          ElementNotFoundException, UiSelectorSyntaxException, ParserConfigurationException, InvalidSelectorException {
    final List<UiSelector> selectors = new ArrayList<UiSelector>();
    UiSelector sel = new UiSelector();

    switch (strategy) {
      case XPATH:
        try {
          selectors.addAll(getXPathSelectors(text, many, contextId));
        } catch (final ElementNotFoundException ignore) {
        }
        break;
      case CLASS_NAME:
        sel = sel.className(text);
        if (!many) {
          sel = sel.instance(0);
        }
        selectors.add(sel);
        break;
      case ID:
        // There are three types of ids on Android.
        // 1. resourceId (API >= 18)
        // 2. accessibility id (content description)
        // 3. strings.xml id
        //
        // If text is a resource id then only use the resource id selector.
        if (API_18) {
          if (resourceIdRegex.matcher(text).matches()) {
            sel = sel.resourceId(text);
            if (!many) {
              sel = sel.instance(0);
            }
            selectors.add(sel);
            break;
          } else {
            // not a fully qualified resource id
            // transform "textToBeChanged" into:
            // com.example.android.testing.espresso.BasicSample:id/textToBeChanged
            // android:id/textToBeChanged
            // either it's prefixed with the app package or the android system page.
            String pkg = (String) params.get("pkg");

            if (pkg != null) {
              sel = sel.resourceId(pkg + ":id/" + text);
              if (!many) {
                sel = sel.instance(0);
              }
              selectors.add(sel);
            }

            sel = sel.resourceId("android:id/" + text);
            if (!many) {
              sel = sel.instance(0);
            }
            selectors.add(sel);

            // webview element ids do not have a package prefix
            sel = sel.resourceId(text);
            if (!many) {
              sel = sel.instance(0);
            }
            selectors.add(sel);
          }
        }

        // must create a new selector or the selector from
        // the resourceId search will cause problems
        sel = new UiSelector().description(text);
        if (!many) {
          sel = sel.instance(0);
        }
        selectors.add(sel);

        // resource id and content description failed to match
        // so the strings.xml selector is used
        final UiSelector stringsXmlSelector = stringsXmlId(many, text);
        if (stringsXmlSelector != null) {
          selectors.add(stringsXmlSelector);
        }
        break;
      case ACCESSIBILITY_ID:
        sel = sel.description(text);
        if (!many) {
          sel = sel.instance(0);
        }
        selectors.add(sel);
        break;
      case NAME:
        sel = new UiSelector().description(text);
        if (!many) {
          sel = sel.instance(0);
        }
        selectors.add(sel);

        sel = new UiSelector().text(text);
        if (!many) {
          sel = sel.instance(0);
        }
        selectors.add(sel);
        break;
      case ANDROID_UIAUTOMATOR:
        List<UiSelector> parsedSelectors;
        try {
          parsedSelectors = uiAutomatorParser.parse(text);
        } catch (final UiSelectorSyntaxException e) {
          throw new UiSelectorSyntaxException(
              "Could not parse UiSelector argument: " + e.getMessage());
        }

        for (final UiSelector selector : parsedSelectors) {
          selectors.add(selector);
        }

        break;
      case LINK_TEXT:
      case PARTIAL_LINK_TEXT:
      case CSS_SELECTOR:
      default:
        throw new InvalidStrategyException("Sorry, we don't support the '"
            + strategy.getStrategyName() + "' locator strategy yet");
    }

    return selectors;
  }

上面的getSelectors()方法代码的代码中,可以看看注释,注释中的many对应的就是上面不知道爹妈的multiple,这里有介绍Boolean that is either only one element (false), or many (true)它是用来区分要查找的元素是一个还是多个,这是由Appium协议中来规定的,如果使用的findElements()方法来查找元素,协议中multiple的值是true,如果用findElement()方法则是false。

回到在getSelectors()方法往下看,通过strategy来决定使用什么定位方式,从这里也可以看到Appium的定位方式有XPATH、CLASS_NAME、ID、ACCESSIBILITY_ID、NAME、ANDROID_UIAUTOMATOR、LINK_TEXT、PARTIAL_LINK_TEXT、CSS_SELECTOR共9种定位方法,先不急着看这么多种定位方法后面会专门开博文学习。从示例代码的id定位先了解源代码,先判断一下API大于等于18的时候才能使用ID定位,这里是做了很严谨的判断,实际上在Appium代码中如果引入的不是API18及以上的jar使用ID定位会报错。

通过正则表达式过滤下selector的值,具体怎么过滤或许不符合过滤规则执行else的代码简单看看,反正到最后都是通过UiSelector的resourceId方法得到UiSelector对象。这样就得到了UiAutomator可以识别的UiSelector对象啦,完成转换成功。而其它如XPATH、CLASS_NAME、ACCESSIBILITY_ID、NAME、ANDROID_UIAUTOMATOR、LINK_TEXT、PARTIAL_LINK_TEXT、CSS_SELECTOR等其他方法都有或者经过处理后对应UiSelector的方法完成转换。

然后再回到Find类的execute()方法,得到UiSelector对象往下就是判断multiple的值来执行单个或多个元素的操作,然后执行fetchElement方法

 private JSONObject fetchElement(final UiSelector sel, final String contextId)
      throws JSONException, ElementNotFoundException {
    final JSONObject res = new JSONObject();
    final AndroidElement el = elements.getElement(sel, contextId);
    return res.put("ELEMENT", el.getId());
  }

这个方法也很简单,显示调用了AndroidElementsHash的getElement()方法

4.Bootstrap的AndroidElementsHash类

AndroidElementsHash类的代码如下,只贴出了相关代码,其他省略号......表示

public class AndroidElementsHash {

  private static final Pattern endsWithInstancePattern = Pattern.compile(".*INSTANCE=\\d+]$");

  public static AndroidElementsHash getInstance() {
    if (AndroidElementsHash.instance == null) {
      AndroidElementsHash.instance = new AndroidElementsHash();
    }
    return AndroidElementsHash.instance;
  }

  private final Hashtable<String, AndroidElement> elements;
  private       Integer                           counter;

  private static AndroidElementsHash instance;


  public AndroidElementsHash() {
    counter = 0;
    elements = new Hashtable<String, AndroidElement>();
  }


  public AndroidElement addElement(final UiObject element) {
    counter++;
    final String key = counter.toString();
    final AndroidElement el = new AndroidElement(key, element);
    elements.put(key, el);
    return el;
  }

......
  public AndroidElement getElement(final UiSelector sel, final String key)
      throws ElementNotFoundException {
    AndroidElement baseEl;
    baseEl = elements.get(key);
    UiObject el;

    if (baseEl == null) {
      el = new UiObject(sel);
    } else {
      try {
        el = baseEl.getChild(sel);
      } catch (final UiObjectNotFoundException e) {
        throw new ElementNotFoundException();
      }
    }

    if (el.exists()) {
      // there are times when UiAutomator returns an element from another parent
      // so we need to see if it is within the bounds of the parent
      try {
        if (baseEl != null && !Rect.intersects(baseEl.getBounds(), el.getBounds())) {
            Logger.debug("UiAutomator returned a child element but it is " +
                         "outside the bounds of the parent. Assuming no " +
                         "child element found");
            throw new ElementNotFoundException();
        }
      } catch (final UiObjectNotFoundException e) {
        throw new ElementNotFoundException();
      }
      return addElement(el);
    } else {
      throw new ElementNotFoundException();
    }
  }

}

先判断了Hashtable取出key值,还没有加入任何元素,所以肯定是null;进入if语句块先初始化UiObject对象,往下接着走,exists()判断元素是否存在,元素存在最后调用了addElement加入到了Hashtable中并返回。如果根据UiSelector的信息找不到UIObject对象,则直接保存元素不存在。

5.回到Find类

fetchElement方法把数据转换JSON返回,完工

猜你喜欢

转载自blog.csdn.net/ouyanggengcheng/article/details/85704113