Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android testing: Waited for the root of the view hierarchy to have window focus

In Android Ui testing, I want to click on a spinner item in a dialog, but it pop up with this error:

va.lang.RuntimeException: Waited for the root of the view hierarchy to have window focus and not be requesting layout for over 10 seconds. If you specified a non default root matcher, it may be picking a root that never takes focus. Otherwise, something is seriously wrong. Selected Root:
Root{application-window-token=android.view.ViewRootImpl$W@2dac97c7, window-token=android.view.ViewRootImpl$W@2dac97c7, has-window-focus=false, layout-params-type=1, layout-params-string=WM.LayoutParams{(0,0)(fillxfill) sim=#10 ty=1 fl=#81810100 pfl=0x8 wanim=0x1030461 surfaceInsets=Rect(0, 0 - 0, 0) mwfl=0x0}, decor-view-string=MultiPhoneDecorView{id=-1, visibility=VISIBLE, width=1600, height=2560, has-focus=true, has-focusable=true, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}}
. All Roots:
Root{application-window-token=android.view.ViewRootImpl$W@3c913e1, window-token=android.view.ViewRootImpl$W@21b23506, has-window-focus=true, layout-params-type=1002, layout-params-string=WM.LayoutParams{(310,600)(722x480) gr=#10000033 sim=#1 ty=1002 fl=#1860200 fmt=-3 wanim=0x10302db surfaceInsets=Rect(0, 0 - 0, 0) mwfl=0x0}, decor-view-string=PopupViewContainer{id=-1, visibility=VISIBLE, width=722, height=480, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}}
Root{application-window-token=android.view.ViewRootImpl$W@3c913e1, window-token=android.view.ViewRootImpl$W@3c913e1, has-window-focus=false, layout-params-type=2, layout-params-string=WM.LayoutParams{(0,0)(wrapxwrap) gr=#11 sim=#20 ty=2 fl=#1800002 pfl=0x8 fmt=-3 wanim=0x1030462 surfaceInsets=Rect(0, 0 - 0, 0) mwfl=0x10}, decor-view-string=DecorView{id=-1, visibility=VISIBLE, width=1136, height=1058, has-focus=true, has-focusable=true, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}}
Root{application-window-token=android.view.ViewRootImpl$W@2dac97c7, window-token=android.view.ViewRootImpl$W@2dac97c7, has-window-focus=false, layout-params-type=1, layout-params-string=WM.LayoutParams{(0,0)(fillxfill) sim=#10 ty=1 fl=#81810100 pfl=0x8 wanim=0x1030461 surfaceInsets=Rect(0, 0 - 0, 0) mwfl=0x0}, decor-view-string=MultiPhoneDecorView{id=-1, visibility=VISIBLE, width=1600, height=2560, has-focus=true, has-focusable=true, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}}
at android.support.test.espresso.base.RootViewPicker.get(RootViewPicker.java:99)
at android.support.test.espresso.ViewInteractionModule.provideRootView(ViewInteractionModule.java:69)
at android.support.test.espresso.ViewInteractionModule_ProvideRootViewFactory.get(ViewInteractionModule_ProvideRootViewFactory.java:23)
at android.support.test.espresso.ViewInteractionModule_ProvideRootViewFactory.get(ViewInteractionModule_ProvideRootViewFactory.java:9)
at android.support.test.espresso.base.ViewFinderImpl.getView(ViewFinderImpl.java:68)
at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:120)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6117)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)

I have tried

onData(allOf(is(instanceOf(String.class)),containsString("A4"))).inRoot(isPlatformPopup()).perform(click());

and

onView(withText(containsString("A4"))).inRoot(isFocusable()).check(matches(isDisplayed()));

and

onView(withText(containsString("A4"))).inRoot(withDecorView(not(getActivity().getWindow().getDecorView()))).check(matches(isDisplayed()));

but none of them works... Can anyone tell me how to get the ralavant root please?

like image 424
Kimmy Avatar asked Sep 12 '16 19:09

Kimmy


3 Answers

This error can happen when a system dialog is displayed — like "Power Off" or "Unfortunately, Launcher has stopped" (a background app crashed) — and you try to run an Espresso unit test whilst that dialog is visible.

Android system dialog: Unfortunately, Launcher has stopped.

Image credit: Android 4.0 emulator always has a crashing Launcher?

You can workaround it in code by dismissing the system dialog before running the test:

// Use the 'testing' Context
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));

// Alternative: Use the Activity for the Context
MyActivity activityUnderTest = activityTestRule.getActivity();
activityUnderTest.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));

Or send the broadcast on the command line using adb:

adb shell am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS

Another cause of the error is when a background app is frozen (ANR) or is running slowly, and a system dialog appears saying "Launcher isn't responding. Do you want to close it? [Wait] [OK]":

Android system dialog: Launcher isn't Responding. Do you want to close it? Wait, OK

Image credit: https://engineering.linkedin.com/blog/2016/08/introducing-and-open-sourcing-test-butler--reliable-android-test

If you try to run an Espresso test while this dialog is visible, the tests will all fail and show the "Waited for the root..." error. There's no easy way to close this dialog programmatically. Espresso cannot click these buttons, for the reasons described here: Dismiss Alert Dialog in Android Espresso Test . However, one way is to use UI Automator to press the "Wait" button in the dialog, just before a test starts:

app/build.gradle

dependencies {
    ...
    androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
}

ActivityUiTest.kt

companion object {
    @JvmStatic
    @BeforeClass
    fun dismissANRSystemDialog() {
        val device = UiDevice.getInstance(getInstrumentation())
        // If running the device in English Locale
        var waitButton = device.findObject(UiSelector().textContains("wait"))
        if (waitButton.exists()) {
            waitButton.click()
        }
        // If running the device in Japanese Locale
        waitButton = device.findObject(UiSelector().textContains("待機"))
        if (waitButton.exists()) {
            waitButton.click()
        }
    }
}

ActivityUiTest.java

@BeforeClass
public static void dismissANRSystemDialog() throws UiObjectNotFoundException {
    UiDevice device = UiDevice.getInstance(getInstrumentation());
    // If the device is running in English Locale
    UiObject waitButton = device.findObject(new UiSelector().textContains("wait"));
    if (waitButton.exists()) {
        waitButton.click();
    }
    // If the device is running in Japanese Locale
    waitButton = device.findObject(new UiSelector().textContains("待機"));
    if (waitButton.exists()) {
        waitButton.click();
    }
}

Alternatively, you could use adb on the command line to send screen taps or key strokes to dismiss it. For example:

# On a 320x480 screen, click at screen location [x=233,y=293] to tap an "OK" dialog button.
# Just in case there is a "Launcher isn't responding" system dialog.
adb shell input tap 233 293

or

# Send keystroke Arrow Right
sleep 3; adb shell input keyevent 22
# Send keystroke Arrow Right again
sleep 3; adb shell input keyevent 22
# Send keystroke Enter to press a button on the dialog
sleep 3; adb shell input keyevent 66

More info:

  • https://groups.google.com/forum/#!topic/android-test-kit-discuss/yIEwus_hjeY
  • How to dismiss system dialog in Android?
  • Android adb shell am broadcast: Bad component name
  • How to use ADB to send touch events to device using sendevent command?
like image 79
Mr-IDE Avatar answered Oct 07 '22 13:10

Mr-IDE


I had the same error, when I used a Spinner inside a DialogFragment. This is the only code that was working for me:

onView(withText(containsString("A4"))).inRoot(isPlatformPopup()).check(matches(isDisplayed()));
like image 37
StefanTo Avatar answered Oct 07 '22 13:10

StefanTo


I had similar issue when a dialog popup contains spinner items (dropdown list), my click couldn't perform on any of the spinner items and got the same error. I found a solution by using onData() method with RootMatchers:

onData(anything()).inRoot(RootMatchers.isPlatformPopup()).atPosition(1).perform(click());

Note that, the index value in atPosition() is the item's index value from the spinner list.

like image 33
Jamil Rahman Avatar answered Oct 07 '22 12:10

Jamil Rahman