-
-
Save xrigau/11284124 to your computer and use it in GitHub Desktop.
<?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 + " :'("); | |
} | |
} | |
} |
Great use case to enhance the gradle-android-command plugin.
Could you please explain why/how this needed for espresso?
👍 important to not add that permission to the live release lol!
@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
The manifest parsing can be avoided using different AndroidManifest files for each flavour and using manifest merging
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.
in grantAnimationPermission packageName should be changed to applicationId
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
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
I made another changes here https://gist.github.com/danielgomezrico/9371a79a7222a156ddad
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
Look at this DeviceAnimationTestRule
Thanks for this I created a custom testrule with the help of this Gist
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.
@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.
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
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.
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.
What this does:
There's a lot of room for improvements, so please feel free to help improving this.