-
-
Save nbarraille/03e8910dc1d415ed9740 to your computer and use it in GitHub Desktop.
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2015 - Nathan Barraille | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
* | |
*/ | |
package net.slideshare.mobile.test.util; | |
import android.app.Activity; | |
import android.content.pm.ActivityInfo; | |
import android.support.test.espresso.UiController; | |
import android.support.test.espresso.ViewAction; | |
import android.support.test.internal.runner.lifecycle.ActivityLifecycleMonitorRegistry; | |
import android.support.test.runner.lifecycle.Stage; | |
import android.view.View; | |
import org.hamcrest.Matcher; | |
import java.util.Collection; | |
import static android.support.test.espresso.matcher.ViewMatchers.isRoot; | |
/** | |
* An Espresso ViewAction that changes the orientation of the screen | |
*/ | |
public class OrientationChangeAction implements ViewAction { | |
private final int orientation; | |
private OrientationChangeAction(int orientation) { | |
this.orientation = orientation; | |
} | |
@Override | |
public Matcher<View> getConstraints() { | |
return isRoot(); | |
} | |
@Override | |
public String getDescription() { | |
return "change orientation to " + orientation; | |
} | |
@Override | |
public void perform(UiController uiController, View view) { | |
uiController.loopMainThreadUntilIdle(); | |
final Activity activity = (Activity) view.getContext(); | |
activity.setRequestedOrientation(orientation); | |
Collection<Activity> resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED); | |
if (resumedActivities.isEmpty()) { | |
throw new RuntimeException("Could not change orientation"); | |
} | |
} | |
public static ViewAction orientationLandscape() { | |
return new OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); | |
} | |
public static ViewAction orientationPortrait() { | |
return new OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
} | |
} |
The ClassCastException
can occur when using AppCompatActivity
- this tends to wrap the Context
in a ContextWrapper
. You may also see a TintContextWrapper
dependent on what you are doing. To get the Activity
you need to unwrap the context using ContextWrapper.getBaseContext()
until you get to an Activity
. NOTE: Activity
itself extends ContextThemeWrapper
This uses recursion which is not obviously ideal but should give you an idea:
public static Activity getActivity(Context context) {
if (context instanceof Activity) {
return (Activity) context;
}
if (context instanceof ContextWrapper) {
return getActivity(((ContextWrapper) context).getBaseContext());
}
return null;
}
Is there an advantage using this instead of e.g. simply activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); ?
Although it works, it was very slow in my case. It does not wait for the orientation to be changed. Same goes for
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
@mattmook there's nothing keeping you from replacing recursion with a loop:
public static Activity getActivity(Context context) {
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity)context;
}
context = ((ContextWrapper)context).getBaseContext();
}
return null;
}
Thank you very much for so beautiful code. But how to perform Espresso tests on the second Activity? It seems that my tests are still applied on the old instance.
I can use SystemClock.sleep(1000); But is there a way to avoid it? :)
Thank you very much for your great snippet! :)
In case, this doesn't work for you because of (since Android 7.0?) com.android.internal.policy.DecorContext
's baseContext is the Application not an Activity, try out a variation of the following code.
activity = getActivity(view.getContext());
if (activity == null && view instanceof ViewGroup) {
ViewGroup v = (ViewGroup)view;
int c = v.getChildCount();
for (int i = 0; i < c && activity == null; ++i) {
activity = getActivity(v.getChildAt(i).getContext());
}
}
Building upon @TWiStErRob and @simonracz answers I've created a fork of this ViewAction which works fine with Android 7.0 and lower.
Kotlin version:
import android.view.View
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import org.hamcrest.Matcher
import android.content.pm.ActivityInfo
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import android.app.Activity
import androidx.test.runner.lifecycle.Stage
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 - Nathan Barraille
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
class OrientationChangeAction(private val orientation: Int): ViewAction {
companion object {
fun orientationLandscape(): ViewAction = OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
fun orientationPortrait(): ViewAction = OrientationChangeAction(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
}
override fun getDescription(): String = "change orientation to $orientation"
override fun getConstraints(): Matcher<View> = isRoot()
override fun perform(uiController: UiController, view: View) {
uiController.loopMainThreadUntilIdle()
var activity = getActivity(view.context)
if (activity == null && view is ViewGroup) {
val c = view.childCount
var i = 0
while (i < c && activity == null) {
activity = getActivity(view.getChildAt(i).context)
++i
}
}
activity!!.requestedOrientation = orientation
}
private fun getActivity(context: Context): Activity? {
var context = context
while (context is ContextWrapper) {
if (context is Activity) {
return context
}
context = (context as ContextWrapper).baseContext
}
return null
}
}
@nbarraille Does this work when we use config annotation?
I tried to run Espresso test case, it worked, but not with Robolectric. I guess I might facing robolectric issue!!
@Config(
qualifiers = "port-xxhdpi"
)
I'm getting
java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity
when calling it withonView(isRoot()).perform(orientationLandscape());
The root view's Context doesn't seem to be an Activity…