-
-
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.Activitywhen calling it withonView(isRoot()).perform(orientationLandscape());The root view's Context doesn't seem to be an Activity…