Instantly share code, notes, and snippets.

Embed
What would you like to do?
Disable animations for Espresso tests - run with `gradle cATDD`
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.novoda.espresso">
<!-- For espresso testing purposes, this is removed in live builds, but not in dev builds -->
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE" />
<!-- ... -->
</manifest>
apply plugin: 'android'
android {
// ...
productFlavors {
dev {
// The one for development/testing
}
live {
// The flavour for releasing
}
}
}
// ...
task grantAnimationPermission(type: Exec, dependsOn: 'installDevDebug') { // or install{productFlavour}{buildType}
commandLine "adb shell pm grant $android.defaultConfig.packageName android.permission.SET_ANIMATION_SCALE".split(' ')
}
tasks.whenTaskAdded { task ->
if (task.name.startsWith('connectedAndroidTest')) {
task.dependsOn grantAnimationPermission
}
}
def copyAndReplaceText(source, dest, Closure replaceText) {
dest.write(replaceText(source.text))
}
// Override Data in Manifest - This can be done using different Manifest files for each flavor, this way there's no need to modify the manifest
android.applicationVariants.all { variant ->
if (variant.name.startsWith('dev')) { // Where dev is the one you'll use to run Espresso tests
System.out.println("Not removing the SET_ANIMATION_SCALE permission for $variant.name")
return
}
System.out.println("Removing the SET_ANIMATION_SCALE permission for $variant.name")
variant.processManifest.doLast {
copyAndReplaceText(manifestOutputFile, manifestOutputFile) {
def replaced = it.replace('<uses-permission android:name="android.permission.SET_ANIMATION_SCALE"/>', '');
if (replaced.contains('SET_ANIMATION_SCALE')) {
// For security, imagine an extra space is added before closing tag, then the replace would fail - TODO use regex
throw new RuntimeException("Don't ship with this permission! android.permission.SET_ANIMATION_SCALE")
}
replaced
}
}
}
public class MyInstrumentationTestCase extends ActivityInstrumentationTestCase2<MyActivity> {
private SystemAnimations systemAnimations;
public MyInstrumentationTestCase() {
super(MyActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
systemAnimations = new SystemAnimations(getInstrumentation().getContext());
systemAnimations.disableAll();
getActivity();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
systemAnimations.enableAll();
}
}
class SystemAnimations {
private static final String ANIMATION_PERMISSION = "android.permission.SET_ANIMATION_SCALE";
private static final float DISABLED = 0.0f;
private static final float DEFAULT = 1.0f;
private final Context context;
SystemAnimations(Context context) {
this.context = context;
}
void disableAll() {
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
if (permStatus == PackageManager.PERMISSION_GRANTED) {
setSystemAnimationsScale(DISABLED);
}
}
void enableAll() {
int permStatus = context.checkCallingOrSelfPermission(ANIMATION_PERMISSION);
if (permStatus == PackageManager.PERMISSION_GRANTED) {
setSystemAnimationsScale(DEFAULT);
}
}
private void setSystemAnimationsScale(float animationScale) {
try {
Class<?> windowManagerStubClazz = Class.forName("android.view.IWindowManager$Stub");
Method asInterface = windowManagerStubClazz.getDeclaredMethod("asInterface", IBinder.class);
Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
Method getService = serviceManagerClazz.getDeclaredMethod("getService", String.class);
Class<?> windowManagerClazz = Class.forName("android.view.IWindowManager");
Method setAnimationScales = windowManagerClazz.getDeclaredMethod("setAnimationScales", float[].class);
Method getAnimationScales = windowManagerClazz.getDeclaredMethod("getAnimationScales");
IBinder windowManagerBinder = (IBinder) getService.invoke(null, "window");
Object windowManagerObj = asInterface.invoke(null, windowManagerBinder);
float[] currentScales = (float[]) getAnimationScales.invoke(windowManagerObj);
for (int i = 0; i < currentScales.length; i++) {
currentScales[i] = animationScale;
}
setAnimationScales.invoke(windowManagerObj, new Object[]{currentScales});
} catch (Exception e) {
Log.e("SystemAnimations", "Could not change animation scale to " + animationScale + " :'(");
}
}
}
@xrigau

This comment has been minimized.

Owner

xrigau commented Apr 25, 2014

What this does:

  • Creates 2 build flavours, one will be used for Espresso tests and the other for releasing the app
  • Adds the SET_ANIMATION_SCALE permission to AndroidManifest
  • Remove the SET_ANIMATION_SCALE permission from the releasing flavour's generated Manifest.
  • Runs a task that installs the APK and runs the adb command to grant permission to the app to change animation scale.
  • Uses a custom InstrumentationTestRunner that disables before calling an Activity's onCreate and enables animations after calling an Activity's onDestroy using the code provided above.

There's a lot of room for improvements, so please feel free to help improving this.

@devisnik

This comment has been minimized.

devisnik commented Apr 25, 2014

Great use case to enhance the gradle-android-command plugin.

Could you please explain why/how this needed for espresso?

@blundell

This comment has been minimized.

blundell commented Apr 25, 2014

👍 important to not add that permission to the live release lol!

@xrigau

This comment has been minimized.

Owner

xrigau commented Apr 25, 2014

@devisnik Yes that would make sense. I made this to disable system animations programmatically, since animations cause flaky tests (sometimes a test would fail just because an animation is running). The idea is that we don't need to do it manually on each device, instead we can disable before tests and enable afterwards - should this be made during setUp & tearDown instead?

Note that I took a snippet from the Espresso wiki and worked from there: https://code.google.com/p/android-test-kit/wiki/DisablingAnimations

@xrigau

This comment has been minimized.

Owner

xrigau commented Apr 29, 2014

The manifest parsing can be avoided using different AndroidManifest files for each flavour and using manifest merging

@jonathanstiansen

This comment has been minimized.

jonathanstiansen commented Aug 11, 2014

In fact, flavours are unnecessary for just testing (unless you are testing your production app, which I guess makes a lot of sense), since it can be added to the 'debug' folder. It will use debug to run tests.

@solcott

This comment has been minimized.

solcott commented Dec 22, 2014

in grantAnimationPermission packageName should be changed to applicationId

@denisk20

This comment has been minimized.

denisk20 commented Jan 11, 2015

No need to put SET_ANIMATION_SCALE permission into the main manifest and remove it afterwards. Just create a build flavor for integration tests and put the manifest with a single permission into it - it will be merged by the manifest merger when integration tests are be run. For example:
build.gradle

productFlavors {
    /**
     * This flavor is to be run only using connectedAndroidTestExtended
     */
    extended {}
}

src/extended/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.mypackage">
    <uses-permission android:name="android.permission.SET_ANIMATION_SCALE"/>
</manifest>

Run integration tests with

./gradlew connectedAndroidTestExtended

Check this for more details:
http://stackoverflow.com/q/27826935/369317

@daj

This comment has been minimized.

daj commented May 5, 2015

I had to make quite a lot of changes to get this Gist to work with Android Studio 1.1.0 and Espresso 2.0. Here's my updated Gist: https://gist.github.com/daj/7b48f1b8a92abf960e7b

@caipivara

This comment has been minimized.

caipivara commented Sep 24, 2015

@tasomaniac

This comment has been minimized.

tasomaniac commented Jan 4, 2016

I know it is not the same thing but it can also be achieved by just the following adb commands. (Only for API 17 and above)

  • adb shell settings put global window_animation_scale 0
  • adb shell settings put global transition_animation_scale 0
  • adb shell settings put global animator_duration_scale 0

Here how it looks like when it is applied to Travis.
tasomaniac/android-topeka@e99e6f3#diff-354f30a63fb0907d4ad57269548329e3R19

@robertofrontado

This comment has been minimized.

robertofrontado commented Nov 17, 2016

@timrijckaert

This comment has been minimized.

timrijckaert commented Dec 3, 2016

Thanks for this I created a custom testrule with the help of this Gist

@tasomaniac

This comment has been minimized.

tasomaniac commented Jan 2, 2017

Disabling system animations support has been added to Novoda Android Command Plugin as well.
https://github.com/novoda/gradle-android-command-plugin/

With the plugin, you can use ./gradlew disableSystemAnimations

You can also put this snippet into you build.gradle file to automatically disable/enable animations before/after connected tests.

afterEvaluate {
  if (tasks.findByPath("connectedAndroidTest") != null) {
    connectedAndroidTest.dependsOn disableSystemAnimations
    connectedAndroidTest.finalizedBy enableSystemAnimations
  }
}

Note: null check was necessary for us because of the latest changes in instant run. It doesn't create all the configurations in advance anymore.

@twyatt

This comment has been minimized.

twyatt commented Jan 3, 2017

@tasomaniac Not sure what I'm doing wrong but issuing any of the adb commands you listed in your Jan 4, 2016 comment doesn't have any effect until I restart my emulator?

Tried with Nexus 5 API 21 AVD and with Genymotion running an API 21 image; neither would change animation scales using adb until I restarted the emulator.

@Kisty

This comment has been minimized.

Kisty commented Jun 2, 2017

Have you seen https://github.com/linkedin/test-butler? Does the same thing but automatically, just need to install a tiny app on emulator.

Gradle support to auto install the test helper app works from 3.0.0-alpha1 or using a little gradle script

@eighthave

This comment has been minimized.

eighthave commented Aug 9, 2018

Why not just make a manifest in the existing androidTest flavor? I.e. app/src/androidTest/AndroidManifest.xml. Then you don't need any special build flavor to include this.

@eighthave

This comment has been minimized.

eighthave commented Aug 9, 2018

I see, because the disable animations thing is happening in the app, rather than in the instrumentation. If someone knows how to run it in the instrumentation, it would eliminate the need for build flavors and other gradle tricks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment