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...).
#!/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
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
}
}
}
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>
}
<?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>