• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.platform.uiautomator_helpers
18 
19 import android.animation.TimeInterpolator
20 import android.app.Instrumentation
21 import android.content.Context
22 import android.graphics.PointF
23 import android.os.Bundle
24 import android.platform.uiautomator_helpers.TracingUtils.trace
25 import android.platform.uiautomator_helpers.WaitUtils.ensureThat
26 import android.platform.uiautomator_helpers.WaitUtils.waitFor
27 import android.platform.uiautomator_helpers.WaitUtils.waitForNullable
28 import android.platform.uiautomator_helpers.WaitUtils.waitForValueToSettle
29 import android.util.Log
30 import androidx.test.platform.app.InstrumentationRegistry
31 import androidx.test.uiautomator.BySelector
32 import androidx.test.uiautomator.UiDevice
33 import androidx.test.uiautomator.UiObject2
34 import java.io.IOException
35 import java.time.Duration
36 
37 private const val TAG = "DeviceHelpers"
38 
39 object DeviceHelpers {
40     private val SHORT_WAIT = Duration.ofMillis(1500)
41     private val LONG_WAIT = Duration.ofSeconds(10)
42     private val DOUBLE_TAP_INTERVAL = Duration.ofMillis(100)
43 
44     private val instrumentationRegistry = InstrumentationRegistry.getInstrumentation()
45 
46     @JvmStatic
47     val uiDevice: UiDevice
48         get() = UiDevice.getInstance(instrumentationRegistry)
49 
50     @JvmStatic
51     val context: Context
52         get() = instrumentationRegistry.targetContext
53 
54     /**
55      * Waits for an object to be visible and returns it.
56      *
57      * Throws an error with message provided by [errorProvider] if the object is not found.
58      *
59      * @deprecated Use [DeviceHelpers.waitForObj] instead.
60      */
waitForObjnull61     fun UiDevice.waitForObj(
62         selector: BySelector,
63         timeout: Duration = LONG_WAIT,
64         errorProvider: () -> String = { "Object $selector not found" },
<lambda>null65     ): UiObject2 = waitFor("$selector object", timeout, errorProvider) { findObject(selector) }
66 
67     /**
68      * Waits for an object to be visible and returns it.
69      *
70      * Throws an error with message provided by [errorProvider] if the object is not found.
71      */
waitForObjnull72     fun waitForObj(
73         selector: BySelector,
74         timeout: Duration = LONG_WAIT,
75         errorProvider: () -> String = { "Object $selector not found" },
76     ): UiObject2 = uiDevice.waitForObj(selector, timeout, errorProvider)
77 
78     /**
79      * Waits for an object to be visible and returns it.
80      *
81      * Throws an error with message provided by [errorProvider] if the object is not found.
82      */
waitForObjnull83     fun UiObject2.waitForObj(
84         selector: BySelector,
85         timeout: Duration = LONG_WAIT,
86         errorProvider: () -> String = { "Object $selector not found" },
<lambda>null87     ): UiObject2 = waitFor("$selector object", timeout, errorProvider) { findObject(selector) }
88 
89     /**
90      * Waits for an object to be visible and returns it. Returns `null` if the object is not found.
91      *
92      * @deprecated use [DeviceHelpers.waitForNullableObj] instead.
93      */
waitForNullableObjnull94     fun UiDevice.waitForNullableObj(
95         selector: BySelector,
96         timeout: Duration = SHORT_WAIT,
97     ): UiObject2? = waitForNullable("nullable $selector objects", timeout) { findObject(selector) }
98 
99     /**
100      * Waits for an object to be visible and returns it. Returns `null` if the object is not found.
101      */
waitForNullableObjnull102     fun waitForNullableObj(
103         selector: BySelector,
104         timeout: Duration = SHORT_WAIT,
105     ): UiObject2? = uiDevice.waitForNullableObj(selector, timeout)
106 
107     /**
108      * Waits for objects matched by [selector] to be visible and returns them. Returns `null` if no
109      * objects are found
110      */
111     fun UiDevice.waitForNullableObjects(
112         selector: BySelector,
113         timeout: Duration = SHORT_WAIT,
114     ): List<UiObject2>? = waitForNullable("$selector objects", timeout) { findObjects(selector) }
115 
116     /**
117      * Asserts visibility of a [selector], waiting for [timeout] until visibility matches the
118      * expected.
119      *
120      * If [container] is provided, the object is searched only inside of it.
121      */
122     @JvmOverloads
123     @JvmStatic
assertVisibilitynull124     fun UiDevice.assertVisibility(
125         selector: BySelector,
126         visible: Boolean = true,
127         timeout: Duration = LONG_WAIT,
128         errorProvider: (() -> String)? = null,
129     ) {
130         ensureThat("$selector is ${visible.asVisibilityBoolean()}", timeout, errorProvider) {
131             hasObject(selector) == visible
132         }
133     }
134 
Booleannull135     private fun Boolean.asVisibilityBoolean(): String =
136         when (this) {
137             true -> "visible"
138             false -> "invisible"
139         }
140 
141     /**
142      * Asserts visibility of a [selector] inside this [UiObject2], waiting for [timeout] until
143      * visibility matches the expected.
144      */
assertVisibilitynull145     fun UiObject2.assertVisibility(
146         selector: BySelector,
147         visible: Boolean,
148         timeout: Duration = LONG_WAIT,
149         errorProvider: (() -> String)? = null,
150     ) {
151         ensureThat(
152             "$selector is ${visible.asVisibilityBoolean()} inside $this",
153             timeout,
154             errorProvider
155         ) {
156             hasObject(selector) == visible
157         }
158     }
159 
160     /** Asserts that a this selector is visible. Throws otherwise. */
BySelectornull161     fun BySelector.assertVisible(
162         timeout: Duration = LONG_WAIT,
163         errorProvider: (() -> String)? = null
164     ) {
165         uiDevice.assertVisibility(
166             selector = this,
167             visible = true,
168             timeout = timeout,
169             errorProvider = errorProvider
170         )
171     }
172     /** Asserts that a this selector is invisible. Throws otherwise. */
BySelectornull173     fun BySelector.assertInvisible(
174         timeout: Duration = LONG_WAIT,
175         errorProvider: (() -> String)? = null
176     ) {
177         uiDevice.assertVisibility(
178             selector = this,
179             visible = false,
180             timeout = timeout,
181             errorProvider = errorProvider
182         )
183     }
184 
185     /**
186      * Executes a shell command on the device.
187      *
188      * Adds some logging. Throws [RuntimeException] In case of failures.
189      */
190     @JvmStatic
shellnull191     fun UiDevice.shell(command: String): String =
192         trace("Executing shell command: $command") {
193             Log.d(TAG, "Executing Shell Command: $command")
194             return try {
195                 executeShellCommand(command)
196             } catch (e: IOException) {
197                 Log.e(TAG, "IOException Occurred.", e)
198                 throw RuntimeException(e)
199             }
200         }
201 
202     /** Perform double tap at specified x and y position */
203     @JvmStatic
UiDevicenull204     fun UiDevice.doubleTapAt(x: Int, y: Int) {
205         click(x, y)
206         Thread.sleep(DOUBLE_TAP_INTERVAL.toMillis())
207         click(x, y)
208     }
209 
210     /**
211      * Aims at replacing [UiDevice.swipe].
212      *
213      * This should be used instead of [UiDevice.swipe] as it causes less flakiness. See
214      * [BetterSwipe].
215      */
216     @JvmStatic
UiDevicenull217     fun UiDevice.betterSwipe(
218         startX: Int,
219         startY: Int,
220         endX: Int,
221         endY: Int,
222         interpolator: TimeInterpolator = FLING_GESTURE_INTERPOLATOR
223     ) {
224         trace("Swiping ($startX,$startY) -> ($endX,$endY)") {
225             BetterSwipe.from(PointF(startX.toFloat(), startY.toFloat()))
226                 .to(PointF(endX.toFloat(), endY.toFloat()), interpolator = interpolator)
227                 .release()
228         }
229     }
230 
231     /** [message] will be visible to the terminal when using `am instrument`. */
printInstrumentationStatusnull232     fun printInstrumentationStatus(tag: String, message: String) {
233         val result =
234             Bundle().apply {
235                 putString(Instrumentation.REPORT_KEY_STREAMRESULT, "[$tag]: $message")
236             }
237         instrumentationRegistry.sendStatus(/* resultCode= */ 0, result)
238     }
239 
240     /**
241      * Returns whether the screen is on.
242      *
243      * As this uses [waitForValueToSettle], it is resilient to fast screen on/off happening.
244      */
245     @JvmStatic
246     val UiDevice.isScreenOnSettled: Boolean
<lambda>null247         get() = waitForValueToSettle("Screen on") { isScreenOn }
248 }
249