Setting animation scale for Android UI tests

Karol WrótniakAndroid Developer

The issue

The simplest (and only officially mentioned) way to disable animations is to go manually to Settings app and adjust appropriate settings in Developer options.

That way is very inconvenient when tests are automated e.g. in CI environment. Devices and/or AVDs used there may be reset to factory settings between different jobs. So we have to add some mechanism which automatically disables animations before starting tests.

Existing solutions

The vast majority of the solutions I’ve found on the internet consist of modifying system settings programmatically from an interior of the tests. This approach requires SET_ANIMATION_SCALE permission. Almost all apps don’t need it usually so it must be added to AndroidManifest.xml. Adding it is not enough, it needs to be explicitly granted.

After that preparations we are able to set animation scale. Unfortunately there is no public API for that so reflection needs to be used. You can see that approach eg. in this snippet collection or DeviceAnimationTestRule project. Such huge number of lines of code only to change 3 settings options… Moreover animations may be disabled multiple times in this approach. Even if they was disabled in previous test, a rule or runner disables it again in next one (or at least next test class). That leads to increased test time, overhead may be significant if you have hundreds or thousands of tests.

Theoretically all those disadvantages may be acceptable. There is some boilerplate code and some actions are performed multiple times needlessly but tests works. Well, nothing could be further from the truth. There is an issue which causes random failures while trying to execute shell command using UiAutomator:

Java

1

2

3

4

5

6

E/UiAutomation:Error executing shell command!

android.os.DeadObjectException

at android.os.BinderProxy.transactNative(NativeMethod)

at android.os.BinderProxy.transact(Binder.java:615)

at android.app.IUiAutomationConnection$Stub$Proxy.executeShellCommand(IUiAutomationConnection.java:418)

at android.app.UiAutomation.executeShellCommand(UiAutomation.java:994)

So we went back to the beginning. Flakiness caused by animations have been transformed to flakiness due to shell command execution… There must be a better way to disable animations!

The better way

Natural solution which comes into mind is to execute ADB shell command from host before running tests. There is a simple command to change the settings: adb shell settings put <namespace> <key> <value> (NB it was proposed in the already linked gist).

However adb invoked directly as a shell command on host supports only single device. We can get rid of this limitation by using ddmlib API. It is a part of Android Gradle Plugin which is applied to all Gradle-based Android projects.

Basic solution

Optionally we can add that task as a dependency so that animations will be automatically disabled before running tests. We can also add reverse task which resets animation settings after tests finish. All that is doable in buildscript, however this solution is not so elegant since it requires copy-pasting to each project. Can we do something better? Of course we can!

Final solution

To make solution easily reusable, we can put it into Gradle plugin. Complete source code is available in github repository. Let’s dive into key parts only:

Java

1

2

3

4

5

6

7

8

9

fun Project.addAnimationTasksWithDependencies()=afterEvaluate{

val disableAnimations=createAnimationScaleTask(false)

val enableAnimations=createAnimationScaleTask(true)

tasks.withType(DeviceProviderInstrumentTestTask::class.java).forEach{

it.dependsOn(disableAnimations)

it.finalizedBy(enableAnimations)

}

}

Here we create tasks which executes appropriate shell commands and add them to the graph. Our task disabling animations becomes dependency of all tasks performing tests on devices. We don’t need to know their names eg. connectedDebugAndroidTest (which depends on test build type and product flavors) since we search for them by class.

Analogously we declare animation enabling as a finalizer of test task. It will run whenever tests succeed or not. So animation settings on devices will always be restored to default values after tests. Even if there are more test tasks (due to multiple product flavors) each animation scale change will be executed only once – disable before first test and enable after last one.

Finally, we can publish our plugin to Gradle plugin portal. After that it can be applied to any project by just adding one line to buildscript plugins stanza, like that: