-
-
Save danielgomezrico/9371a79a7222a156ddad to your computer and use it in GitHub Desktop.
Android - AndroidJUnitRunner that disable animations, disable screen lock and wake processor all the time to avoid Tests to fail because of test device setup. Note that my test buildType is mock to have a manifest just for tests (dont want to ship an app with SET_ANIMATION_SCALE permissions...).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<!-- This file should be outside of release manifest (in this case app/src/mock/Manifest.xml --> | |
<manifest | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
package="com.example.tests"> | |
<!-- For espresso testing purposes, this is removed in live builds, but not in dev builds --> | |
<uses-permission android:name="android.permission.SET_ANIMATION_SCALE" /> | |
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> | |
<uses-permission android:name="android.permission.WAKE_LOCK" /> | |
</manifest> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def appId = "com.example.tests" | |
def appVersionName = "1.0" | |
def appVersionCode = 2 | |
android { | |
compileSdkVersion 23 | |
buildToolsVersion "23.0.1" | |
defaultConfig { | |
applicationId appId | |
minSdkVersion 15 | |
targetSdkVersion 23 | |
versionCode appVersionCode | |
versionName appVersionName | |
multiDexEnabled = true // Avoid Bug with java 1.7 | |
testInstrumentationRunner "com.example.tests.PreparerTestRunner" | |
//testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | |
testBuildType "mock" | |
} | |
} | |
dependencies { | |
androidTestCompile 'com.android.support.test:runner:0.4' | |
androidTestCompile 'com.android.support.test:rules:0.4' | |
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' | |
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.1' | |
androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.1') { | |
// this library uses the newest app compat v22 but the espresso contrib still v21. | |
// you have to specifically exclude the older versions of the contrib library or | |
// there will be some conflicts | |
exclude group: 'com.android.support', module: 'appcompat' | |
exclude group: 'com.android.support', module: 'support-v4' | |
exclude module: 'recyclerview-v7' | |
} | |
androidTestCompile 'junit:junit:4.12' | |
androidTestCompile 'com.squareup.retrofit:retrofit-mock:1.9.0' | |
androidTestCompile 'com.squareup.assertj:assertj-android:1.1.0' | |
androidTestCompile 'com.squareup.spoon:spoon-client:1.2.0' | |
} | |
// Grant animation permissions to avoid test failure because of ui sync. | |
task grantAnimationPermissions(type: Exec, dependsOn: 'installMock') { | |
group = 'test' | |
description = 'Grant permissions for testing.' | |
def absolutePath = file('..') // Get project absolute path | |
commandLine "$absolutePath/set_animation_permissions.sh $appId".split(" ") | |
} | |
// Source: http://stackoverflow.com/q/29908110/112705 | |
afterEvaluate { | |
// When launching individual tests from Android Studio, it seems that only the assemble tasks | |
// get called directly, not the install* versions | |
tasks.each { task -> | |
if (task.name.startsWith('assembleMockAndroidTest')) { | |
task.dependsOn grantAnimationPermissions | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.Manifest; | |
import android.app.KeyguardManager; | |
import android.content.Context; | |
import android.content.pm.PackageManager; | |
import android.os.Bundle; | |
import android.os.IBinder; | |
import android.os.PowerManager; | |
import android.support.test.runner.AndroidJUnitRunner; | |
import android.util.Log; | |
import java.lang.reflect.Method; | |
import static android.content.Context.KEYGUARD_SERVICE; | |
import static android.content.Context.POWER_SERVICE; | |
import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP; | |
import static android.os.PowerManager.FULL_WAKE_LOCK; | |
import static android.os.PowerManager.ON_AFTER_RELEASE; | |
import static android.support.test.InstrumentationRegistry.getInstrumentation; | |
/** | |
* Tests can fail for other reasons than code, it´ because of the animations and espresso sync and | |
* emulator state (screen off or locked) | |
* | |
* Before all the tests prepare the device to run tests and avoid these problems. | |
* | |
* - Disable animations | |
* - Disable keyguard lock | |
* - Set it to be awake all the time (dont let the processor sleep) | |
* | |
* @see <a href="u2020 open source app by Jake Wharton">https://github.com/JakeWharton/u2020</a> | |
* @see <a href="Daj gist">https://gist.github.com/daj/7b48f1b8a92abf960e7b</a> | |
* @see <a href="Android-test-kit Disabling Animations">https://code.google.com/p/android-test-kit/wiki/DisablingAnimations</a> | |
*/ | |
public final class PreparerTestRunner extends AndroidJUnitRunner { | |
@Override | |
public void onStart() { | |
runOnMainSync(() -> { | |
Context app = getTargetContext().getApplicationContext(); | |
disableAnimations(app); | |
String name = PreparerTestRunner.class.getSimpleName(); | |
// Unlock the device so that the tests can input keystrokes. | |
KeyguardManager keyguard = (KeyguardManager) app.getSystemService(KEYGUARD_SERVICE); | |
keyguard.newKeyguardLock(name).disableKeyguard(); | |
// Wake up the screen. | |
PowerManager power = (PowerManager) app.getSystemService(POWER_SERVICE); | |
power.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | ON_AFTER_RELEASE, name) | |
.acquire(); | |
}); | |
super.onStart(); | |
} | |
@Override | |
public void finish(int resultCode, Bundle results) { | |
super.finish(resultCode, results); | |
enableAnimations(getInstrumentation().getContext()); | |
} | |
//<editor-fold desc="Animations"> | |
void disableAnimations(Context context) { | |
int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE); | |
if (permStatus == PackageManager.PERMISSION_GRANTED) { | |
setSystemAnimationsScale(0.0f); | |
} | |
} | |
void enableAnimations(Context context) { | |
int permStatus = context.checkCallingOrSelfPermission(Manifest.permission.SET_ANIMATION_SCALE); | |
if (permStatus == PackageManager.PERMISSION_GRANTED) { | |
setSystemAnimationsScale(1.0f); | |
} | |
} | |
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 + " :'("); | |
} | |
} | |
//</editor-fold> | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# source https://github.com/zielmicha/adb-wrapper | |
# | |
# argument: apk package | |
# Set permission android.permission.SET_ANIMATION_SCALE for each device. | |
# ex: sh set_animation_permissions.sh <package> | |
# | |
adb=$ANDROID_HOME/platform-tools/adb | |
package=$1 | |
if [ "$#" = 0 ]; then | |
echo "No parameters found, run with sh set_animation_permissions.sh <package>" | |
exit 0 | |
fi | |
# get all the devices | |
devices=$($adb devices | grep -v 'List of devices' | cut -f1 | grep '.') | |
for device in $devices; do | |
echo "Setting permissions to device" $device "for package" $package | |
$adb -s $device shell pm grant $package android.permission.SET_ANIMATION_SCALE | |
done |
@roboto gist updated
Notes: not working with Marshmallow emulator.
https://code.google.com/p/android-developer-preview/issues/detail?id=3229
Per https://stackoverflow.com/questions/43751079/espresso-testing-disable-animation, there's a flag in gradle you can use now instead.
android {
...
testOptions {
animationsDisabled=true
}
}
@jamespullar it worked for you? it is not working for me
@jamespullar, same as @caipivara, animationsDisabled
doesn't do anything, checkout this comment: link
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, what exacty is variable $appId and how to create them ?