UiAutomator是由谷歌在Android4.1版本发布时推出的一款用Java编写的UI自动化测试框架。
基于Accessibility服务,该工具提供了对外开放的的api,可以使用这些api对安卓应用进行一系列的自动化测试操作。例如:打开app、点击、滑动、键盘输入、长按以及常用的断言等一系列模拟ui操作。
UiAutomator是Android自动化测试框架,其最大的特点就是可以跨进程操作,基本用法:
device = UiDevice.getInstance(getInstrumentation()); device.pressHome(); // Bring up the default launcher by searching for a UI component // that matches the content description for the launcher button. UiObject allAppsButton = device .findObject(new UiSelector().description("Apps")); // Perform a click on the button to load the launcher. allAppsButton.clickAndWaitForNewWindow();
UiDevice提供了很多操作的接口,在上一章介绍过。本章介绍UIObject和UiSelector
UiObject代表了Android应用中定义的任意的View控件,可以根据UiSelector属性在运行时找到匹配的视图,可以对应不同的view。由此可以看出UIObject是视图对象,UiSelector是获取视图对象的匹配规则。
看在UiDevice中的findObject方法:
/** * Returns a UiObject which represents a view that matches the specified selector criteria. * * @param selector * @return UiObject object */ public UiObject findObject(UiSelector selector) { return new UiObject(this, selector); }
这个findObject方法返回了一个 UIObject对象,看UIObject的构造函数:
/** * Package-private constructor. Used by {@link UiDevice#findObject(UiSelector)} to construct a * UiObject. */ UiObject(UiDevice device, UiSelector selector) { mDevice = device; mUiSelecto
这是一个包内可见的构造函数,所以如果我们想自己new UIObject,就会提示
'UiObject(androidx.test.uiautomator.UiDevice, androidx.test.uiautomator.UiSelector)' is not public in 'androidx.test.uiautomator.UiObject'. Cannot be accessed from outside package
所以我们只能用UiDevice通过UiSelector去查找界面中的UIObject。在UIObject这个类中我们也可以看到有获取UIObject属性的方法,如:
/** * Retrieves the <code>className</code> property of the UI element. * * @return class name of the current node represented by this UiObject * @throws UiObjectNotFoundException if no match was found * @since API Level 18 */ public String getClassName() throws UiObjectNotFoundException { Tracer.trace(); AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout()); if(node == null) { throw new UiObjectNotFoundException(mUiSelector.toString()); } String retVal = safeStringReturn(node.getClassName()); Log.d(LOG_TAG, String.format("getClassName() = %s", retVal)); return retVal; }
AccessibilityNodeInfo node = findAccessibilityNodeInfo(mConfig.getWaitForSelectorTimeout());
方法里调用的是QueryController中的方法findAccessibilityNodeInfo(getSelector());
在这个方法中有一个同步的代码块查找根节点:
synchronized (mLock) { AccessibilityNodeInfo rootNode = getRootNode(); if (rootNode == null) { Log.e(LOG_TAG, "Cannot proceed when root node is null. Aborted search"); return null; } // Copy so that we don't modify the original's sub selectors UiSelector uiSelector = new UiSelector(selector); return translateCompoundSelector(uiSelector, rootNode, isCounting); }
这里是UiAutomator得到节点的核心方法。
getRootNode()最终调用的是UiAutomation的getRootInActiveWindow方法(与AccessibilityService交互)来获取根节点rootNode。根节点获取之后,通过递归
translateCompoundSelector遍历 子节点。
1 /** 2 * A compoundSelector encapsulate both Regular and Pattern selectors. The formats follows: 3 * <p/> 4 * regular_selector = By[attributes... CHILD=By[attributes... CHILD=By[....]]] 5 * <br/> 6 * pattern_selector = ...CONTAINER=By[..] PATTERN=By[instance=x PATTERN=[regular_selector] 7 * <br/> 8 * compound_selector = [regular_selector [pattern_selector]] 9 * <p/> 10 * regular_selectors are the most common form of selectors and the search for them 11 * is straightforward. On the other hand pattern_selectors requires search to be 12 * performed as in regular_selector but where regular_selector search returns immediately 13 * upon a successful match, the search for pattern_selector continues until the 14 * requested matched _instance_ of that pattern is matched. 15 * <p/> 16 * Counting UI objects requires using pattern_selectors. The counting search is the same 17 * as a pattern_search however we're not looking to match an instance of the pattern but 18 * rather continuously walking the accessibility node hierarchy while counting matched 19 * patterns, until the end of the tree. 20 * <p/> 21 * If both present, order of parsing begins with CONTAINER followed by PATTERN then the 22 * top most selector is processed as regular_selector within the context of the previous 23 * CONTAINER and its PATTERN information. If neither is present then the top selector is 24 * directly treated as regular_selector. So the presence of a CONTAINER and PATTERN within 25 * a selector simply dictates that the selector matching will be constraint to the sub tree 26 * node where the CONTAINER and its child PATTERN have identified. 27 * @param selector 28 * @param fromNode 29 * @param isCounting 30 * @return AccessibilityNodeInfo 31 */ 32 private AccessibilityNodeInfo translateCompoundSelector(UiSelector selector, 33 AccessibilityNodeInfo fromNode, boolean isCounting) { 34 // Start translating compound selectors by translating the regular_selector first 35 // The regular_selector is then used as a container for any optional pattern_selectors 36 // that may or may not be specified. 37 if(selector.hasContainerSelector()) 38 // nested pattern selectors 39 if(selector.getContainerSelector().hasContainerSelector()) { 40 fromNode = translateCompoundSelector( 41 selector.getContainerSelector(), fromNode, false); 42 initializeNewSearch(); 43 } else 44 fromNode = translateReqularSelector(selector.getContainerSelector(), fromNode); 45 else 46 fromNode = translateReqularSelector(selector, fromNode); 47 if(fromNode == null) { 48 if (DEBUG) 49 Log.d(LOG_TAG, "Container selector not found: " + selector.dumpToString(false)); 50 return null; 51 } 52 if(selector.hasPatternSelector()) { 53 fromNode = translatePatternSelector(selector.getPatternSelector(), 54 fromNode, isCounting); 55 if (isCounting) { 56 Log.i(LOG_TAG, String.format( 57 "Counted %d instances of: %s", mPatternCounter, selector)); 58 return null; 59 } else { 60 if(fromNode == null) { 61 if (DEBUG) 62 Log.d(LOG_TAG, "Pattern selector not found: " + 63 selector.dumpToString(false)); 64 return null; 65 } 66 } 67 } 68 // translate any additions to the selector that may have been added by tests 69 // with getChild(By selector) after a container and pattern selectors 70 if(selector.hasContainerSelector() || selector.hasPatternSelector()) { 71 if(selector.hasChildSelector() || selector.hasParentSelector()) 72 fromNode = translateReqularSelector(selector, fromNode); 73 } 74 if(fromNode == null) { 75 if (DEBUG) 76 Log.d(LOG_TAG, "Object Not Found for selector " + selector); 77 return null; 78 } 79 Log.i(LOG_TAG, String.format("Matched selector: %s <<==>> [%s]", selector, fromNode)); 80 return fromNode; 81 }
遍历的条件是selector.hasContainerSelector(),UiSelector稍后会详细的解读。
在UiObject中,还有对对象的操作方法,如click swipe等.这些方法调用的和UiDevice中的方法一样,都是
getAutomatorBridge().getInteractionController().clickNoSync(x, y);如果不一样可能也是多了一些超时参数之类的重载方法。
所以之后的流程可以参考上篇 UiDevice的解读,这里不再复述。
UiDevice: https://www.cnblogs.com/yuan1225/p/13254235.html