说明:
原本打算按照项目的目录,将源码都分析一遍,但是其中遇到部分有些不太理解的,还有部分是依赖于另外一些代码的。所以这次特地先抽出相当重要的一块代码 find进行解释。后续的click,setText等等都是依赖于find才能够进行的,所以先抽出find来进行讲解
正文:
首先我们先来看看find的源代码
private AndroidCommandResult execute(final AndroidCommand command,
final boolean isRetry) throws JSONException {
//这里是解析出command中的每一个参数,将其转换成Hashtable,还是类似于key-value
final Hashtable<String, Object> params = command.params();
// only makes sense on a device
final Strategy strategy;
try {
//判断参数中是否有strategy,如果没有直接返回错误异常
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;
//通过getSelector 获取一个UiSelector的列表
final List<UiSelector> selectors = getSelectors(strategy, text, multiple);
if (!multiple) {
//这里会遍历selector列表,并且在没找到元素对象的时候,如果找到了直接跳出循环,不会继续往下进行查找
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());
}
}
我们来看看getSelectors吧,它是如何通过你的strategy, text, multiple来获取到UiSelector列表的。这里的multiple代表的是你要查找的元素是一个还是多个
private List<UiSelector> getSelectors(final Strategy strategy,
final String text, final boolean many) throws InvalidStrategyException,
ElementNotFoundException, UiSelectorSyntaxException,
ParserConfigurationException, InvalidSelectorException {
final List<UiSelector> selectors = new ArrayList<UiSelector>();
UiSelector sel = new UiSelector();
switch (strategy) {
case XPATH:
for (final UiSelector selector : getXPathSelectors(text, many)) {
selectors.add(selector);
}
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);
}
}
// 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;
}
代码有点长,我们先逐一的进行分析吧,
xpath:这个暂时还没明白,先过吧。。
class_name:
case CLASS_NAME:
//这里比较简单直接是通过UiSelector的className进行查找,但是问题是如果是many为true的情况下,实际上selectors仍然只是一个而已,所以这个地方没发现为什么能够返回List的UiSelector
sel = sel.className(text);
if (!many) {
sel = sel.instance(0);
}
selectors.add(sel);
break;
#### id
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);
}
ID这里分了两种情况就是你传入的ID是否包含了对应的包名:
- 如果不包含则尝试给你的ID值加上你的测试软件的包名,或者是系统自带的包名(android:id/),这里的判断是根据参数中是否有pkg.
- 再统一进行UiSelector的resourceId的查找,这里的话会要求api的版本是要大于18。如何API的版本小于18 这个时候selector就采用description的方式进行查找。
- 如果说id的方式以及description的方式都匹配失败的情况下 最后的方案就是使用stringxml,匹配到对应的内容
AccessibilityId
实际上就是description进行查找
name
这里的name匹配说来有点坑了,实际上它说先找的还是description,如果说description没有找到的情况下,它才会去匹配name这个属性。
android_UiAutomator 关于UIAutomator的相关命令 后续会有相应的篇章来说明
就先讲到这里,下一篇,我们来看看查找多个元素的时候,appium又做了哪些处理呢。