Writing multiple dynamic containers at one time brings great efficiency to the development, but it is a great challenge for the efficiency of the test process that still requires multiple verification. Based on the dynamic layout technique in dynamic container, this paper describes how to improve test efficiency through testability modification. I hope I can bring some inspiration and help to students who also need to test dynamic pages.
Page features of Meituan App
For different users, the page of Meituan App is presented in various ways, which is called “thousands of faces”. Take the “Guess you like” module of Meituan home page as an example, there are many different forms such as single column, Tab and double column for different users. With so many different page style requirements, it was a challenge for the development team to complete the development, test, and launch process in a single day. So test engineers need to rely heavily on automated testing to develop a fast acceptance mechanism.
Technical challenges in automated test implementation
Next, this paper will explain from three dimensions: page elements cannot be located, the principle of Appium element location, AccessibilityNodeInfo and Drawable.
Page elements cannot be located
At present, the automation of Meituan App client mainly relies on Appium (an open source, cross-platform testing framework, which can be used to test native and mixed mobile applications) to realize the positioning and operation of page elements. When we examine page elements through the Appium Inspector, The only information that can be found through the element review is the outer border and the two buttons below. The rest of the information is unrecognizable (see Figure 2). The image in the center and the text in the upper left corner cannot be located and parsed by existing UI automation schemes. Not being able to locate elements makes it impossible to manipulate and assert pages, which severely affects the implementation of automation.
Upon further investigation, we found that these page cards used a large number of Drawable objects to draw information about the page, resulting in elements that could not be located. Why can’t a Drawable object be located? Let’s take a look at the principles of UI automation element positioning.
Principles of Appium element positioning
Current UI automation testing uses Appium to locate and manipulate page elements. As shown in the figure below, AppiumServer communicates with the mobile end of UiAutomator2 to complete the operation of elements.
The process of locating by reading Appium source code is shown in the figure below:
- First, Appium is called
findElement
Is used to locate elements. - Then, call Android to provide
UIDevice
The object’sfindObject
Methods. - Finally, through
PartialMatch.accept
Completes the element search.
Let’s take a look at how the partialmatch.accept completes element positioning. After exploring the source code, we found that the element information is stored in an object called AccessibilityNodeInfo. The source code uses a lot of node.getxxx method information, does everyone look familiar? This information is essentially the attributes of UI elements that we can retrieve in our daily automated tests.
Drawable cannot obtain element information. Is AccessibilityNodeInfo related? Let’s further explore the relationship between Drawable and AccessibilityNodeInfo.
AccessibilityNodeInfo and Drawable
After exploring the source code, we drew the following class diagram to explain the relationship between AccessibilityNodeInfo and Drawable.
View implements AccessibilityEventSource interface and implements a method called onInitializeAccessibilityNodeInfo to fill the information. We also found this information in the Official Android documentation:
OnInitializeAccessibilityNodeInfo () : this method is barrier-free service provides information about the state of the view. The default View implementation has a standard set of View properties, but if your custom View provides interactive controls other than a simple TextView or Button, you should replace this method and set additional information about the View into the AccessibilityNodeInfo object handled by this method.
Drawable does not implement the corresponding method, so it cannot be found by automated tests. Now that we’ve explored the element finding principle, we’re ready to solve the problem.
Page view testability revamp -XraySDK
Comparison of positioning schemes
Knowing that Drawable is not populated with AccessibilityNodeInfo means THAT I can’t plug into the current automated test scheme to get the page content. Then we can think of the following three solutions to solve the problem:
Implementation scheme | scope |
---|---|
Modify the Appium positioning method so that Drawable can be recognized | We need to modify the underlying AccessibilityNodeInfo obtain(View,int) method and add AccessibilityNodeInfo to Drawable |
Use View instead of Drawable | Dynamic layout cards are drawn with Drawable because Drawable uses fewer resources than View and has better drawing performance. To give up Drawable is to give up performance improvement |
Location using image recognition | Many images in dynamic cards contain text, as well as multi-line text, which will greatly affect the accuracy of image recognition |
The above three schemes can not effectively solve the problem of dynamic card element positioning. How to achieve the goal of obtaining view information under the premise of less influence? Next, we will further study the implementation of dynamic layout.
Get and store view information -XrayDumper
Our application scenario is very clear. The automated test obtains the ability to interact with the Client by integrating the Client, and obtains the page information by sending instructions to the App through the Client. Then we can consider embedding an SDK (XraySDK) to get the view, and then providing a client (XrayClient) to automate this part of the function.
The functions of XraySDK are divided as follows:
Module name | Functional division | Runtime environment | Product form |
---|---|---|---|
Xray-Client | 1. Interact with Xray-server to send instructions and receive data 2. Expose external APIS to automation or other systems |
Within the App | Client SDK (AAR and POD-Library) |
Xray-SDK | 1. Obtaining and structuring page information (Xray-Dumper) 2. Receive user instructions to output structured data (Xray-server) |
Internal automation or internal tripartite systems | JAR packages or dependencies based on other languages |
How does XraySDK get the Drawable information we need? Let’s first study the implementation of dynamic layout.
The view rendering process of dynamic layout is divided into: parsing template -> binding data -> calculating layout -> page rendering. After the calculation of layout, the position of elements on the page has been determined, so as long as the information at this stage can be intercepted to achieve the acquisition of view information.
Through investigations of the code, we found that in com. Sankuai. Litho. Recycler. AdapterCompat this class controls the view layout behavior, in bindViewHolder complete view of the final layout and calculation. First, we intercept layout information by inserting a custom listener here.
public final void bindViewHolder(BaseViewHolder<Data> viewHolder, int position) { if (viewHolder ! = null) { viewHolder.bindView(context, getData(position), position); / / test automation callback if (componentTreeCreateListeners! = null) { if (viewHolder instanceof LithoViewHolder) { DataHolder holder = getData(position); LithoView view = ((LithoViewHolder<Data>) viewHolder). LithoView; LayoutController layoutController = ((LithoDynamicDataHolder) holder).getLayoutController(null); VirtualNodeBase node = layoutController.viewNodeRoot; / / by the listener will view information to pass measurability gaiden SDK componentTreeCreateListeners. OnComponentTreeCreated (node, the getRootView (), view.getComponentTree()); }}}}Copy the code
The listener is then initialized by exposing a static method to the testability SDK.
public static void setComponentTreeCreateListener(ComponentTreeCreateListener l) { AdapterCompat.componentTreeCreateListeners = l; Try {// MBC compatible dynamic layout automation testing, to avoid cyclic dependencies, using reflective calls to Class<? > mbcDynamicClass = Class.forName("com.sankuai.meituan.mbc.business.item.dynamic.DynamicLithoItem"); Method setComponentTreeCreateListener = mbcDynamicClass.getMethod("setComponentTreeCreateListener", ComponentTreeCreateListener.class); setComponentTreeCreateListener.invoke(null, l); } catch (Exception e) { e.printStackTrace(); } try {// Search for new framework dynamic layout automation test Class<? > searchDynamicClass = Class.forName("com.sankuai.meituan.search.result2.model.DynamicItem"); Method setSearchComponentTreeCreateListener = searchDynamicClass.getMethod("setComponentTreeCreateListener", ComponentTreeCreateListener.class); setSearchComponentTreeCreateListener.invoke(null, l); } catch (Exception e) { e.printStackTrace(); }}Copy the code
Finally, automation takes care of retrieving and storing view information by setting up custom listeners.
/ / monitor layout by static method to set a ComponentTreeCreateListener event AdapterCompat. SetComponentTreeCreateListener (new AdapterCompat.ComponentTreeCreateListener() { @Override public void onComponentTreeCreated(VirtualNodeBase node, ViewInfoObserver vif = new ViewInfoObserver(); ViewInfoObserver vif = new ViewInfoObserver(); vif.update(node, rootView, tree); }});Copy the code
We store the view information in an object like ViewInfoObserver.
public class ViewInfoObserver implements AutoTestObserver{
public static HashMap<String, View> VIEW_MAP = new HashMap<>();
public static HashMap<VirtualNodeBase, View> VIEW = new HashMap<>();
public static HashMap<String, ComponentTree> COMPTREE_MAP = new HashMap<>();
public static String uri = "http://dashboard.ep.dev.sankuai.com/outter/dynamicTemplateKeyFromJson";
@Override
public void update(VirtualNodeBase vn, View view,ComponentTree tree) {
if (null != vn && null != vn.jsonObject) {
try {
String string = vn.jsonObject.toString();
Gson g = new GsonBuilder().setPrettyPrinting().create();
JsonParser p = new JsonParser();
JsonElement e = p.parse(string);
String templateName = null;
String name1 = getObject(e,"templateName");
String name2 = getObject(e,"template_name");
String name3 = getObject(e,"template");
templateName = null != name1 ? name1 : (null != name2 ? name2 : (null != name3 ? name3 : null));
if (null != templateName) {
//如果已经存储则更新视图信息
if (VIEW_MAP.containsKey(templateName)) {
VIEW_MAP.remove(templateName);
}
//存储视图编号
VIEW_MAP.put(templateName, view);
if (VIEW.containsKey(templateName)) {
VIEW.remove(templateName);
}
//存储视图信息
VIEW.put(vn, view);
if (COMPTREE_MAP.containsKey(templateName)) {
COMPTREE_MAP.remove(templateName);
}
COMPTREE_MAP.put(templateName, tree);
System.out.println("autotestDyn:update success");
}
} catch (Exception e) {
System.out.println(e.toString());
System.out.println("autotestDyn:templateName not exist!");
}
}
}
Copy the code
When this information needs to be queried, XrayDumper can be used to complete the output of information.
public class SubViewInfo { public JSONObject getOutData(String template) throws JSONException { JSONObject outData = new JSONObject(); JSONObject componentTouchables = new JSONObject(); if (! COMPTREE_MAP.isEmpty() && COMPTREE_MAP.containsKey(template) && null ! = COMPTREE_MAP.get(template)) { ComponentTree cpt = COMPTREE_MAP.get(template); JSONArray componentArray = new JSONArray(); ArrayList<View> touchables = cpt.getLithoView().getTouchables(); LithoView lithoView = cpt.getLithoView(); int[] ls = new int[2]; lithoView.getLocationOnScreen(ls); int pointX = ls[0]; int pointY = ls[1]; for (int i = 0; i < touchables.size(); i++) { JSONObject temp = new JSONObject(); int height = touchables.get(i).getHeight(); int width = touchables.get(i).getWidth(); int[] tl = new int[2]; touchables.get(i).getLocationOnScreen(tl); temp.put("height",height); temp.put("width",width); temp.put("pointX",tl[0]); temp.put("pointY",tl[1]); String url = ""; try { EventHandler eh = (EventHandler) getValue(getValue(touchables.get(i), "mOnClickListener"), "mEventHandler"); DynamicClickListener listener = (DynamicClickListener) getValue(getValue(eh, "mHasEventDispatcher"), "listener"); Uri clickUri = (Uri) getValue(listener, "uri"); if (null ! = clickUri) { url = clickUri.toString(); } } catch (Exception e) { Log.d("autotest", "get click url error!" ); } temp.put("url",url); componentArray.put(temp); } componentTouchables.put("componentTouchables",componentArray); componentTouchables.put("componentTouchablesCount", cpt.getLithoView().getTouchables().size()); View[] root = (View[])getValue(cpt.getLithoView(),"mChildren"); JSONArray allComponentArray = new JSONArray(); if (root.length > 0) { for (int i = 0; i < root.length; i++) { try { if (null ! = root[i]) { Object items[] = (Object[]) getValue(getValue(root[i], "mMountItems"), "mValues"); componentTouchables.put("componentCount", items.length); for (int itemIndex = 0; itemIndex < items.length; itemIndex++) { getMountItems(allComponentArray, items[itemIndex], pointX, pointY); } } } catch (Exception e) { } } } componentTouchables.put("componentUntouchables",allComponentArray); } else { Log.d("autotest","COMPTREE_MAP is null!" ); } outData.put(template,componentTouchables); System.out.println(outData); return outData; }}}Copy the code
Output of view information -XrayServer
Now that we have the information, we need to think about how to pass the view information to an automated test script, and we refer to Appium’s design.
Appium uses the InstrumentsClient installed on the phone to start a SocketServer using the HTTP protocol to automate data communication with the underlying testing framework. We can also learn from the above ideas and start a WebServer in Meituan App to complete the output of information.
In the first step, we implemented a function that inherits the Service component so that we can easily start and stop testability from the command line.
public class AutoTestServer extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { .... return super.onStartCommand(intent, flags, startId); }}Copy the code
The second step is to expose the communication interface through HttpServer.
public class AutoTestServer extends Service { @Override public IBinder onBind(Intent intent) { return null; } @override public int onStartCommand(Intent Intent, int flags, int startId) { = null) { int randNum = intent.getIntExtra("autoTestPort",8999); HttpServer myServer = new HttpServer(randNum); Try {// start HTTP service myserver.start (); System.out.println("AutoTestPort:" + randNum); } catch (IOException e) { System.err.println("AutoTestPort:" + e.getMessage()); myServer = new HttpServer(8999); try { myServer.start(); System.out.println("AutoTestPort:8999"); } catch (IOException e1) { System.err.println("Default:" + e.getMessage()); } } } return super.onStartCommand(intent, flags, startId); }}Copy the code
The third step is to register the listeners you set up earlier.
public class AutoTestServer extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, Int startId) {/ / registered listeners AdapterCompat. SetComponentTreeCreateListener (new ponentTreeCreateListener AdapterCompat.Com () { @Override public void onComponentTreeCreated(VirtualNodeBase node, View rootView, ComponentTree tree) { ViewInfoObserver vif = new ViewInfoObserver(); vif.update(node, rootView, tree); }}); // Create an object and pass the port to..... return super.onStartCommand(intent, flags, startId); }}Copy the code
Finally, there are different paths to receive different instructions in HttpServer.
private JSONObject getResponseByUri(@Nonnull IHTTPSession session) throws JSONException { String uri = session.getUri(); if (isFindCommand(uri)) { return getResponseByFindUri(uri); } } @Nonnull private JSONObject getResponseByFindUri(@Nonnull String uri) throws JSONException { String template = uri.split("/")[2]; String protocol = uri.split("/")[3]; switch (protocol) { case "frame": TemplateLayoutFrame tlf = new TemplateLayoutFrame(); return tlf.getOutData(template); case "subview": SubViewInfo svi = new SubViewInfo(); return svi.getOutData(template); // Omits part of the code handling logic.... default: JSONObject errorJson = new JSONObject(); errorJson.put("success", false); Errorjson. put("message", "entered find link address error "); return errorJson; }}Copy the code
Overall FUNCTIONAL structure of the SDK
Automation scripts through access to the device to a specific port (for example: http://localhost:8899/find/subview), via XrayServer, through the access path forward requests to the XrayDumper for information extraction, and output. The layout parser then serializes the layout information into JSON data, which is passed over the XrayServer network as an HTTP response to an automated test script.
View information enhancement
In addition to the usual information about location, content, type, etc., we further enhance the validity of the page view structure by checking time listeners to determine whether view elements can interact.
// setGestures ArrayList<String> gestures = new ArrayList<>(); if (view.isClickable()){ gestures.add("isClickable"); } if (view.isLongClickable()){ gestures.add("isLongClickable"); } // omit some code.....Copy the code
Benefits of dynamic layout automation
Based on the improvement of view testability, the automatic test coverage of Meituan dynamic cards has been greatly improved, from the original automatic test can not be done, to more than 80% of dynamic cards have realized automatic test, and the efficiency has also been significantly improved.
future
Page view information, as one of the most basic and important attributes of client testing, is a code-level representation of user visual information. It is very important for the machine to recognize page element information, and the technical team will benefit greatly from its scalability modification. For your reference, we will list a few areas of exploration for view testability transformation.
Use view resolution principles to locate WebView elements
Applying the same idea, we can also solve the problem of WebView element positioning.
The corresponding WebView instance can be obtained through the SDK running inside the App. You can get all the view information by getting to the root node, iterating from the root node, and storing the information for each node.
Is there an equally appropriate root node in the WebView? Based on our understanding of HTML, we can assume that all tags in HTML hang below the BODY tag, and the BODY tag is the root node we need to select. WebElement[“attrName”] is used to obtain attributes.
More application scenarios for view testability transformation
- Improve the reliability of functional test: In the function test automation, the stability of automatic test can be effectively improved through more stable and rapid internal view information output. Avoid automated test failures due to element unavailability or slow element acquisition.
- Improve reliability test efficiency: For reliability tests that rely on random or random page operations based on view information, only interactive elements can be operated depending on the filtering of view information (whether the event listener of element is empty). In this way, the efficiency of reliability testing can be effectively improved, and more pages can be checked per unit time.
- Added compatibility testing methods: To check page compatibility, scan for UI abnormalities such as unreasonable stacking, blank areas, and abnormal shapes based on the location information and properties of components on the page. You can also retrieve content information, such as images and text, to check for inappropriate content presentation. It can be used as an effective supplement for image comparison scheme.
Recruitment information
Meituan Platform Quality Technology Center, responsible for the basic technology quality work of MEituan App business and large front-end (mobile client and Web front-end), precipitation process specifications and supporting tools, and improvement of r&d efficiency. The team has excellent technology and good atmosphere. If you are interested, please send your resume to [email protected]
Read more technical articles from meituan’s technical team
Front end | | algorithm back-end | | | data security operations | iOS | Android | test
| in the public bar menu dialog reply goodies for [2020], [2019] special purchases, goodies for [2018], [2017] special purchases such as keywords, to view Meituan technology team calendar year essay collection.
| this paper Meituan produced by the technical team, the copyright ownership Meituan. You are welcome to reprint or use the content of this article for non-commercial purposes such as sharing and communication. Please mark “Content reprinted from Meituan Technical team”. This article shall not be reproduced or used commercially without permission. For any commercial activity, please send an email to [email protected] for authorization.