1 /* 2 * Copyright (C) 2015 DroidDriver committers 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /** 18 * Helper classes for writing an Android UI test framework using DroidDriver. 19 * 20 * <h2>UI test framework design principles</h2> 21 * 22 * A UI test framework should model the UI of the AUT in a hierarchical way to maximize code reuse. 23 * Common interactions should be abstracted as methods of page objects. Uncommon interactions may 24 * not be abstracted, but carried out using "driver" directly. 25 * <p> 26 * The organization of the entities (pages, components) does not need to strictly follow the AUT 27 * structure. The UI model can be greatly simplified to make it easy to use. 28 * <p> 29 * In general the framework should follow these principles: 30 * <ul> 31 * <li>Layered abstraction: at the highest level, methods completely abstract the implementation 32 * detail. This kind of methods carry out a complex action, usually involving multiple steps. 33 * At a lower level, methods can expose some details, e.g. clickInstallButton(), which does a 34 * single action and returns a dialog instance it opens, and let the caller decide how to 35 * further interact with it. Lastly at the lowest level, you can always use "driver" to access 36 * any elements if no higher-level methods are available.</li> 37 * <li>Instance methods of a page object assume the page is currently shown.</li> 38 * <li>If a method opens another page, it should return that page on a best-effort basis. There 39 * could be exceptions where we let callers determine the type of the new page, but that 40 * should be considered hacks and be clearly documented.</li> 41 * <li>The page object constructors are public so that it's easy to hack as mentioned above, but 42 * don't abuse it -- typically callers should acquire page objects by calling methods of other 43 * page objects. The root is the home page of the AUT.</li> 44 * <li>Simple dialogs may not merit their own top-level classes, and can be nested as static 45 * subclasses.</li> 46 * <li>Define constants that use values generated from Android resources instead of using string 47 * literals. For example, call {@link android.content.Context#getResources} to get the 48 * Resources instance, then call {@link android.content.res.Resources#getResourceName} to get 49 * the string representation of a resource id, or call {@link 50 * android.content.res.Resources#getString} to get the localized string of a string resource. 51 * This gives you compile-time check over incompatible changes.</li> 52 * <li>Avoid public constants. Typically clients of a page object are interested in what can be 53 * done on the page (the content or actions), not how to achieve that (which is an 54 * implementation detail). The constants used by the page object hence should be encapsulated 55 * (declared private). Another reason for this item is that the constants may not be real 56 * constants. Instead they are generated from resources and acquiring the values requires the 57 * {@link android.content.Context}, which is not available until setUp() is called. If those 58 * are referenced in static fields of a test class, they will be initialized at class loading 59 * time and result in a crash.</li> 60 * <li>There are cases that exposing public constants is arguably desired. For example, when the 61 * interaction is trivial (e.g. clicking a button that does not open a new page), and there 62 * are many similar elements on the page, thus adding distinct methods for them will bloat the 63 * page object class. In these cases you may define public constants, with a warning that 64 * "Don't use them in static fields of tests".</li> 65 * </ul> 66 * 67 * <h2>Common pitfalls</h2> 68 * <ul> 69 * <li>UI elements are generally views. Users can get attributes and perform actions. Note that 70 * actions often update a UiElement, so users are advised not to store instances of UiElement 71 * for later use - the instances could become stale. In other words, UiElement represents a 72 * dynamic object, while Finder represents a static object. Don't declare fields of the type 73 * UiElement; use Finder instead.</li> 74 * <li>{@link android.test.ActivityInstrumentationTestCase2#getActivity} calls 75 * {@link android.test.InstrumentationTestCase#launchActivityWithIntent}, which may hang in 76 * {@link android.app.Instrumentation#waitForIdleSync}. You can call 77 * {@link android.content.Context#startActivity} directly.</li> 78 * <li>startActivity does not wait until the new Activity is shown. This may cause problem when 79 * the old Activity on screen contains UiElements that match what are expected on the new 80 * Activity - interaction with the UiElements fails because the old Activity is closing. 81 * Sometimes it shows as a test passes when run alone but fails when run with other tests. 82 * The work-around is to add a delay after calling startActivity.</li> 83 * <li>Error "android.content.res.Resources$NotFoundException: Unable to find resource ID ..."? 84 * <br> 85 * This may occur if you reference the AUT's resource in tests, and the two APKs are out of 86 * sync. Solution: build and install both AUT and tests together.</li> 87 * <li>"You said the test runs on older devices as well as API18 devices, but mine is broken on 88 * X (e.g. GingerBread)!" 89 * <br> 90 * This may occur if your AUT has different implementations on older devices. In this case, 91 * your tests have to match the different execution paths of AUT, which requires insight into 92 * the implementation of the AUT. A tip for testing older devices: uiautomatorviewer does not 93 * work on ore-API16 devices (the "Device screenshot" button won't work), but you can use it 94 * with dumps from DroidDriver (use to-uiautomator.xsl to convert the format).</li> 95 * <li>"com.android.launcher has stopped unexpectedly" and logcat says OutOfMemoryError 96 * <br> 97 * This is sometimes seen on GingerBread or other low-memory and slow devices. GC is not fast 98 * enough to reclaim memory on those devices. A work-around: call gc more aggressively and 99 * sleep to let gc run, e.g. 100 * <pre> 101 public void setUp() throws Exception { 102 super.setUp(); 103 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { 104 Runtime.getRuntime().gc(); 105 SystemClock.sleep(1000L); 106 } 107 } 108 </pre></li> 109 * </ul> 110 */ 111 package io.appium.droiddriver.helpers; 112