From dea3da5fa78031fb20aa2967ef7983cd7b461487 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Wed, 16 Apr 2025 13:31:34 +0200 Subject: [PATCH] Migrate benchmarks to UiAutomator 3 API Change-Id: I3ccc4108cb1cc20daff83cf3b48b0c14f125dbb4 --- .../test/uiautomator/UiAutomatorHelpers.kt | 44 --------- .../apps/nowinandroid/GeneralActions.kt | 65 ++++-------- .../google/samples/apps/nowinandroid/Utils.kt | 24 +---- .../BookmarksBaselineProfile.kt | 12 ++- .../baselineprofile/ForYouBaselineProfile.kt | 15 +-- .../InterestsBaselineProfile.kt | 14 +-- .../baselineprofile/StartupBaselineProfile.kt | 6 +- .../bookmarks/BookmarksActions.kt | 16 ++- .../apps/nowinandroid/foryou/ForYouActions.kt | 98 ++++++------------- .../foryou/ScrollForYouFeedBenchmark.kt | 22 +++-- .../interests/InterestsActions.kt | 42 ++++---- .../interests/ScrollTopicListBenchmark.kt | 29 +++--- .../ScrollTopicListPowerMetricsBenchmark.kt | 31 +++--- .../TopicsScreenRecompositionBenchmark.kt | 32 +++--- .../nowinandroid/startup/StartupBenchmark.kt | 19 ++-- gradle/libs.versions.toml | 4 +- settings.gradle.kts | 6 ++ 17 files changed, 186 insertions(+), 293 deletions(-) delete mode 100644 benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt diff --git a/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt b/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt deleted file mode 100644 index b0eb754c7f..0000000000 --- a/benchmarks/src/main/kotlin/androidx/test/uiautomator/UiAutomatorHelpers.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.test.uiautomator - -import androidx.test.uiautomator.HasChildrenOp.AT_LEAST -import androidx.test.uiautomator.HasChildrenOp.AT_MOST -import androidx.test.uiautomator.HasChildrenOp.EXACTLY - -// These helpers need to be in the androidx.test.uiautomator package, -// because the abstract class has package local method that needs to be implemented. - -/** - * Condition will be satisfied if given element has specified count of children - */ -fun untilHasChildren( - childCount: Int = 1, - op: HasChildrenOp = AT_LEAST, -): UiObject2Condition = object : UiObject2Condition() { - override fun apply(element: UiObject2): Boolean = when (op) { - AT_LEAST -> element.childCount >= childCount - EXACTLY -> element.childCount == childCount - AT_MOST -> element.childCount <= childCount - } -} - -enum class HasChildrenOp { - AT_LEAST, - EXACTLY, - AT_MOST, -} diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/GeneralActions.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/GeneralActions.kt index 8df52104ae..21bd5f22d0 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/GeneralActions.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/GeneralActions.kt @@ -16,57 +16,30 @@ package com.google.samples.apps.nowinandroid -import android.Manifest.permission -import android.os.Build.VERSION.SDK_INT -import android.os.Build.VERSION_CODES.TIRAMISU -import androidx.benchmark.macro.MacrobenchmarkScope -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until +import android.view.accessibility.AccessibilityNodeInfo +import androidx.test.uiautomator.UiAutomatorTestScope +import androidx.test.uiautomator.onView +import androidx.test.uiautomator.textAsString +import androidx.test.uiautomator.watcher.PermissionDialog /** - * Because the app under test is different from the one running the instrumentation test, - * the permission has to be granted manually by either: - * - * - tapping the Allow button - * ```kotlin - * val obj = By.text("Allow") - * val dialog = device.wait(Until.findObject(obj), TIMEOUT) - * dialog?.let { - * it.click() - * device.wait(Until.gone(obj), 5_000) - * } - * ``` - * - or (preferred) executing the grant command on the target package. - */ -fun MacrobenchmarkScope.allowNotifications() { - if (SDK_INT >= TIRAMISU) { - val command = "pm grant $packageName ${permission.POST_NOTIFICATIONS}" - device.executeShellCommand(command) - } -} - -/** - * Wraps starting the default activity, waiting for it to start and then allowing notifications in + * Wraps starting the default activity, waiting for it to start and then allowing permissions in * one convenient call. */ -fun MacrobenchmarkScope.startActivityAndAllowNotifications() { - startActivityAndWait() - allowNotifications() +fun UiAutomatorTestScope.startAppAndAllowPermission() { + startApp(PACKAGE_NAME) + watchFor(PermissionDialog) { + clickAllow() + } } -/** - * Waits for and returns the `niaTopAppBar` - */ -fun MacrobenchmarkScope.getTopAppBar(): UiObject2 { - device.wait(Until.hasObject(By.res("niaTopAppBar")), 2_000) - return device.findObject(By.res("niaTopAppBar")) -} +fun UiAutomatorTestScope.textVisibleOnTopAppBar(text: String) = + onTopAppBar { textAsString == text && isVisibleToUser } -/** - * Waits for an object on the top app bar, passed in as [selector]. - */ -fun MacrobenchmarkScope.waitForObjectOnTopAppBar(selector: BySelector, timeout: Long = 2_000) { - getTopAppBar().wait(Until.hasObject(selector), timeout) +fun UiAutomatorTestScope.onTopAppBar( + timeoutMs: Long = 10000, + pollIntervalMs: Long = 100, + block: AccessibilityNodeInfo.() -> (Boolean), +) { + onView { viewIdResourceName == "niaTopAppBar" }.onView(timeoutMs, pollIntervalMs, block) } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Utils.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Utils.kt index e8fb53c4f4..5c3fc797be 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Utils.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/Utils.kt @@ -16,11 +16,7 @@ package com.google.samples.apps.nowinandroid -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.Direction import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until import com.google.samples.apps.nowinandroid.benchmarks.BuildConfig import java.io.ByteArrayOutputStream @@ -32,26 +28,10 @@ val PACKAGE_NAME = buildString { append(BuildConfig.APP_FLAVOR_SUFFIX) } -fun UiDevice.flingElementDownUp(element: UiObject2) { - // Set some margin from the sides to prevent triggering system navigation - element.setGestureMargin(displayWidth / 5) - - element.fling(Direction.DOWN) - waitForIdle() - element.fling(Direction.UP) -} - /** - * Waits until an object with [selector] if visible on screen and returns the object. - * If the element is not available in [timeout], throws [AssertionError] + * Default iteration parameter for Now in Android. */ -fun UiDevice.waitAndFindObject(selector: BySelector, timeout: Long): UiObject2 { - if (!wait(Until.hasObject(selector), timeout)) { - throw AssertionError("Element not found on screen in ${timeout}ms (selector=$selector)") - } - - return findObject(selector) -} +const val ITERATIONS = 10 /** * Helper to dump window hierarchy into a string. diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/BookmarksBaselineProfile.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/BookmarksBaselineProfile.kt index eca3f059b7..3dfa8d5cf8 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/BookmarksBaselineProfile.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/BookmarksBaselineProfile.kt @@ -17,9 +17,10 @@ package com.google.samples.apps.nowinandroid.baselineprofile import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.uiautomator.uiAutomator import com.google.samples.apps.nowinandroid.PACKAGE_NAME import com.google.samples.apps.nowinandroid.bookmarks.goToBookmarksScreen -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test @@ -32,9 +33,10 @@ class BookmarksBaselineProfile { @Test fun generate() = baselineProfileRule.collect(PACKAGE_NAME) { - startActivityAndAllowNotifications() - - // Navigate to saved screen - goToBookmarksScreen() + uiAutomator { + startAppAndAllowPermission() + // Navigate to saved screen + goToBookmarksScreen() + } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt index e8722e1168..f49a903a47 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/ForYouBaselineProfile.kt @@ -17,11 +17,12 @@ package com.google.samples.apps.nowinandroid.baselineprofile import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.uiautomator.uiAutomator import com.google.samples.apps.nowinandroid.PACKAGE_NAME import com.google.samples.apps.nowinandroid.foryou.forYouScrollFeedDownUp import com.google.samples.apps.nowinandroid.foryou.forYouSelectTopics import com.google.samples.apps.nowinandroid.foryou.forYouWaitForContent -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test @@ -34,11 +35,13 @@ class ForYouBaselineProfile { @Test fun generate() = baselineProfileRule.collect(PACKAGE_NAME) { - startActivityAndAllowNotifications() + uiAutomator { + startAppAndAllowPermission() - // Scroll the feed critical user journey - forYouWaitForContent() - forYouSelectTopics(true) - forYouScrollFeedDownUp() + // Scroll the feed critical user journey + forYouWaitForContent() + forYouSelectTopics(true) + forYouScrollFeedDownUp() + } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/InterestsBaselineProfile.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/InterestsBaselineProfile.kt index dd2166dc2c..a7a4378ea8 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/InterestsBaselineProfile.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/InterestsBaselineProfile.kt @@ -17,10 +17,11 @@ package com.google.samples.apps.nowinandroid.baselineprofile import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.uiautomator.uiAutomator import com.google.samples.apps.nowinandroid.PACKAGE_NAME import com.google.samples.apps.nowinandroid.interests.goToInterestsScreen import com.google.samples.apps.nowinandroid.interests.interestsScrollTopicsDownUp -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test @@ -33,10 +34,11 @@ class InterestsBaselineProfile { @Test fun generate() = baselineProfileRule.collect(PACKAGE_NAME) { - startActivityAndAllowNotifications() - - // Navigate to interests screen - goToInterestsScreen() - interestsScrollTopicsDownUp() + uiAutomator { + startAppAndAllowPermission() + // Navigate to interests screen + goToInterestsScreen() + interestsScrollTopicsDownUp() + } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt index d8128a670d..921f93ce6f 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/baselineprofile/StartupBaselineProfile.kt @@ -16,10 +16,10 @@ package com.google.samples.apps.nowinandroid.baselineprofile -import androidx.benchmark.macro.MacrobenchmarkScope import androidx.benchmark.macro.junit4.BaselineProfileRule +import androidx.test.uiautomator.uiAutomator import com.google.samples.apps.nowinandroid.PACKAGE_NAME -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test @@ -34,6 +34,6 @@ class StartupBaselineProfile { fun generate() = baselineProfileRule.collect( PACKAGE_NAME, includeInStartupProfile = true, - profileBlock = MacrobenchmarkScope::startActivityAndAllowNotifications, + profileBlock = { uiAutomator { startAppAndAllowPermission() } }, ) } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/bookmarks/BookmarksActions.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/bookmarks/BookmarksActions.kt index eb01c7d1b0..2a94400676 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/bookmarks/BookmarksActions.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/bookmarks/BookmarksActions.kt @@ -16,15 +16,11 @@ package com.google.samples.apps.nowinandroid.bookmarks -import androidx.benchmark.macro.MacrobenchmarkScope -import androidx.test.uiautomator.By -import com.google.samples.apps.nowinandroid.waitForObjectOnTopAppBar +import androidx.test.uiautomator.UiAutomatorTestScope +import androidx.test.uiautomator.textAsString +import com.google.samples.apps.nowinandroid.textVisibleOnTopAppBar -fun MacrobenchmarkScope.goToBookmarksScreen() { - val savedSelector = By.text("Saved") - val savedButton = device.findObject(savedSelector) - savedButton.click() - device.waitForIdle() - // Wait until saved title are shown on screen - waitForObjectOnTopAppBar(savedSelector) +fun UiAutomatorTestScope.goToBookmarksScreen() { + onView { textAsString == "Saved" }.click() + textVisibleOnTopAppBar("Saved") } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt index 20b941a243..a982fd01a5 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ForYouActions.kt @@ -16,93 +16,57 @@ package com.google.samples.apps.nowinandroid.foryou -import androidx.benchmark.macro.MacrobenchmarkScope -import androidx.test.uiautomator.By -import androidx.test.uiautomator.Until -import androidx.test.uiautomator.untilHasChildren -import com.google.samples.apps.nowinandroid.flingElementDownUp -import com.google.samples.apps.nowinandroid.waitAndFindObject -import com.google.samples.apps.nowinandroid.waitForObjectOnTopAppBar +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiAutomatorTestScope +import androidx.test.uiautomator.textAsString +import com.google.samples.apps.nowinandroid.textVisibleOnTopAppBar import org.junit.Assert.fail -fun MacrobenchmarkScope.forYouWaitForContent() { - // Wait until content is loaded by checking if topics are loaded - device.wait(Until.gone(By.res("loadingWheel")), 5_000) +fun UiAutomatorTestScope.forYouWaitForContent() { + onViewOrNull(timeoutMs = 500) { viewIdResourceName == "loadingWheel" && !isVisibleToUser } // Sometimes, the loading wheel is gone, but the content is not loaded yet // So we'll wait here for topics to be sure - val obj = device.waitAndFindObject(By.res("forYou:topicSelection"), 10_000) // Timeout here is quite big, because sometimes data loading takes a long time! - obj.wait(untilHasChildren(), 60_000) + onView(timeoutMs = 30_000) { + viewIdResourceName == "forYou:topicSelection" && childCount > 0 && isVisibleToUser + } } /** * Selects some topics, which will show the feed content for them. * [recheckTopicsIfChecked] Topics may be already checked from the previous iteration. */ -fun MacrobenchmarkScope.forYouSelectTopics(recheckTopicsIfChecked: Boolean = false) { - val topics = device.findObject(By.res("forYou:topicSelection")) - - // Set gesture margin from sides not to trigger system gesture navigation - val horizontalMargin = 10 * topics.visibleBounds.width() / 100 - topics.setGestureMargins(horizontalMargin, 0, horizontalMargin, 0) - - // Select some topics to show some feed content - var index = 0 - var visited = 0 - - while (visited < 3) { - if (topics.childCount == 0) { +fun UiAutomatorTestScope.forYouSelectTopics(recheckTopicsIfChecked: Boolean = false) { + onView { viewIdResourceName == "forYou:topicSelection" }.run { + if (children.isEmpty()) { fail("No topics found, can't generate profile for ForYou page.") } - // Selecting some topics, which will populate items in the feed. - val topic = topics.children[index % topics.childCount] - // Find the checkable element to figure out whether it's checked or not - val topicCheckIcon = topic.findObject(By.checkable(true)) - // Topic icon may not be visible if it's out of the screen boundaries - // If that's the case, let's try another index - if (topicCheckIcon == null) { - index++ - continue - } - - when { - // Topic wasn't checked, so just do that - !topicCheckIcon.isChecked -> { - topic.click() - device.waitForIdle() - } - // Topic was checked already and we want to recheck it, so just do it twice - recheckTopicsIfChecked -> { - repeat(2) { - topic.click() - device.waitForIdle() - } - } + val horizontalMargin = visibleBounds.width() / 10 + setGestureMargins(horizontalMargin, 0, horizontalMargin, 0) - else -> { - // Topic is checked, but we don't recheck it + // Filter for any checkable and unchecked children unless recheck is enabled + children.filter { child -> + child.isCheckable and (recheckTopicsIfChecked or !child.isChecked) + }.forEach { child -> + // recheck check to uncheck before recheck + if (child.isChecked) { + child.click() } + child.click() } - - index++ - visited++ } } -fun MacrobenchmarkScope.forYouScrollFeedDownUp() { - val feedList = device.findObject(By.res("forYou:feed")) - device.flingElementDownUp(feedList) -} - -fun MacrobenchmarkScope.setAppTheme(isDark: Boolean) { - when (isDark) { - true -> device.findObject(By.text("Dark")).click() - false -> device.findObject(By.text("Light")).click() +fun UiAutomatorTestScope.forYouScrollFeedDownUp() { + onView { viewIdResourceName == "forYou:feed" }.run { + fling(Direction.DOWN) + fling(Direction.UP) } - device.waitForIdle() - device.findObject(By.text("OK")).click() +} - // Wait until the top app bar is visible on screen - waitForObjectOnTopAppBar(By.text("Now in Android")) +fun UiAutomatorTestScope.setAppTheme(isDark: Boolean) { + onView { textAsString == if (isDark) "Dark" else "Light" }.click() + onView { textAsString == "OK" }.click() + textVisibleOnTopAppBar("Now in Android") } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt index c74d79307c..1d8f13b254 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/foryou/ScrollForYouFeedBenchmark.kt @@ -21,8 +21,10 @@ import androidx.benchmark.macro.FrameTimingMetric import androidx.benchmark.macro.StartupMode import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.uiautomator.uiAutomator +import com.google.samples.apps.nowinandroid.ITERATIONS import com.google.samples.apps.nowinandroid.PACKAGE_NAME -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -45,16 +47,20 @@ class ScrollForYouFeedBenchmark { packageName = PACKAGE_NAME, metrics = listOf(FrameTimingMetric()), compilationMode = compilationMode, - iterations = 10, + iterations = ITERATIONS, startupMode = StartupMode.WARM, setupBlock = { - // Start the app - pressHome() - startActivityAndAllowNotifications() + uiAutomator { + // Start the app + pressHome() + startAppAndAllowPermission() + } }, ) { - forYouWaitForContent() - forYouSelectTopics() - forYouScrollFeedDownUp() + uiAutomator { + forYouWaitForContent() + forYouSelectTopics() + forYouScrollFeedDownUp() + } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsActions.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsActions.kt index 05b276faab..2fd2f011a6 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsActions.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/InterestsActions.kt @@ -16,35 +16,27 @@ package com.google.samples.apps.nowinandroid.interests -import androidx.benchmark.macro.MacrobenchmarkScope -import androidx.test.uiautomator.By -import androidx.test.uiautomator.Until -import com.google.samples.apps.nowinandroid.flingElementDownUp -import com.google.samples.apps.nowinandroid.waitForObjectOnTopAppBar - -fun MacrobenchmarkScope.goToInterestsScreen() { - device.findObject(By.text("Interests")).click() - device.waitForIdle() - // Wait until interests are shown on screen - waitForObjectOnTopAppBar(By.text("Interests")) +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiAutomatorTestScope +import androidx.test.uiautomator.textAsString +import com.google.samples.apps.nowinandroid.textVisibleOnTopAppBar +fun UiAutomatorTestScope.goToInterestsScreen() { + onView { textAsString == "Interests" }.click() + textVisibleOnTopAppBar("Interests") // Wait until content is loaded by checking if interests are loaded - device.wait(Until.gone(By.res("loadingWheel")), 5_000) -} - -fun MacrobenchmarkScope.interestsScrollTopicsDownUp() { - device.wait(Until.hasObject(By.res("interests:topics")), 5_000) - val topicsList = device.findObject(By.res("interests:topics")) - device.flingElementDownUp(topicsList) + assert(onViewOrNull { viewIdResourceName == "loadingWheel" && !isVisibleToUser } == null) } -fun MacrobenchmarkScope.interestsWaitForTopics() { - device.wait(Until.hasObject(By.text("Accessibility")), 30_000) +fun UiAutomatorTestScope.interestsScrollTopicsDownUp() { + onView { viewIdResourceName == "interests:topics" }.run { + // Set some margin from the sides to prevent triggering system navigation + setGestureMargin(uiDevice.displayWidth / 5) + fling(Direction.DOWN) + fling(Direction.UP) + } } -fun MacrobenchmarkScope.interestsToggleBookmarked() { - val topicsList = device.findObject(By.res("interests:topics")) - val checkable = topicsList.findObject(By.checkable(true)) - checkable.click() - device.waitForIdle() +fun UiAutomatorTestScope.interestsWaitForTopics() { + onView(30_000) { textAsString == "Accessibility" } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListBenchmark.kt index b53e2e05ce..cf27f75173 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListBenchmark.kt @@ -21,9 +21,11 @@ import androidx.benchmark.macro.FrameTimingMetric import androidx.benchmark.macro.StartupMode import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.uiautomator.By +import androidx.test.uiautomator.textAsString +import androidx.test.uiautomator.uiAutomator +import com.google.samples.apps.nowinandroid.ITERATIONS import com.google.samples.apps.nowinandroid.PACKAGE_NAME -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -42,20 +44,23 @@ class ScrollTopicListBenchmark { packageName = PACKAGE_NAME, metrics = listOf(FrameTimingMetric()), compilationMode = compilationMode, - iterations = 10, + iterations = ITERATIONS, startupMode = StartupMode.WARM, setupBlock = { - // Start the app - pressHome() - startActivityAndAllowNotifications() - // Navigate to interests screen - device.findObject(By.text("Interests")).click() - device.waitForIdle() + uiAutomator { + // Start the app + pressHome() + startAppAndAllowPermission() + // Navigate to interests screen + onView { textAsString == "Interests" }.click() + } }, ) { - interestsWaitForTopics() - repeat(3) { - interestsScrollTopicsDownUp() + uiAutomator { + interestsWaitForTopics() + repeat(3) { + interestsScrollTopicsDownUp() + } } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt index f938fad624..07e3f06ccf 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/ScrollTopicListPowerMetricsBenchmark.kt @@ -27,13 +27,14 @@ import androidx.benchmark.macro.PowerMetric import androidx.benchmark.macro.StartupMode import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.uiautomator.By +import androidx.test.uiautomator.uiAutomator +import com.google.samples.apps.nowinandroid.ITERATIONS import com.google.samples.apps.nowinandroid.PACKAGE_NAME -import com.google.samples.apps.nowinandroid.allowNotifications import com.google.samples.apps.nowinandroid.foryou.forYouScrollFeedDownUp import com.google.samples.apps.nowinandroid.foryou.forYouSelectTopics import com.google.samples.apps.nowinandroid.foryou.forYouWaitForContent import com.google.samples.apps.nowinandroid.foryou.setAppTheme +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -61,23 +62,23 @@ class ScrollTopicListPowerMetricsBenchmark { packageName = PACKAGE_NAME, metrics = listOf(FrameTimingMetric(), PowerMetric(PowerMetric.Energy(categories))), compilationMode = compilationMode, - iterations = 2, + iterations = ITERATIONS, startupMode = StartupMode.WARM, setupBlock = { - // Start the app - pressHome() - startActivityAndWait() - allowNotifications() - // Navigate to Settings - device.findObject(By.desc("Settings")).click() - device.waitForIdle() - setAppTheme(isDark) + uiAutomator { + pressHome() + startAppAndAllowPermission() + onView { contentDescription == "Settings" }.click() + setAppTheme(isDark) + } }, ) { - forYouWaitForContent() - forYouSelectTopics() - repeat(3) { - forYouScrollFeedDownUp() + uiAutomator { + forYouWaitForContent() + forYouSelectTopics() + repeat(3) { + forYouScrollFeedDownUp() + } } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt index faf0803f3d..3375bbf6b8 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/interests/TopicsScreenRecompositionBenchmark.kt @@ -21,9 +21,12 @@ import androidx.benchmark.macro.FrameTimingMetric import androidx.benchmark.macro.StartupMode import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.uiautomator.By +import androidx.test.uiautomator.onView +import androidx.test.uiautomator.textAsString +import androidx.test.uiautomator.uiAutomator +import com.google.samples.apps.nowinandroid.ITERATIONS import com.google.samples.apps.nowinandroid.PACKAGE_NAME -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -42,20 +45,25 @@ class TopicsScreenRecompositionBenchmark { packageName = PACKAGE_NAME, metrics = listOf(FrameTimingMetric()), compilationMode = compilationMode, - iterations = 10, + iterations = ITERATIONS, startupMode = StartupMode.WARM, setupBlock = { - // Start the app - pressHome() - startActivityAndAllowNotifications() - // Navigate to interests screen - device.findObject(By.text("Interests")).click() - device.waitForIdle() + uiAutomator { + // Start the app + pressHome() + startAppAndAllowPermission() + // Navigate to interests screen + onView { textAsString == "Interests" }.click() + } }, ) { - interestsWaitForTopics() - repeat(3) { - interestsToggleBookmarked() + uiAutomator { + interestsWaitForTopics() + repeat(3) { + // Toggle bookmarked + onView { viewIdResourceName == "interests:topics" }.onView { isCheckable } + .click() + } } } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt index 4aab929b10..0c0c2fe92c 100644 --- a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/startup/StartupBenchmark.kt @@ -22,11 +22,12 @@ import androidx.benchmark.macro.CompilationMode import androidx.benchmark.macro.StartupMode.COLD import androidx.benchmark.macro.junit4.MacrobenchmarkRule import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.uiautomator.uiAutomator import com.google.samples.apps.nowinandroid.BaselineProfileMetrics +import com.google.samples.apps.nowinandroid.ITERATIONS import com.google.samples.apps.nowinandroid.PACKAGE_NAME -import com.google.samples.apps.nowinandroid.allowNotifications import com.google.samples.apps.nowinandroid.foryou.forYouWaitForContent -import com.google.samples.apps.nowinandroid.startActivityAndAllowNotifications +import com.google.samples.apps.nowinandroid.startAppAndAllowPermission import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -61,15 +62,13 @@ class StartupBenchmark { metrics = BaselineProfileMetrics.allMetrics, compilationMode = compilationMode, // More iterations result in higher statistical significance. - iterations = 20, + iterations = ITERATIONS, startupMode = COLD, - setupBlock = { - pressHome() - allowNotifications() - }, ) { - startActivityAndAllowNotifications() - // Waits until the content is ready to capture Time To Full Display - forYouWaitForContent() + uiAutomator { + startAppAndAllowPermission() + // Waits until the content is ready to capture Time To Full Display + forYouWaitForContent() + } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 477dde9165..468c4b3b0a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ androidxEspresso = "3.6.1" androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.8.7" androidxLintGradle = "1.0.0-alpha03" -androidxMacroBenchmark = "1.3.4" +androidxMacroBenchmark = "1.4.0-alpha11" androidxMetrics = "1.0.0-beta01" androidxNavigation = "2.8.5" androidxProfileinstaller = "1.4.1" @@ -27,7 +27,7 @@ androidxTestExt = "1.2.1" androidxTestRules = "1.6.1" androidxTestRunner = "1.6.2" androidxTracing = "1.3.0-alpha02" -androidxUiAutomator = "2.3.0" +androidxUiAutomator = "2.4.0-SNAPSHOT" androidxWindowManager = "1.3.0" androidxWork = "2.10.0" coil = "2.7.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index 2b8c6e45c6..b9d5cc5056 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,9 @@ pluginManagement { } mavenCentral() gradlePluginPortal() + maven { // androidx snapshots + url = uri("https://androidx.dev/snapshots/builds/13364063/artifacts/repository") + } } } @@ -40,6 +43,9 @@ dependencyResolutionManagement { } } mavenCentral() + maven { // androidx snapshots + url = uri("https://androidx.dev/snapshots/builds/13364063/artifacts/repository") + } } } rootProject.name = "nowinandroid"