├── .gitignore ├── BasicSample ├── .gitignore ├── README.md ├── app │ ├── build.gradle │ └── src │ │ ├── androidTest │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── testing │ │ │ └── espresso │ │ │ └── BasicSample │ │ │ ├── ChangeTextBehaviorTest.java │ │ │ ├── GetRootView.java │ │ │ └── ViewToUix.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ └── testing │ │ │ └── espresso │ │ │ └── BasicSample │ │ │ ├── MainActivity.java │ │ │ └── ShowTextActivity.java │ │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ │ ├── drawable-xxxhdpi │ │ └── ic_launcher.png │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_show_text.xml │ │ ├── values-v13 │ │ └── styles.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── LICENSE ├── readme.md ├── readme ├── inspector.png └── open_files.png └── uix ├── espresso_dump.uix └── espresso_image.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | local.properties 4 | build 5 | .gradle 6 | # Eclipse project files 7 | .project 8 | .settings/ 9 | .classpath 10 | -------------------------------------------------------------------------------- /BasicSample/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | local.properties 3 | .idea 4 | .DS_Store 5 | build 6 | *.iml 7 | -------------------------------------------------------------------------------- /BasicSample/README.md: -------------------------------------------------------------------------------- 1 | # Basic sample for Espresso 2 | 3 | *If you are new to Espresso, try this sample first.* 4 | 5 | This project uses the Gradle build system. You don't need an IDE to build and execute it but Android Studio is recommended. 6 | 7 | 1. Download the project code, preferably using `git clone`. 8 | 1. Open the Android SDK Manager (*Tools* Menu | *Android*) and make sure you have installed the *Android Support Repository* under *Extras*. (For more Information click [here](http://developer.android.com/tools/testing-support-library/index.html#setup)) 9 | 1. In Android Studio, select *File* | *Open...* and point to the `./build.gradle` file. 10 | 1. Check out the relevant code: 11 | * The application under test is located in `src/main/java` 12 | * Tests are in `src/androidTest/java` 13 | 1. Create the test configuration with a custom runner: `android.support.test.runner.AndroidJUnitRunner` 14 | * Open *Run* menu | *Edit Configurations* 15 | * Add a new *Android Tests* configuration 16 | * Choose a module 17 | * Add a *Specific instrumentation runner*: `android.support.test.runner.AndroidJUnitRunner` 18 | 1. Connect a device or start an emulator 19 | * Turn animations off. 20 | (On your device, under Settings->Developer options disable the following 3 settings: "Window animation scale", "Transition animation scale" and "Animator duration scale") 21 | 1. Run the newly created configuration 22 | 23 | The application will be started on the device/emulator and a series of actions will be performed automatically. 24 | 25 | If you are using Android Studio, the *Run* window will show the test results. 26 | -------------------------------------------------------------------------------- /BasicSample/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion '23.0.1' 6 | defaultConfig { 7 | applicationId "com.example.android.testing.espresso.BasicSample" 8 | minSdkVersion 10 9 | targetSdkVersion 23 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | packagingOptions { 16 | exclude 'LICENSE.txt' 17 | } 18 | lintOptions { 19 | abortOnError false 20 | } 21 | productFlavors { 22 | } 23 | } 24 | 25 | dependencies { 26 | // App dependencies 27 | compile 'com.android.support:support-annotations:23.0.1' 28 | compile 'com.google.guava:guava:18.0' 29 | // Testing-only dependencies 30 | // Force usage of support annotations in the test app, since it is internally used by the runner module. 31 | androidTestCompile 'com.android.support:support-annotations:23.0.1' 32 | androidTestCompile 'com.android.support.test:runner:0.4.1' 33 | androidTestCompile 'com.android.support.test:rules:0.4.1' 34 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' 35 | androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1' 36 | 37 | } 38 | -------------------------------------------------------------------------------- /BasicSample/app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/ChangeTextBehaviorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, The Android Open Source Project 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 | package com.example.android.testing.espresso.BasicSample; 18 | 19 | import org.junit.Before; 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | 24 | import android.app.Activity; 25 | import android.support.test.espresso.action.ViewActions; 26 | import android.support.test.espresso.matcher.ViewMatchers; 27 | import android.support.test.rule.ActivityTestRule; 28 | import android.support.test.runner.AndroidJUnit4; 29 | import android.test.ActivityInstrumentationTestCase2; 30 | import android.test.suitebuilder.annotation.LargeTest; 31 | 32 | import static android.support.test.espresso.Espresso.onView; 33 | import static android.support.test.espresso.action.ViewActions.click; 34 | import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; 35 | import static android.support.test.espresso.action.ViewActions.typeText; 36 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 37 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 38 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 39 | 40 | 41 | /** 42 | * Basic tests showcasing simple view matchers and actions like {@link ViewMatchers#withId}, 43 | * {@link ViewActions#click} and {@link ViewActions#typeText}. 44 | *

45 | * Note that there is no need to tell Espresso that a view is in a different {@link Activity}. 46 | */ 47 | @RunWith(AndroidJUnit4.class) 48 | @LargeTest 49 | public class ChangeTextBehaviorTest { 50 | 51 | public static final String STRING_TO_BE_TYPED = "Espresso"; 52 | 53 | /** 54 | * A JUnit {@link Rule @Rule} to launch your activity under test. This is a replacement 55 | * for {@link ActivityInstrumentationTestCase2}. 56 | *

57 | * Rules are interceptors which are executed for each test method and will run before 58 | * any of your setup code in the {@link Before @Before} method. 59 | *

60 | * {@link ActivityTestRule} will create and launch of the activity for you and also expose 61 | * the activity under test. To get a reference to the activity you can use 62 | * the {@link ActivityTestRule#getActivity()} method. 63 | */ 64 | @Rule 65 | public ActivityTestRule mActivityRule = new ActivityTestRule<>( 66 | MainActivity.class); 67 | 68 | @Test 69 | public void changeText_sameActivity() { 70 | // Type text and then press the button. 71 | onView(withId(R.id.editTextUserInput)) 72 | .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard()); 73 | onView(withId(R.id.changeTextBt)).perform(click()); 74 | 75 | // Check that the text was changed. 76 | onView(withId(R.id.textToBeChanged)).check(matches(withText(STRING_TO_BE_TYPED))); 77 | 78 | // Dump. Download with adb pull /sdcard/Android/data/com.example.android.testing.espresso.BasicSample/cache/dump 79 | ViewToUix.dumpView(); 80 | } 81 | } -------------------------------------------------------------------------------- /BasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/GetRootView.java: -------------------------------------------------------------------------------- 1 | package com.example.android.testing.espresso.BasicSample; 2 | 3 | import android.support.test.espresso.UiController; 4 | import android.support.test.espresso.ViewAction; 5 | import android.support.test.espresso.matcher.ViewMatchers; 6 | import android.view.View; 7 | 8 | import org.hamcrest.Matcher; 9 | import org.hamcrest.Matchers; 10 | 11 | public class GetRootView implements ViewAction { 12 | View rootView; 13 | 14 | @Override 15 | public Matcher getConstraints() { 16 | return Matchers.allOf(ViewMatchers.isRoot()); 17 | } 18 | 19 | @Override 20 | public String getDescription() { 21 | return "get root view"; 22 | } 23 | 24 | @Override 25 | public void perform(UiController uiController, View view) { 26 | rootView = view; 27 | } 28 | 29 | public View getRootView() { 30 | return rootView; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BasicSample/app/src/androidTest/java/com/example/android/testing/espresso/BasicSample/ViewToUix.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 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 | * Copyright (C) 2012 The Android Open Source Project 19 | * 20 | * Licensed under the Apache License, Version 2.0 (the "License"); 21 | * you may not use this file except in compliance with the License. 22 | * You may obtain a copy of the License at 23 | * 24 | * http://www.apache.org/licenses/LICENSE-2.0 25 | * 26 | * Unless required by applicable law or agreed to in writing, software 27 | * distributed under the License is distributed on an "AS IS" BASIS, 28 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29 | * See the License for the specific language governing permissions and 30 | * limitations under the License. 31 | */ 32 | package com.example.android.testing.espresso.BasicSample; 33 | 34 | import android.content.res.Resources; 35 | import android.graphics.Rect; 36 | import android.os.Build; 37 | import android.support.test.InstrumentationRegistry; 38 | import android.support.test.espresso.core.deps.guava.base.Charsets; 39 | import android.support.test.espresso.core.deps.guava.io.Files; 40 | import android.support.test.uiautomator.UiDevice; 41 | import android.util.Log; 42 | import android.util.Printer; 43 | import android.util.StringBuilderPrinter; 44 | import android.view.View; 45 | import android.view.ViewGroup; 46 | import android.view.accessibility.AccessibilityNodeInfo; 47 | import android.view.inputmethod.EditorInfo; 48 | import android.view.inputmethod.InputConnection; 49 | import android.widget.Checkable; 50 | import android.widget.TextView; 51 | 52 | import org.junit.Assert; 53 | import org.w3c.dom.Document; 54 | import org.w3c.dom.Element; 55 | 56 | import java.io.BufferedOutputStream; 57 | import java.io.ByteArrayOutputStream; 58 | import java.io.File; 59 | import java.util.HashMap; 60 | import java.util.Map; 61 | 62 | import javax.xml.parsers.DocumentBuilderFactory; 63 | import javax.xml.parsers.ParserConfigurationException; 64 | import javax.xml.transform.OutputKeys; 65 | import javax.xml.transform.Transformer; 66 | import javax.xml.transform.TransformerFactory; 67 | import javax.xml.transform.dom.DOMSource; 68 | import javax.xml.transform.stream.StreamResult; 69 | 70 | import static android.support.test.InstrumentationRegistry.getTargetContext; 71 | import static android.support.test.espresso.Espresso.onView; 72 | import static android.support.test.espresso.matcher.ViewMatchers.isRoot; 73 | import static org.hamcrest.Matchers.allOf; 74 | 75 | /** Converts Espresso view tree to XML format that works with uiautomatorviewer **/ 76 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/ByXPath.java#L198 77 | public class ViewToUix { 78 | private static final File dumpDir = new File(getTargetContext().getExternalCacheDir(), "dump"); 79 | private static final File DUMP_XML = new File(dumpDir, "espresso_dump.uix"); 80 | private static final File DUMP_PNG = new File(dumpDir, "espresso_image.png"); 81 | private static final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 82 | private static final String TAG = "espresso"; 83 | 84 | // document needs to be static so that when buildDomNode is called recursively 85 | // on children they are in the same document to be appended. 86 | private static Document document; 87 | // The two maps should be kept in sync 88 | private static final Map TO_DOM_MAP = 89 | new HashMap<>(); 90 | 91 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/ByXPath.java#L64 92 | private static void clearData() { 93 | TO_DOM_MAP.clear(); 94 | document = null; 95 | } 96 | 97 | /** 98 | * Returns the DOM node representing this UiElement. 99 | */ 100 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/ByXPath.java#L127 101 | private static Element getDomNode(View view, int index) { 102 | Element domNode = TO_DOM_MAP.get(view); 103 | if (domNode == null) { 104 | domNode = buildDomNode(view, index); 105 | } 106 | return domNode; 107 | } 108 | 109 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/ByXPath.java#L113 110 | private static Document getDocument() { 111 | if (document == null) { 112 | try { 113 | document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 114 | } catch (ParserConfigurationException e) { 115 | throw new RuntimeException(e); 116 | } 117 | } 118 | return document; 119 | } 120 | 121 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/XPaths.java 122 | private static String tag(String className) { 123 | return simpleClassName(className); 124 | } 125 | 126 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/XPaths.java 127 | private static String simpleClassName(String name) { 128 | // the nth anonymous class has a class name ending in "Outer$n" 129 | // and local inner classes have names ending in "Outer.$1Inner" 130 | name = name.replaceAll("\\$[0-9]+", "\\$"); 131 | 132 | // we want the name of the inner class all by its lonesome 133 | int start = name.lastIndexOf('$'); 134 | 135 | // if this isn't an inner class, just find the start of the 136 | // top level class name. 137 | if (start == -1) { 138 | start = name.lastIndexOf('.'); 139 | } 140 | return name.substring(start + 1); 141 | } 142 | 143 | 144 | // https://github.com/bootstraponline/uiautomator2/blob/fa866903ece601742d694279c03a6db48a953c22/src/main/java/android/support/test/uiautomator/AccessibilityNodeInfoDumper.java#L166 145 | private static String safeCharSeqToString(CharSequence cs) { 146 | if (cs == null) 147 | return ""; 148 | else { 149 | return stripInvalidXMLChars(cs); 150 | } 151 | } 152 | 153 | // https://github.com/bootstraponline/uiautomator2/blob/fa866903ece601742d694279c03a6db48a953c22/src/main/java/android/support/test/uiautomator/AccessibilityNodeInfoDumper.java#L174 154 | private static String stripInvalidXMLChars(CharSequence cs) { 155 | StringBuffer ret = new StringBuffer(); 156 | char ch; 157 | /* http://www.w3.org/TR/xml11/#charsets 158 | [#x1-#x8], [#xB-#xC], [#xE-#x1F], [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF], 159 | [#x1FFFE-#x1FFFF], [#x2FFFE-#x2FFFF], [#x3FFFE-#x3FFFF], 160 | [#x4FFFE-#x4FFFF], [#x5FFFE-#x5FFFF], [#x6FFFE-#x6FFFF], 161 | [#x7FFFE-#x7FFFF], [#x8FFFE-#x8FFFF], [#x9FFFE-#x9FFFF], 162 | [#xAFFFE-#xAFFFF], [#xBFFFE-#xBFFFF], [#xCFFFE-#xCFFFF], 163 | [#xDFFFE-#xDFFFF], [#xEFFFE-#xEFFFF], [#xFFFFE-#xFFFFF], 164 | [#x10FFFE-#x10FFFF]. 165 | */ 166 | for (int i = 0; i < cs.length(); i++) { 167 | ch = cs.charAt(i); 168 | 169 | if ((ch >= 0x1 && ch <= 0x8) || (ch >= 0xB && ch <= 0xC) || (ch >= 0xE && ch <= 0x1F) || 170 | (ch >= 0x7F && ch <= 0x84) || (ch >= 0x86 && ch <= 0x9f) || 171 | (ch >= 0xFDD0 && ch <= 0xFDDF) || (ch >= 0x1FFFE && ch <= 0x1FFFF) || 172 | (ch >= 0x2FFFE && ch <= 0x2FFFF) || (ch >= 0x3FFFE && ch <= 0x3FFFF) || 173 | (ch >= 0x4FFFE && ch <= 0x4FFFF) || (ch >= 0x5FFFE && ch <= 0x5FFFF) || 174 | (ch >= 0x6FFFE && ch <= 0x6FFFF) || (ch >= 0x7FFFE && ch <= 0x7FFFF) || 175 | (ch >= 0x8FFFE && ch <= 0x8FFFF) || (ch >= 0x9FFFE && ch <= 0x9FFFF) || 176 | (ch >= 0xAFFFE && ch <= 0xAFFFF) || (ch >= 0xBFFFE && ch <= 0xBFFFF) || 177 | (ch >= 0xCFFFE && ch <= 0xCFFFF) || (ch >= 0xDFFFE && ch <= 0xDFFFF) || 178 | (ch >= 0xEFFFE && ch <= 0xEFFFF) || (ch >= 0xFFFFE && ch <= 0xFFFFF) || 179 | (ch >= 0x10FFFE && ch <= 0x10FFFF)) 180 | ret.append("."); 181 | else 182 | ret.append(ch); 183 | } 184 | return ret.toString(); 185 | } 186 | 187 | // https://raw.githubusercontent.com/bootstraponline/uiautomator2/android-support-test/src/main/java/android/support/test/uiautomator/AccessibilityNodeInfoHelper.java 188 | /** 189 | * Returns the node's bounds clipped to the size of the display 190 | * 191 | * @param node 192 | * @param width pixel width of the display 193 | * @param height pixel height of the display 194 | * @return null if node is null, else a Rect containing visible bounds 195 | */ 196 | static Rect getVisibleBoundsInScreen(AccessibilityNodeInfo node, int width, int height) { 197 | if (node == null) { 198 | return null; 199 | } 200 | // targeted node's bounds 201 | Rect nodeRect = new Rect(); 202 | node.getBoundsInScreen(nodeRect); 203 | 204 | Rect displayRect = new Rect(); 205 | displayRect.top = 0; 206 | displayRect.left = 0; 207 | displayRect.right = width; 208 | displayRect.bottom = height; 209 | 210 | // may change rect. must call 211 | //noinspection ResourceType 212 | nodeRect.intersect(displayRect); 213 | return nodeRect; 214 | } 215 | 216 | private static void attr(Element element, String attribute, CharSequence value) { 217 | element.setAttribute(attribute, safeCharSeqToString(value)); 218 | } 219 | 220 | private static void attr(Element element, String attribute, Boolean value) { 221 | element.setAttribute(attribute, Boolean.toString(value)); 222 | } 223 | 224 | private static void attr(Element element, String attribute, Integer value) { 225 | element.setAttribute(attribute, Integer.toString(value)); 226 | } 227 | 228 | private static void attr(Element element, String attribute, Float value) { 229 | element.setAttribute(attribute, Float.toString(value)); 230 | } 231 | 232 | // https://github.com/bootstraponline/platform_frameworks_testing/blob/fb9996b698387e62bd70fd8e42e4a5f0304d2d81/espresso/core/src/main/java/android/support/test/espresso/util/HumanReadables.java#L237 233 | private static void innerDescribe(Element element, TextView textBox) { 234 | if (null != textBox.getText()) { 235 | attr(element, "text", textBox.getText()); 236 | } 237 | 238 | if (null != textBox.getError()) { 239 | attr(element, "error-text", textBox.getError()); 240 | } 241 | 242 | if (null != textBox.getHint()) { 243 | attr(element, "hint", textBox.getHint()); 244 | } 245 | 246 | attr(element, "input-type", textBox.getInputType()); 247 | attr(element, "ime-target", textBox.isInputMethodTarget()); 248 | attr(element, "has-links", textBox.getUrls().length > 0); 249 | } 250 | 251 | // https://github.com/bootstraponline/platform_frameworks_testing/blob/fb9996b698387e62bd70fd8e42e4a5f0304d2d81/espresso/core/src/main/java/android/support/test/espresso/util/HumanReadables.java#L255 252 | private static void innerDescribe(Element element, Checkable checkable) { 253 | attr(element, "is-checked", checkable.isChecked()); 254 | } 255 | 256 | // https://github.com/bootstraponline/platform_frameworks_testing/blob/fb9996b698387e62bd70fd8e42e4a5f0304d2d81/espresso/core/src/main/java/android/support/test/espresso/util/HumanReadables.java#L259 257 | private static void innerDescribe(Element element, ViewGroup viewGroup) { 258 | attr(element, "child-count", viewGroup.getChildCount()); 259 | } 260 | 261 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/ByXPath.java#L136 262 | private static Element buildDomNode(View view, int index) { 263 | Element element = getDocument().createElement(tag("node")); 264 | TO_DOM_MAP.put(view, element); 265 | 266 | AccessibilityNodeInfo node = view.createAccessibilityNodeInfo(); 267 | 268 | if (Build.VERSION.SDK_INT >= 18) { 269 | // Manually set viewId since Espresso has no easy way of enabling 270 | // AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS 271 | // https://android.googlesource.com/platform/frameworks/base/+/47b50333c110194565498011379988e5c05f7890/core/java/android/view/View.java 272 | try { 273 | String viewId = view.getResources().getResourceName(view.getId()); 274 | node.setViewIdResourceName(viewId); 275 | } catch (Resources.NotFoundException nfe) { 276 | /* ignore */ 277 | } 278 | } 279 | 280 | // https://github.com/bootstraponline/uiautomator2/blob/085063a4e394f8e9c6195ec00cdf81a2c2b4f1c9/src/main/java/android/support/test/uiautomator/AccessibilityNodeInfoDumper.java 281 | attr(element, "index", index); 282 | attr(element, "text", node.getText()); 283 | if (Build.VERSION.SDK_INT >= 18) { 284 | attr(element, "resource-id", node.getViewIdResourceName()); // aka res-name 285 | } 286 | // node.getClassName() returns the accessibility class which isn't used by Espresso 287 | // espresso requires the concrete class. uiautomatorviewer looks nicer using simple names 288 | attr(element, "class", view.getClass().getSimpleName()); 289 | attr(element, "package", node.getPackageName()); 290 | attr(element, "content-desc", node.getContentDescription()); // aka desc 291 | attr(element, "checkable", node.isCheckable()); 292 | attr(element, "checked", node.isChecked()); 293 | attr(element, "clickable", node.isClickable()); // aka is-clickable 294 | attr(element, "enabled", node.isEnabled()); // aka is-enabled 295 | attr(element, "focusable", node.isFocusable()); // aka is-focusable 296 | attr(element, "focused", node.isFocused()); // aka is-focus 297 | attr(element, "scrollable", node.isScrollable()); 298 | attr(element, "long-clickable", node.isLongClickable()); 299 | attr(element, "password", node.isPassword()); 300 | attr(element, "selected", node.isSelected()); // aka is-selected 301 | 302 | int width = device.getDisplayWidth(); 303 | int height = device.getDisplayHeight(); 304 | 305 | element.setAttribute("bounds", getVisibleBoundsInScreen(node, width, height).toShortString()); 306 | 307 | // extra attributes 308 | attr(element, "full-class", view.getClass().getName()); // fully qualified class 309 | attr(element, "acc-class", node.getClassName()); // accessibility class 310 | 311 | // Espresso human readable attributes 312 | // https://github.com/bootstraponline/platform_frameworks_testing/blob/android-support-test/espresso/core/src/main/java/android/support/test/espresso/util/HumanReadables.java#L161 313 | attr(element, "view-id", view.getId()); // aka 'id' 314 | 315 | switch (view.getVisibility()) { 316 | case View.GONE: 317 | attr(element, "visibility", "GONE"); 318 | break; 319 | case View.INVISIBLE: 320 | attr(element, "visibility", "INVISIBLE"); 321 | break; 322 | case View.VISIBLE: 323 | attr(element, "visibility", "VISIBLE"); 324 | break; 325 | default: 326 | attr(element, "visibility", view.getVisibility()); 327 | } 328 | 329 | attr(element, "width", view.getWidth()); 330 | attr(element, "height", view.getHeight()); 331 | attr(element, "has-focus", view.hasFocus()); 332 | attr(element, "has-focusable", view.hasFocusable()); 333 | attr(element, "has-window-focus", view.hasWindowFocus()); 334 | attr(element, "is-layout-requested", view.isLayoutRequested()); 335 | 336 | if (null != view.getRootView()) { 337 | // pretty much only true in unit-tests. 338 | attr(element, "root-is-layout-requested", view.getRootView().isLayoutRequested()); 339 | } 340 | 341 | EditorInfo ei = new EditorInfo(); 342 | InputConnection ic = view.onCreateInputConnection(ei); 343 | boolean hasInputConnection = ic != null; 344 | attr(element, "has-input-connection", hasInputConnection); 345 | if (hasInputConnection) { 346 | StringBuilder sb = new StringBuilder(); 347 | sb.append("["); 348 | Printer p = new StringBuilderPrinter(sb); 349 | ei.dump(p, ""); 350 | sb.append("]"); 351 | attr(element, "editor-info", sb.toString().replace("\n", " ")); 352 | } 353 | 354 | if (Build.VERSION.SDK_INT > 10) { 355 | attr(element, "view-x", view.getX()); 356 | attr(element, "view-y", view.getY()); 357 | } 358 | 359 | if (view instanceof TextView) { 360 | innerDescribe(element, (TextView) view); 361 | } 362 | 363 | if (view instanceof Checkable) { 364 | innerDescribe(element, (Checkable) view); 365 | } 366 | 367 | if (view instanceof ViewGroup) { 368 | innerDescribe(element, (ViewGroup) view); 369 | } 370 | 371 | // https://github.com/bootstraponline/espresso_2_clone/blob/436878d48678428fdbef57bdc90153833c1a3d61/espresso-core-2.0-sources/android/support/test/espresso/util/TreeIterables.java#L199 372 | if (view instanceof ViewGroup) { 373 | ViewGroup group = (ViewGroup) view; 374 | int childCount = group.getChildCount(); 375 | for (int i = 0; i < childCount; i++) { 376 | element.appendChild(getDomNode(group.getChildAt(i), i)); 377 | } 378 | } 379 | 380 | return element; 381 | } 382 | 383 | // https://github.com/appium/droiddriver/blob/c1f89919ed5e88650548f34fe8ca9e5d458a41df/src/io/appium/droiddriver/finders/ByXPath.java#L198 384 | private static byte[] dumpDomBytes(View view) throws Exception { 385 | String path = DUMP_XML.toString(); 386 | BufferedOutputStream bos = null; 387 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 388 | 389 | try { 390 | bos = new BufferedOutputStream(baos); 391 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 392 | transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 393 | clearData(); 394 | 395 | Element domNode = getDomNode(view, 0); 396 | transformer.transform(new DOMSource(domNode), new StreamResult(bos)); 397 | Log.i(TAG, "Wrote dom to " + path); 398 | } catch (Exception e) { 399 | Log.e(TAG, "Failed to transform node", e); 400 | throw e; 401 | } finally { 402 | clearData(); 403 | if (bos != null) { 404 | try { 405 | bos.flush(); 406 | bos.close(); 407 | } catch (Exception e) { 408 | // ignore 409 | } 410 | } 411 | } 412 | return baos.toByteArray(); 413 | } 414 | 415 | /** 416 | * Dumps view as a String 417 | **/ 418 | private static String dumpDom(View view) throws Exception { 419 | return new String(dumpDomBytes(view), Charsets.UTF_8); 420 | } 421 | 422 | /** 423 | * Dumps view xml and png. 424 | * Download with adb pull /sdcard/Android/data/com.example.android.testing.espresso.BasicSample/cache/dump 425 | **/ 426 | private static boolean dumpToFile(View view) { 427 | boolean result = false; 428 | dumpDir.mkdirs(); 429 | DUMP_PNG.delete(); 430 | DUMP_XML.delete(); 431 | try { 432 | device.takeScreenshot(DUMP_PNG); 433 | Files.write(dumpDomBytes(view), DUMP_XML); 434 | result = true; 435 | } catch (Exception e) { 436 | // ignored 437 | } 438 | return result; 439 | } 440 | 441 | /** 442 | * Invoke ViewToUix.dumpView(); from a test to dump the view 443 | **/ 444 | public static void dumpView() { 445 | GetRootView getRoot = new GetRootView(); 446 | onView(allOf(isRoot())).perform(getRoot); 447 | View rootView = getRoot.getRootView(); 448 | 449 | boolean dumpResult = ViewToUix.dumpToFile(rootView); 450 | Assert.assertTrue(dumpResult); 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/testing/espresso/BasicSample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, The Android Open Source Project 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 | package com.example.android.testing.espresso.BasicSample; 18 | 19 | import android.app.Activity; 20 | import android.content.Intent; 21 | import android.os.Bundle; 22 | import android.view.View; 23 | import android.widget.EditText; 24 | import android.widget.TextView; 25 | 26 | /** 27 | * An {@link Activity} that gets a text string from the user and displays it back when the user 28 | * clicks on one of the two buttons. The first one shows it in the same activity and the second 29 | * one opens another activity and displays the message. 30 | */ 31 | public class MainActivity extends Activity implements View.OnClickListener { 32 | 33 | // The TextView used to display the message inside the Activity. 34 | private TextView mTextView; 35 | 36 | // The EditText where the user types the message. 37 | private EditText mEditText; 38 | 39 | @Override 40 | protected void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | setContentView(R.layout.activity_main); 43 | 44 | // Set the listeners for the buttons. 45 | findViewById(R.id.changeTextBt).setOnClickListener(this); 46 | findViewById(R.id.activityChangeTextBtn).setOnClickListener(this); 47 | 48 | mTextView = (TextView) findViewById(R.id.textToBeChanged); 49 | mEditText = (EditText) findViewById(R.id.editTextUserInput); 50 | } 51 | 52 | @Override 53 | public void onClick(View view) { 54 | // Get the text from the EditText view. 55 | final String text = mEditText.getText().toString(); 56 | 57 | switch (view.getId()) { 58 | case R.id.changeTextBt: 59 | // First button's interaction: set a text in a text view. 60 | mTextView.setText(text); 61 | break; 62 | case R.id.activityChangeTextBtn: 63 | // Second button's interaction: start an activity and send a message to it. 64 | Intent intent = ShowTextActivity.newStartIntent(this, text); 65 | startActivity(intent); 66 | break; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/java/com/example/android/testing/espresso/BasicSample/ShowTextActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015, The Android Open Source Project 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 | package com.example.android.testing.espresso.BasicSample; 18 | 19 | import com.google.common.base.Strings; 20 | 21 | import android.app.Activity; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.os.Bundle; 25 | import android.widget.TextView; 26 | 27 | /** 28 | * A simple {@link Activity} that shows a message. 29 | */ 30 | public class ShowTextActivity extends Activity { 31 | 32 | // The name of the extra data sent through an {@link Intent}. 33 | public final static String KEY_EXTRA_MESSAGE = 34 | "com.example.android.testing.espresso.basicsample.MESSAGE"; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_show_text); 40 | 41 | // Get the message from the Intent. 42 | Intent intent = getIntent(); 43 | String message = Strings.nullToEmpty(intent.getStringExtra(KEY_EXTRA_MESSAGE)); 44 | 45 | // Show message. 46 | ((TextView)findViewById(R.id.show_text_view)).setText(message); 47 | } 48 | 49 | /** 50 | * Creates an {@link Intent} for {@link ShowTextActivity} with the message to be displayed. 51 | * @param context the {@link Context} where the {@link Intent} will be used 52 | * @param message a {@link String} with text to be displayed 53 | * @return an {@link Intent} used to start {@link ShowTextActivity} 54 | */ 55 | static protected Intent newStartIntent(Context context, String message) { 56 | Intent newIntent = new Intent(context, ShowTextActivity.class); 57 | newIntent.putExtra(KEY_EXTRA_MESSAGE, message); 58 | return newIntent; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstraponline/view_to_uix/636d942c5f0a01cc6e830f89b880c05d3280663f/BasicSample/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstraponline/view_to_uix/636d942c5f0a01cc6e830f89b880c05d3280663f/BasicSample/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstraponline/view_to_uix/636d942c5f0a01cc6e830f89b880c05d3280663f/BasicSample/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstraponline/view_to_uix/636d942c5f0a01cc6e830f89b880c05d3280663f/BasicSample/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootstraponline/view_to_uix/636d942c5f0a01cc6e830f89b880c05d3280663f/BasicSample/app/src/main/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /BasicSample/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 24 | 25 | 26 | 35 | 36 | 37 | 43 | 44 |