Skip to content

WIP: Migrate benchmarks to UiAutomator 3.0 API #322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MacrobenchmarkSample/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ profileInstaller = "1.4.1"
runtimeTracing = "1.7.8"
tracing = "1.3.0-beta01"
tracingPerfetto = "1.0.0"
uiAutomator = "2.3.0"
uiAutomator = "2.4.0-SNAPSHOT"

[libraries]
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@

package com.example.macrobenchmark.baselineprofile

import androidx.benchmark.macro.MacrobenchmarkScope
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiAutomatorTestScope

const val TARGET_PACKAGE = "com.example.macrobenchmark.target"

/**
* Clears the application data for the package specified in the [MacrobenchmarkScope].
* @param scope The [MacrobenchmarkScope] providing information about the benchmark,
* including the package name of the app under test.
* Clears the application data for the launcher package.
*/
fun UiDevice.clearData(scope: MacrobenchmarkScope) {
val command = "pm clear ${scope.packageName}"
val output = executeShellCommand(command)
// Assert.assertEquals("Success", output)
fun UiAutomatorTestScope.clearData() {
val command = "pm clear $TARGET_PACKAGE"
uiDevice.executeShellCommand(command)
Comment on lines +26 to +28

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The KDoc comment should describe the purpose of the function, not the parameter. Also, it should describe what package the data is being cleared for.

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,12 @@ package com.example.macrobenchmark.baselineprofile
import android.content.Intent
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import com.example.macrobenchmark.benchmark.util.findOrFail
import com.example.macrobenchmark.benchmark.util.waitAndFind
import org.junit.Ignore
import androidx.test.uiautomator.uiAutomator
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@Ignore // TODO causing stale object excpetion on CI .. why?
@RunWith(AndroidJUnit4ClassRunner::class)
class ComposeActivityBaselineProfileGenerator {

Expand All @@ -42,16 +38,16 @@ class ComposeActivityBaselineProfileGenerator {
maxIterations = 15,
stableIterations = 3
) {
// Start into the Compose Activity
startActivityAndWait(Intent("$TARGET_PACKAGE.COMPOSE_ACTIVITY"))
uiAutomator {
// Start into the Compose Activity
startIntent(Intent("$TARGET_PACKAGE.COMPOSE_ACTIVITY"))

// Scrolling through the Compose journey
device.waitAndFind(By.res("myLazyColumn")).also {
it.setGestureMargin(device.displayWidth / 10)
it.fling(Direction.DOWN)
// Scrolling through the Compose journey
with(onView { viewIdResourceName == "myLazyColumn" }) {
setGestureMargin(device.displayWidth / 10)
listOf(Direction.DOWN, Direction.UP).forEach { fling(it) }
}
}

device.findOrFail(By.res("myLazyColumn")).fling(Direction.UP)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package com.example.macrobenchmark.baselineprofile
import android.content.Intent
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.uiautomator.By
import androidx.test.uiautomator.uiAutomator
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -37,12 +37,13 @@ class LoginBaselineProfileGenerator {
maxIterations = 15,
stableIterations = 3
) {
device.clearData(this)
startActivityAndWait(Intent("$packageName.LOGIN_ACTIVITY"))
device.findObject(By.res("userName")).text = "user"
device.findObject(By.res("password")).text = "password"
device.findObject(By.res("login")).click()
device.waitForIdle()
uiAutomator {
clearData()
startIntent(Intent("$packageName.LOGIN_ACTIVITY"))
onView { viewIdResourceName == "userName" }.text = "user"
onView { viewIdResourceName == "password" }.text = "password"
onView { viewIdResourceName == "login" }.click()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The waitForIdle() call is removed. This call is important to ensure that the UI is fully loaded before proceeding with the next action. It should be added back to ensure the reliability of the benchmark.

onView { viewIdResourceName == "login" }.click()
                uiDevice.waitForIdle()

}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ package com.example.macrobenchmark.baselineprofile
import android.content.Intent
import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import org.junit.Ignore
import androidx.test.uiautomator.uiAutomator
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@Ignore // TODO flinging not working, ignore for now to test the pipeline
@RunWith(AndroidJUnit4ClassRunner::class)
class RecyclerViewActivityBaselineProfileGenerator {

Expand All @@ -40,14 +38,14 @@ class RecyclerViewActivityBaselineProfileGenerator {
maxIterations = 15,
stableIterations = 3
) {
// Start into the RecyclerViewActivity
startActivityAndWait(Intent("$TARGET_PACKAGE.RECYCLER_VIEW_ACTIVITY"))
uiAutomator {
// Start into the RecyclerViewActivity
startIntent(Intent("$TARGET_PACKAGE.RECYCLER_VIEW_ACTIVITY"))

// Scrolling RecyclerView journey
device.findObject(By.res(packageName, "recycler")).also {
it.setGestureMargin(device.displayWidth / 10)
it.fling(Direction.DOWN)
it.fling(Direction.UP)
with(onView { viewIdResourceName == "recycler" }) {
setGestureMargin(device.displayWidth / 10)
listOf(Direction.DOWN, Direction.UP).forEach { fling(it) }
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.example.macrobenchmark.baselineprofile

import androidx.benchmark.macro.junit4.BaselineProfileRule
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner
import androidx.test.uiautomator.uiAutomator
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -41,7 +42,9 @@ class StartupProfileGenerator {
stableIterations = 3,
includeInStartupProfile = true
) {
startActivityAndWait()
uiAutomator {
startApp()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

startActivityAndWait is a macrobenchmark function, this new one is a UIAutomator function. Are we sure this is correct? startActivityAndWait isn't doing anything else that now wouldn't be done?

}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import androidx.benchmark.macro.StartupMode
import androidx.benchmark.macro.StartupTimingMetric
import androidx.benchmark.macro.junit4.MacrobenchmarkRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiAutomatorTestScope
import androidx.test.uiautomator.uiAutomator
import com.example.macrobenchmark.benchmark.permissions.allowNotifications
import com.example.macrobenchmark.benchmark.util.DEFAULT_ITERATIONS
import com.example.macrobenchmark.benchmark.util.TARGET_PACKAGE
import com.example.macrobenchmark.benchmark.permissions.allowNotifications
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -51,33 +52,35 @@ class LoginBenchmark {
@Test
fun loginInSetupBlock() {
benchmarkLoginActivity(setupBlock = {
startActivityAndWait(Intent("$packageName.LOGIN_ACTIVITY"))
login()
uiAutomator {
startIntent(Intent("$packageName.LOGIN_ACTIVITY"))
login()
}
})
}

@Test
fun loginWithUiAutomator() {
benchmarkLoginActivity {
login()
uiAutomator { login() }
}
}

@Test
fun loginInAfterPermissionsGranted() {
benchmarkLoginActivity(setupBlock = {
allowNotifications()

startActivityAndWait(Intent("$packageName.LOGIN_ACTIVITY"))
login()
uiAutomator {
startIntent(Intent("$packageName.LOGIN_ACTIVITY"))
login()
}
})
}

private fun MacrobenchmarkScope.login() {
device.findObject(By.res("userName")).text = "user"
device.findObject(By.res("password")).text = "password"
device.findObject(By.res("login")).click()
device.waitForIdle()
private fun UiAutomatorTestScope.login() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding uiDevice.waitForIdle() after the click action to ensure the UI is fully updated before the benchmark finishes.

onView { viewIdResourceName == "login" }.click()
        uiDevice.waitForIdle()

onView { viewIdResourceName == "userName" }.text = "user"
onView { viewIdResourceName == "password" }.text = "password"
onView { viewIdResourceName == "login" }.click()
}

private fun benchmarkLoginActivity(
Expand All @@ -93,12 +96,10 @@ class LoginBenchmark {
iterations = DEFAULT_ITERATIONS,
setupBlock = setupBlock,
) {
startActivityAndWait(
Intent()
.putExtras(extras)
.setAction("$packageName.LOGIN_ACTIVITY")
)
measureBlock()
uiAutomator {
startIntent(Intent().putExtras(extras).setAction("$packageName.LOGIN_ACTIVITY"))
measureBlock()
}
}
}
}
Loading
Loading