How are you going to use UiAutomator in Jetpack Compose apps?
UiAutomator is a UI testing framework appropriate for testing throughout the system and put in apps. It permits you to work together with the seen parts on a display no matter what app is in focus.
Whereas many libraries depend on operating as a part of the app’s course of, UiAutomator doesn’t. It allows you to write integration assessments, which know nothing concerning the app’s internals. That is necessary for producing Baseline Profiles and efficiency testing (utilizing Jetpack Macrobenchmark) the place operating a profileable launch construct is advisable.
As a way to ship dependable outcomes, the testing framework must work together with the app however with out manipulating the app straight. And UiAutomator does simply that, by delivering enter occasions in a method an everyday consumer would, however extra constant.
UiAutomator has a number of methods of accessing UI parts on display relying on a component sort. You should utilize the By
selector class for accessing parts. You should utilize By.textual content()
to entry parts by textual content label, By.desc()
to entry by contentDescription
, by aspect flags akin to By.scrollable()
, By.checkable()
, By.clickable()
, and many others; and By.res()
to entry a component by its resource-id android:id="@+id/some_id"
.
Within the View system, you normally arrange an identifier to allow entry and set properties of that View
. On this case accessing a component isn’t an issue, as a result of you need to use By.res(packageName, "some_id")
to get the deal with on that View after which work together with it.
Jetpack Compose renders UI in another way than the View system. Compose has a declarative method of describing UI, so it doesn’t require useful resource identifiers (android:id
). Whereas it’s extremely handy for builders, it may be problematic for UiAutomator to entry parts that don’t have distinctive identifiers akin to textual content labels or a component flag. That is particularly necessary for format elements, akin to Row
, Column
, LazyColumn
, and others.
Technically, it’s doable to make use of the contentDescription
to get entry to a composable, however use it solely when it is smart for accessibility functions. The contentDescription
parameter is utilized by the accessibility framework and in case you add tags that are not necessary to people, you successfully make your app more durable to make use of for many who depend on accessibility.
As a substitute, with Jetpack Compose you’ll be able to leverage Modifier.testTag()
. This isn’t enabled by default, so let’s see how one can allow it.
First, you might want to allow testTagAsResourceId
within the composable hierarchy you need to take a look at. This flag will allow changing the testTag
to useful resource identifiers for all nested composables. When you’ve got a single Exercise Compose venture, you’ll be able to allow it solely as soon as near the basis of the composable tree. It will guarantee all the nested composables with Modifier.testTag
are accessible from the UiAutomator.
Be aware: This flag is offered in Jetpack Compose 1.2.0 or newer. Should you use compose-bom, any model will comprise this characteristic.
Within the Now in Android pattern, we modified the Scaffold
, which is a part of the NiaApp
composable. This composable is the basis composable (aside from NiaTheme
, which isn’t able to setting any Modifier
). You may add the Modifier.semantics
with testTagAsResourceId = true
as proven within the following snippet:
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */Scaffold(
modifier = Modifier.semantics {
testTagsAsResourceId = true
},
// ...
)
After you have that, you need to use Modifier.testTag("identifier")
wherever within the Composables hierarchy and the identifiers might be propagated to the UiAutomator as a resource-id.
Within the ForYouScreen composable, let’s add the Modifier.testTag("forYou:feed")
to the LazyVerticalGrid
. The parameter identify is bigoted, you need not observe the identical sample we chosen for Now in android.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */LazyVerticalGrid(
modifier = modifier
.fillMaxSize()
.testTag("forYou:feed"),
// ...
)
Now you can entry the LazyVerticalGrid
from the Ui assessments with out the necessity to sacrifice content material description for testing. From the assessments, you need to use By.res("forYou:feed")
selector.
In our case, we use the UiAutomator for benchmarking and producing Baseline Profiles.
For Baseline Profile generator, you write an instrumentation take a look at like on this following snippet.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Take a look at
enjoyable generate() {
rule.collectBaselineProfile(PACKAGE_NAME) {
// This block defines the app's vital consumer journey.
// Right here we're all in favour of optimizing for app startup.
pressHome()
startActivityAndWait()
}
}
}
This take a look at will begin the default exercise a number of occasions to generate a Baseline Profile.
However you’ll be able to even transcend simply app startup optimization. You may optimize the runtime efficiency of the feed that’s loaded on the primary display. So that you look forward to and discover the feed record by utilizing By.res("forYou:feed")
selector and do some interactions with it.
/* Copyright 2022 Google LLC.
SPDX-License-Identifier: Apache-2.0 */class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Take a look at
enjoyable generate() {
rule.collectBaselineProfile(PACKAGE_NAME) {
// This block defines the app's vital consumer journey.
// Right here we're all in favour of optimizing for app startup.
pressHome()
startActivityAndWait()
// Wait till content material is asynchronously loaded.
// We discover aspect with resource-id "forYou:feed", which equals to Modifier.testTag("forYou:feed")
system.wait(Till.hasObject(By.res("forYou:feed")), 5_000)
val feedList = system.findObject(By.res("forYou:feed"))
// Set some margin from the perimeters to forestall triggering system navigation
feedList.setGestureMargin(system.displayWidth / 5)
// Fling the feed
feedList.fling(Route.DOWN)
system.waitForIdle()
feedList.fling(Route.UP)
}
}
}
Remember! There’s a distinction in utilizing
By.res(packageName, "identifier")
andBy.res("identifier")
. The previous might be looking for@packageName:id/identifier
within the UI hierarchy, whereas the latter only for identifier, which is required for theModifier.testTag
.
In our instance, in case you’d use By.res("com.google.samples.apps.nowinandroid", "forYou:feed")
, it will outcome within the UiAutomator looking for an UI aspect with @com.google.samples.apps.nowinandroid:id/forYou:feed
resource-id, however it doesn’t exist, so the take a look at fails with java.lang.NullPointerException
. The proper method, with out the package deal identify — By.res("forYou:feed")
, will resolve to forYou:feed
resource-id, which is appropriately discovered on display.
After studying this text you’ve seen that enabling UiAutomator interoperability with Jetpack Compose is simple and you’ll go away the content material description for accessibility functions.
To dig right into a extra full pattern, test the Now in Android benchmarks module that generates Baseline Profiles and measures efficiency. The performance-samples Github repository additionally makes use of this interoperability whereas additionally displaying different efficiency samples. For extra details about Baseline Profiles, test the documentation, or, in case you like a extra palms on strategy, test our Baseline Profiles codelab. Additionally, you might test the new updates for UiAutomator.
Subsequent job — writing the customized Baseline Profiles generator and getting the efficiency enhancements.