android-test icon indicating copy to clipboard operation
android-test copied to clipboard

ActivityScenario.getResult() occasionally delays 45 secs

Open mikepenz opened this issue 4 years ago • 18 comments

Description

Using the new ActivityScenario for our tests. Sadly in various occasions the getResult() will wait 30 seconds until it returns successfully. This behavior is not deterministic as sometimes it works again.

In both cases the result will be delivered successfully.

In cases the getResult() waits for 45 seconds the tested activity is already finished and the empty activity with black background is shown.

Steps to Reproduce

The steps in our project to reproduce:

launchActivity

ActivityScenario.use{ 
    // do the testing  
    // ui code finishing the activity
 
    // retrieve `result` 
    result.checkResultData()
}

Expected Results

It would not wait for 45 seconds but less time before retrieving the result data

Actual Results

It freezes randomly for 45 seconds before successful result data is retrieved

AndroidX Test and Android OS Versions

  • 3.3.0-rc01
  • Android 11, but also happening on Android 10

Link to a public git repo demonstrating the problem:

mikepenz avatar Jun 24 '20 12:06 mikepenz

This also happens in ActivityScenario.close() after a test manually calls finish() on the activity.

jameswald avatar Jun 30 '20 22:06 jameswald

if there would be something in the log which I can provide to help resolving this problem or anything else that would be great :/

it's adding significant of additional time onto our ui tests

mikepenz avatar Jul 01 '20 15:07 mikepenz

I've been trying to migrate to ActivityScenario / ActivityScenarioRule from the deprecated ActivityTestRule and this black screen / 30 second freeze happens every test for me at the scenario.close() line. I'm going to have to go back to the ActivityTestRule for now :/

free5ty1e avatar Jul 02 '20 20:07 free5ty1e

I started to look further into the issue and I identify the problem. It's actually a race condition + lock on the main thread between the latch which waits for the activity result and the broadcast receiver.

Basically the thought of the InstrumentationActivityInvoker would be to place a latch and await for it to be counted down, the issue surfaces that this latch blocks the main thread, but the broadcast receiver would also be returned on the main thread. So if the latch awaits it will ultimately prevent the broadcast receiver to countdown on the latch, never returning.

This is the problematic latch: https://github.com/android/android-test/blob/master/core/java/androidx/test/core/app/InstrumentationActivityInvoker.java#L268

And here's the problematic broadcast receiver: https://github.com/android/android-test/blob/master/core/java/androidx/test/core/app/InstrumentationActivityInvoker.java#L443-L449

To solve this issue I'd actually recommend that the testing library implements a polling mechanism for the time of of the timeout, and loops to check for the result until the timeout is reached.


So the great news is that I actually also have 1 working workaround and 1 alternative (which doesn't work it seems).

Workaround

// define our custom result property
val ActivityScenario<*>.safeResult: Instrumentation.ActivityResult
    get() {
        awaitBlock { state == Lifecycle.State.DESTROYED } // await for the activity to be destroyed
        return this.result // this will return quick as the result is already retrieved
    }


// util function to retry and await until the block is true or the timeout is reached
fun awaitBlock(timeOut: Int = 7500, block: () -> Boolean) {
    val start = System.currentTimeMillis()
    var value = block.invoke()
    while (!value && System.currentTimeMillis() < start + timeOut) {
        wait(50)
        value = block.invoke()
    }
    Assert.assertTrue("Couldn't await the condition", value)
}

Alternative Workaround

Based on the code, the latch timeout is retrieved from the arguments from the registry:

https://github.com/android/android-test/blob/master/runner/monitor/java/androidx/test/internal/platform/app/ActivityLifecycleTimeout.java#L16 https://github.com/android/android-test/blob/master/runner/monitor/java/androidx/test/internal/platform/util/InstrumentationParameterUtil.java#L22

So in theory lowering the timeout (which should be absolutely fine, no clue in which case it would be 45 seconds?!) should lower the lost time:

InstrumentationRegistry.getArguments().putString("activityLifecycleChangeTimeoutMillis", "5000")

But when debugged it would not have this value, so I am a bit puzzled.

mikepenz avatar Jul 14 '20 11:07 mikepenz

Something like this may work: https://github.com/mikepenz/android-test/tree/fix/676 ?

mikepenz avatar Jul 14 '20 13:07 mikepenz

Issue is present at final 1.3.0 release still and workaround works. Kudos to Mike for documenting and issue and providing a fix👍

plastiv avatar Sep 08 '20 15:09 plastiv

I've spent hours with this bug thinking I was using the library wrong. Doing specifically what @jameswald mentioned: an activity under test that calls finish() and then calling close() on the ActivityScenario blocking for 45 seconds only to succeed the test.

I found another, uglier but simpler workaround: just giving time to the main thread to update the state of the activity. So putting a Thread.sleep() before calling scenario.close() is also fixing it for me. Sleeps are ugly, unreliable, etc etc. But at least one more workaround

theHilikus avatar Sep 17 '20 17:09 theHilikus

I'm seeing a very similar issue, but the workarounds mentioned here don't seem to be alleviating it for me. If I do what's mentioned here https://stackoverflow.com/questions/65112750/android-test-with-activityscenariorule-hanging-forever and remove singleInstance from my manifest I'm all good, but obviously changing the functionality of my app to make the UI tests run correctly isn't ideal

lostcart avatar Jan 21 '21 13:01 lostcart

I've spent hours with this bug thinking I was using the library wrong. Doing specifically what @jameswald mentioned: an activity under test that calls finish() and then calling close() on the ActivityScenario blocking for 45 seconds only to succeed the test.

I found another, uglier but simpler workaround: just giving time to the main thread to update the state of the activity. So putting a Thread.sleep() before calling scenario.close() is also fixing it for me. Sleeps are ugly, unreliable, etc etc. But at least one more workaround

This worked for me. To save someone else future trouble, here's how this workaround might look in a sample test:

    @Test
    public void when_cancelButtonClicked_then_activityIsFinished() throws InterruptedException {
        onView(withId(R.id.cancelButton))
                .perform(click());

        final ActivityScenario scenario = activityRule.getScenario();
        assertEquals(Activity.RESULT_OK, scenario.getResult().getResultCode());

        Thread.sleep(1000);
        scenario.close();
    }
}

LifeAlgorithm avatar Mar 19 '21 08:03 LifeAlgorithm

I'm not quite sure if this is related, but I have the same issue where the test will keep running 45s after the Activity is closed. I was able to link the behaviour to launchMode="singleInstance". Changing the launch mode to singleTask resolves the issue and the test finishes instantly when the Activity is closed and the test code is all run.

crysxd avatar Aug 10 '21 14:08 crysxd

I'm not quite sure if this is related, but I have the same issue where the test will keep running 45s after the Activity is closed. I was able to link the behaviour to launchMode="singleInstance". Changing the launch mode to singleTask resolves the issue and the test finishes instantly when the Activity is closed and the test code is all run.

I resolved same issue this way

yoonseopshin avatar Aug 11 '21 07:08 yoonseopshin

I believe I have a similar issue my tests are very simple but after one runs I receive an empty activity like this post: https://stackoverflow.com/questions/65112750/android-test-with-activityscenariorule-hanging-forever I know my both my test cases are finishing as I debugged them to the last line. But once this blank activity is reached it never moves on the next test.

Link to Test class, should be using all the latest dependencies:

https://github.com/maxkernchen/WalkingAlarm/blob/master/app/src/androidTest/java/com/example/walkingalarm/WalkingAlarmUITests.java

maxkernchen avatar Sep 24 '21 14:09 maxkernchen

In the developer docs it is stated that you should not directly call close

https://developer.android.com/reference/androidx/test/core/app/ActivityScenario#close()

The recommendation is to try with resources for Java, or "use" with Kotlin

Instead try the following:

Java

 @Test
    public void when_cancelButtonClicked_then_activityIsFinished() throws InterruptedException 
        try(activityRule.getScenario()){
                onView(withId(R.id.cancelButton)).perform(click());
                assertEquals(Activity.RESULT_OK, scenario.getResult().getResultCode());
          }
    }

Kotlin

@Test
fun when_cancelButtonClicked_then_activityIsFinished() {
   activityRule.scenario.use{
                 onView(withId(R.id.cancelButton)).perform(click());
                assertEquals(Activity.RESULT_OK, scenario.getResult().getResultCode());
   }
}

More examples here: https://developer.android.com/guide/components/activities/testing#determine-current-state

I was running into this issue and using separate scenarios for each test in my class, removed the race condition issue and deadlock problems I was having by manually closing before or after the activity had been destroyed.

kmo9709 avatar Apr 01 '22 06:04 kmo9709

Hey guys, I spent several hours on this same problem and then I stumble upon this thread. Pls give this version a try androidx.test:core-ktx:1.5.0-alpha01" (https://developer.android.com/jetpack/androidx/releases/test#core_150_2). I'm using launchActivityForResult(startIntent) and it is working reliably.

paolopaa avatar Jul 21 '22 02:07 paolopaa

We tried a bunch of suggestions the only consistant working one was @mikepenz first workaround, not ideal, but way better than 45 waits. Seems like they need to put the getReport on a different thread.

chrisjenx avatar Aug 12 '22 16:08 chrisjenx

Could reproduce this reliably on androidx.test:core:1.4.0. androidx.test:core:1.5.0-alpha02 seems to solve it.

ViliusSutkus89 avatar Sep 21 '22 08:09 ViliusSutkus89

At this time, we are still able to see this problem occurring with the most recent 1.5.0 release of the androidx.test release.

mikepenz avatar Jun 01 '23 13:06 mikepenz

I'm also experiencing this issue. It would be great if this could get fixed soon. Testing on Android is already difficult enough without issues like this. Also, can confirm @mikepenz solution workaround is what I'm using now too.

donnfelker avatar Sep 21 '23 18:09 donnfelker