Android Espresso, Wake up device before test. How to use a custom manifest for test?
Asked Answered
P

9

25

I've been writing tests with androids new espresso framework and find that it works well. One annoying thing (not particular to espresso) is that I have to make sure my screen is awake and unlocked for the tests to run. I found a workaround (through various sources) however I am not sure the best way to integrate it.

So this is what I did, in my "Home" activity I have the following code:

Home.class:

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /************ Put this in a conditional for a test version ***********/
    KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
    keyguardLock.disableKeyguard();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}

Also you need to add the following permissions:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

So after doing this my tests now wake my phone up to run so I don't have to stand guard and make sure that the screen doesn't turn off right before the tests start.

I would rather not include those permissions though in my app obviously. I know with gradle it is possible to make different "flavors" that have their own android manifest which will merge into the main manifest. I was thinking of using that but I'd rather not add a flavor just for that reason given this is already using the test build type to run. It looks like from the android gradle documentation that you can't create an AndroidManifest for the instrumentTest directory as it will be auto generated.

However I was wondering if there is another way to do it without creating a variant and then specifying that the tests should run that variant. Also I'm not sure of the exact syntax of all that and thought it would be nice just to have this information on the site for others as it seems to be scattered about.

Lastly, if anyone knows of a better way of solving the issue of waking the phone up for the tests I'd love to hear it as I'm not a big fan of this way I'm attempting.

Policewoman answered 9/11, 2013 at 0:16 Comment(3)
Wouldn't a simple power manager partial wake lock do ?Sandasandakan
I dont want to keep my app awake for the duration of the test (that happens on it's own), I want to wake it up to start the test. I would prefer if I could do the wake up from the test code itself rather than the app but I don't know how.Policewoman
The mechanism to wake applications is the AlarmManager. Don't know about espresso, sorrySandasandakan
P
16

I actually figured out a really easy way to handle this. Remove the keyguard and wakelock permissions from the main manifest and put them in src/debug/AndroidManifest.xml like so:

src/debug/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>

When the app is built for debug the above permissions will merge into the main manifest. By default the build system uses the debug build for instrument tests so this works fine.

Then in my onCreate I put the code mentioned in the question:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (BuildConfig.DEBUG) {
        KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
        KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
        keyguardLock.disableKeyguard();
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    }
    ...
}

Now my phone can run tests without me waking them up by hand first and I didn't have to add the above permissions to the release version of my app.

Policewoman answered 13/11, 2013 at 19:52 Comment(2)
Why should Activity code be modified to fit unit test's requirement? It seems to be an anti-pattern...Kinswoman
Why don't you put the manifest code in the "androidTest" folder and use this code only in your tests? Polluting your code with test boilerplate is a really bad idea.Petra
M
10

The easiest approach is to use adb command like below if you are running the tests from CI environment for example:

adb -s $DEVICE_ID shell input keyevent 82

This will unlock your device screen.

Masker answered 25/5, 2015 at 20:32 Comment(7)
For how long? As configured by user? What happens if that is set to 30 sec and the tests last 60 sec?Suitor
In my opinion this is the best solution to achieve this. It can be easily added as a build step to any build system.Mosira
BTW, you can lock screen/turn off: adb -s $DEVICE_ID shell input keyevent 26Mosira
This won't unlock, this will only turn the screen onCountryfied
input keyevent 3 (KEYCODE_HOME) is more appropriate than other keys. Key code 26 (KEYCODE_POWER) may turn off the screen if the screen was on at that time.Davis
@BhawnaRaheja emulator-$PORTSuzan
You have to do keyevent 3 and then keyevent 82Osteal
S
9

Now that KeyguardLock is deprecated, you can simply use:

    if (BuildConfig.DEBUG) {
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    }
Sparhawk answered 9/2, 2016 at 8:0 Comment(2)
This won't work when you are also using UIAutomator for interaction with UI elements outside the app.Countryfied
this does not work for me. where should this be called?Source
B
5

I've created the src/debug/AndroidManifest.xml file as Matt suggested and added to following code to the testCase:

   @Override
    public void setUp() throws Exception {
        super.setUp();
        // Espresso will not launch our activity for us, we must launch it via getActivity().
        Activity activity = getActivity();
        KeyguardManager km = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
        KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
        keyguardLock.disableKeyguard();
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);

    }
Bummer answered 22/4, 2014 at 14:58 Comment(1)
This works great, except it results in errors about touching the view hierarchy from the wrong thread, at least for me (I'm using Robotium). The solution for me was to run the code inside of a getInstrumentation().runOnMainSync() block.Ashes
A
5

Here is an approach that does not use any deprecated APIs, no manifest permissions, and works even if the device has a lock pin set.

Option 1: In @Before

@RunWith(AndroidJUnit4::class)
class MyActivityTest {

    // Replace with your own Activity test rule.
    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @Before
    fun setUp() {
        UiDevice.getInstance(getInstrumentation()).wakeUp()
        composeTestRule.activity.setShowWhenLocked(true)
    }
}

Option 2: Rule

@RunWith(AndroidJUnit4::class)
class MyActivityTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<MyActivity>()

    @get:Rule
    val screenLockRule = RunWhenScreenOffOrLockedRule()
}

/**
* This rule will allow the tests to run even if the device's screen is off or locked.
* Allows a developer to fire and forget running the UI Test across different devices or on the CI
* emulator.
*/
class RunWhenScreenOffOrLockedRule : TestRule {
    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {

            override fun evaluate() {
                // Turn screen on
                UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).wakeUp()

                // Allow any activity to run when locked
                ActivityLifecycleMonitorRegistry
                    .getInstance()
                    .addLifecycleCallback { activity, stage ->
                        if (stage === Stage.PRE_ON_CREATE) {
                            activity.setShowWhenLocked(true)
                        }
                    }

                // Continue with other statements
                base.evaluate()
            }
        }
    }
}
Avery answered 11/11, 2021 at 15:4 Comment(0)
S
4

Although you already have accepted Matt Wolve answer, I think that polluting your code with test boilerplate is not a good idea (I am aware that using Espresso there are some situations where you have to, like adding idle flags for custom IdlingResources). I would like to add another approach:

    @ClassRule
    public static ActivityTestRule<HomeActivity> mActivityRuleSetUp = new ActivityTestRule<>(
            HomeActivity.class);


    private static void wakeUpDevice(){
        if (BuildConfig.DEBUG){
            HomeActivity homeActivity = mActivityRuleSetUp.getActivity();

            KeyguardManager myKM = (KeyguardManager) homeActivity.getSystemService(HomeActivity.KEYGUARD_SERVICE);
            boolean isPhoneLocked = myKM.inKeyguardRestrictedInputMode();

            if (isPhoneLocked){
                homeActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
            }
        }
     }


    @BeforeClass
    public static void setUp(){
        wakeUpDevice();
    }

Hope it helps.

Sublunary answered 12/11, 2017 at 18:35 Comment(3)
could you elaborate more on this approach? It seems like what I'm trying to do but don't entirely understand how to go about the implementation. Any links to examples?Suzan
There is a missing bracket btw for the method wakeUpDevice(). Why are the methods static btw? What is the point in that?Llanes
Thanks TheOddCoder for the bracket. Apologies but to be honest I can't remember if they were static by restrictions I couldn't manage. Just to say that I always try to avoid every "static" modifier if don't require.Sublunary
E
1

For testing device set Lock pattern to NONE in Settings->Security Then use instance of UiDevice and call its wakeUp() method

This method simulates pressing the power button if the screen is OFF else it does nothing if the screen is already ON. If the screen was OFF and it just got turned ON, this method will insert a 500ms delay to allow the device time to wake up and accept input.

Eloquent answered 18/3, 2016 at 13:25 Comment(0)
G
1

Another best way to wake up device before test. Simply add ActivityLifecycleCallback in your setUp method.

public class Moduletest extends ActivityInstrumentationTestCase2<Your Activity>{

 protected void setUp(){
    super.setUp();

    ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(new ActivityLifecycleCallback() {
      @Override public void onActivityLifecycleChanged(Activity activity, Stage stage) {
        if (stage == Stage.PRE_ON_CREATE) {
          activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        }
      }
    });
  }
}
Goldiegoldilocks answered 9/11, 2016 at 11:42 Comment(0)
A
1

I have done it this way: First make two rules, one for the activity and one for UI Thread:

@Rule
public ActivityTestRule<Your Activity> mActivityRule =
        new ActivityTestRule<>(Your Activity.class, true, true);

@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

And then in my first test method made this:

   @Test
   @LargeTest
   public void CreateAndSaveTaskEntity() throws Throwable {

            uiThreadTestRule.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Your Activity activity = mActivityRule.getActivity();
            activity.getWindow()
                    .addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

                }
            });

            //begin test cases

            ...
    }

Of course you have to add in AndroidManifest.xml the permissions:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
Artiste answered 23/3, 2017 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.