From dea3da5fa78031fb20aa2967ef7983cd7b461487 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Wed, 16 Apr 2025 13:31:34 +0200 Subject: [PATCH 1/2] 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" From e986cd2a78d4e88d9489b6db0855df7e63942155 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Mon, 7 Jul 2025 13:34:02 +0200 Subject: [PATCH 2/2] Move from SNAPSHOT to alpha / rc Change-Id: I75c04745b3641ad4cabd5df1e3c8b4518e9db9a6 --- benchmarks/build.gradle.kts | 1 + .../apps/nowinandroid/GeneralActions.kt | 6 +- .../bookmarks/BookmarksActions.kt | 2 +- .../apps/nowinandroid/foryou/ForYouActions.kt | 12 +-- .../interests/InterestsActions.kt | 10 +-- .../interests/ScrollTopicListBenchmark.kt | 8 +- .../ScrollTopicListPowerMetricsBenchmark.kt | 2 +- .../TopicsScreenRecompositionBenchmark.kt | 6 +- .../userjourney/NewUserJourneyBenchmark.kt | 84 +++++++++++++++++++ gradle/libs.versions.toml | 5 +- 10 files changed, 114 insertions(+), 22 deletions(-) create mode 100644 benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/userjourney/NewUserJourneyBenchmark.kt diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index e36f021b09..d32db8d6d6 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -69,6 +69,7 @@ baselineProfile { dependencies { implementation(libs.androidx.benchmark.macro) + implementation(libs.androidx.benchmark.traceprocessor) implementation(libs.androidx.test.core) implementation(libs.androidx.test.espresso.core) implementation(libs.androidx.test.ext) 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 21bd5f22d0..9c6beb5a5d 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 @@ -18,7 +18,7 @@ package com.google.samples.apps.nowinandroid import android.view.accessibility.AccessibilityNodeInfo import androidx.test.uiautomator.UiAutomatorTestScope -import androidx.test.uiautomator.onView +import androidx.test.uiautomator.onElement import androidx.test.uiautomator.textAsString import androidx.test.uiautomator.watcher.PermissionDialog @@ -34,12 +34,12 @@ fun UiAutomatorTestScope.startAppAndAllowPermission() { } fun UiAutomatorTestScope.textVisibleOnTopAppBar(text: String) = - onTopAppBar { textAsString == text && isVisibleToUser } + onTopAppBar { textAsString() == text && isVisibleToUser } fun UiAutomatorTestScope.onTopAppBar( timeoutMs: Long = 10000, pollIntervalMs: Long = 100, block: AccessibilityNodeInfo.() -> (Boolean), ) { - onView { viewIdResourceName == "niaTopAppBar" }.onView(timeoutMs, pollIntervalMs, block) + onElement { viewIdResourceName == "niaTopAppBar" }.onElement(timeoutMs, pollIntervalMs, block) } 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 2a94400676..308583df07 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 @@ -21,6 +21,6 @@ import androidx.test.uiautomator.textAsString import com.google.samples.apps.nowinandroid.textVisibleOnTopAppBar fun UiAutomatorTestScope.goToBookmarksScreen() { - onView { textAsString == "Saved" }.click() + onElement { 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 a982fd01a5..691b7db550 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 @@ -23,11 +23,11 @@ import com.google.samples.apps.nowinandroid.textVisibleOnTopAppBar import org.junit.Assert.fail fun UiAutomatorTestScope.forYouWaitForContent() { - onViewOrNull(timeoutMs = 500) { viewIdResourceName == "loadingWheel" && !isVisibleToUser } + onElementOrNull(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 // Timeout here is quite big, because sometimes data loading takes a long time! - onView(timeoutMs = 30_000) { + onElement(timeoutMs = 30_000) { viewIdResourceName == "forYou:topicSelection" && childCount > 0 && isVisibleToUser } } @@ -37,7 +37,7 @@ fun UiAutomatorTestScope.forYouWaitForContent() { * [recheckTopicsIfChecked] Topics may be already checked from the previous iteration. */ fun UiAutomatorTestScope.forYouSelectTopics(recheckTopicsIfChecked: Boolean = false) { - onView { viewIdResourceName == "forYou:topicSelection" }.run { + onElement { viewIdResourceName == "forYou:topicSelection" }.run { if (children.isEmpty()) { fail("No topics found, can't generate profile for ForYou page.") } @@ -59,14 +59,14 @@ fun UiAutomatorTestScope.forYouSelectTopics(recheckTopicsIfChecked: Boolean = fa } fun UiAutomatorTestScope.forYouScrollFeedDownUp() { - onView { viewIdResourceName == "forYou:feed" }.run { + onElement { viewIdResourceName == "forYou:feed" }.run { fling(Direction.DOWN) fling(Direction.UP) } } fun UiAutomatorTestScope.setAppTheme(isDark: Boolean) { - onView { textAsString == if (isDark) "Dark" else "Light" }.click() - onView { textAsString == "OK" }.click() + onElement { textAsString() == if (isDark) "Dark" else "Light" }.click() + onElement { textAsString() == "OK" }.click() textVisibleOnTopAppBar("Now in Android") } 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 2fd2f011a6..6312d0cf95 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 @@ -22,21 +22,21 @@ import androidx.test.uiautomator.textAsString import com.google.samples.apps.nowinandroid.textVisibleOnTopAppBar fun UiAutomatorTestScope.goToInterestsScreen() { - onView { textAsString == "Interests" }.click() + onElement { textAsString() == "Interests" }.click() textVisibleOnTopAppBar("Interests") // Wait until content is loaded by checking if interests are loaded - assert(onViewOrNull { viewIdResourceName == "loadingWheel" && !isVisibleToUser } == null) + assert(onElementOrNull { viewIdResourceName == "loadingWheel" && !isVisibleToUser } == null) } fun UiAutomatorTestScope.interestsScrollTopicsDownUp() { - onView { viewIdResourceName == "interests:topics" }.run { + onElement { viewIdResourceName == "interests:topics" }.run { // Set some margin from the sides to prevent triggering system navigation - setGestureMargin(uiDevice.displayWidth / 5) + setGestureMargin(device.displayWidth / 5) fling(Direction.DOWN) fling(Direction.UP) } } fun UiAutomatorTestScope.interestsWaitForTopics() { - onView(30_000) { textAsString == "Accessibility" } + onElement(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 cf27f75173..9397206b5f 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 @@ -16,10 +16,14 @@ package com.google.samples.apps.nowinandroid.interests +import androidx.benchmark.ExperimentalBenchmarkConfigApi +import androidx.benchmark.ExperimentalConfig +import androidx.benchmark.StartupInsightsConfig import androidx.benchmark.macro.CompilationMode import androidx.benchmark.macro.FrameTimingMetric import androidx.benchmark.macro.StartupMode import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.textAsString import androidx.test.uiautomator.uiAutomator @@ -30,6 +34,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +@OptIn(ExperimentalBenchmarkConfigApi::class, ExperimentalPerfettoCaptureApi::class) @RunWith(AndroidJUnit4::class) class ScrollTopicListBenchmark { @get:Rule @@ -46,13 +51,14 @@ class ScrollTopicListBenchmark { compilationMode = compilationMode, iterations = ITERATIONS, startupMode = StartupMode.WARM, + experimentalConfig = ExperimentalConfig(startupInsightsConfig = StartupInsightsConfig(true)), setupBlock = { uiAutomator { // Start the app pressHome() startAppAndAllowPermission() // Navigate to interests screen - onView { textAsString == "Interests" }.click() + onElement { textAsString() == "Interests" }.click() } }, ) { 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 07e3f06ccf..aa9235bc4e 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 @@ -68,7 +68,7 @@ class ScrollTopicListPowerMetricsBenchmark { uiAutomator { pressHome() startAppAndAllowPermission() - onView { contentDescription == "Settings" }.click() + onElement { contentDescription == "Settings" }.click() setAppTheme(isDark) } }, 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 3375bbf6b8..e0eb412a41 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,7 +21,7 @@ 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.onView +import androidx.test.uiautomator.onElement import androidx.test.uiautomator.textAsString import androidx.test.uiautomator.uiAutomator import com.google.samples.apps.nowinandroid.ITERATIONS @@ -53,7 +53,7 @@ class TopicsScreenRecompositionBenchmark { pressHome() startAppAndAllowPermission() // Navigate to interests screen - onView { textAsString == "Interests" }.click() + onElement { textAsString() == "Interests" }.click() } }, ) { @@ -61,7 +61,7 @@ class TopicsScreenRecompositionBenchmark { interestsWaitForTopics() repeat(3) { // Toggle bookmarked - onView { viewIdResourceName == "interests:topics" }.onView { isCheckable } + onElement { viewIdResourceName == "interests:topics" }.onElement { isCheckable } .click() } } diff --git a/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/userjourney/NewUserJourneyBenchmark.kt b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/userjourney/NewUserJourneyBenchmark.kt new file mode 100644 index 0000000000..ae7217ac98 --- /dev/null +++ b/benchmarks/src/main/kotlin/com/google/samples/apps/nowinandroid/userjourney/NewUserJourneyBenchmark.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2025 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 com.google.samples.apps.nowinandroid.userjourney + +import androidx.benchmark.macro.CompilationMode +import androidx.benchmark.macro.StartupMode +import androidx.benchmark.macro.junit4.MacrobenchmarkRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.scrollToElement +import androidx.test.uiautomator.textAsString +import androidx.test.uiautomator.uiAutomator +import androidx.test.uiautomator.waitForStable +import androidx.test.uiautomator.watcher.PermissionDialog +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.foryou.forYouSelectTopics +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class NewUserJourneyBenchmark { + + @get:Rule + val benchmarkRule = MacrobenchmarkRule() + + @Test + fun measureJourney() = benchmarkRule.measureRepeated( + packageName = PACKAGE_NAME, + metrics = BaselineProfileMetrics.allMetrics, + compilationMode = CompilationMode.DEFAULT, + startupMode = StartupMode.COLD, + iterations = ITERATIONS, + ) { + uiAutomator { + startApp(PACKAGE_NAME) + watchFor(PermissionDialog) { + clickAllow() + } + // Select some topics + forYouSelectTopics() + onElement { textAsString() == "Done" }.click() + onElement { isScrollable }.scroll(Direction.DOWN, 0.25f) + onElement { textAsString() == "Interests" }.click() + + activeWindowRoot().waitForStable() + + onElement { isScrollable }.scrollToElement( + direction = Direction.DOWN, + block = { + textAsString() == "Performance" + }, + ).click() + + // Enable dynamic color in settings + onElement { contentDescription == "Settings" }.click() + onElement { textAsString() == "Yes" }.click() + pressBack() + + // Search for "AndroidX releases" + onElement { contentDescription == "Search" }.click() + // Wait for animations to finish + activeWindowRoot().waitForStable() + type("AndroidX releases") + pressEnter() + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 468c4b3b0a..aea65cba8a 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.4.0-alpha11" +androidxMacroBenchmark = "1.4.0-rc01" 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.4.0-SNAPSHOT" +androidxUiAutomator = "2.4.0-alpha05" androidxWindowManager = "1.3.0" androidxWork = "2.10.0" coil = "2.7.0" @@ -69,6 +69,7 @@ android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } +androidx-benchmark-traceprocessor = { group = "androidx.benchmark", name = "benchmark-traceprocessor", version.ref = "androidxMacroBenchmark" } androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "androidxBrowser" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom-alpha", version.ref = "androidxComposeBom" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "androidxComposeFoundation" }