3

I have modified ActivityTestRule to retry executing failed test cases. I need to restart the activity and re-run the execution whenever there is a test failure. I have tried calling mActivity.finish() but current activity is not finishing and not restarting. But the tests are restarted. I have followed this method from here

public class MyActivityTestRule<T extends Activity> extends ActivityTestRule<T> {

private final Class<T> mActivityClass;
private T mActivity;
private static final String TAG = "ActivityInstrumentationRule";
private boolean mInitialTouchMode = false;
private Instrumentation mInstrumentation;


public MyActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
    super(activityClass, initialTouchMode, launchActivity);
    mActivityClass = activityClass;
    mInitialTouchMode = initialTouchMode;
    mInstrumentation = InstrumentationRegistry.getInstrumentation();
}

@Override
public Statement apply(Statement base, Description description) {
    final String testClassName = description.getClassName();
    final String testMethodName = description.getMethodName();
    final Context context =  InstrumentationRegistry.getTargetContext();
   /* android.support.test.espresso.Espresso.setFailureHandler(new FailureHandler() {
        @Override public void handle(Throwable throwable, Matcher<View> matcher) {
            if(AutomationTestCase.mEnableScreenshotOnFailure)
            SpoonScreenshotOnFailure.perform("espresso_assertion_failed", testClassName, testMethodName);
            new DefaultFailureHandler(context).handle(throwable, matcher);
        }
    });*/
     // return super.apply(base, description);
     return statement(base, description);
}

private Statement statement(final Statement base, final Description description) {
    return new Statement() {
        @Override
        public void evaluate() throws Throwable {
            Throwable caughtThrowable = null;
            //retry logic
            for (int i = 0; i < 3; i++) {
                try {
                    launchAppActivity(getActivityIntent());
                    base.evaluate();
                    return;
                } catch (Throwable t) {
                    caughtThrowable = t;
                    System.err.println(description.getDisplayName() + ": run " + (i+1) + " failed");
                    finishActivity();
                } finally {
                    finishActivity();
            }
            }
            System.err.println(description.getDisplayName() + ": giving up after " + 3 + " failures");
            throw caughtThrowable;
        }
    };
}

void finishActivity() {
    if (mActivity != null) {
        mActivity.finish();
        mActivity = null;
    }
}

public T launchAppActivity(@Nullable Intent startIntent) {
    // set initial touch mode
    mInstrumentation.setInTouchMode(mInitialTouchMode);

    final String targetPackage = mInstrumentation.getTargetContext().getPackageName();
    // inject custom intent, if provided
    if (null == startIntent) {
        startIntent = getActivityIntent();
        if (null == startIntent) {
            Log.w(TAG, "getActivityIntent() returned null using default: " +
                    "Intent(Intent.ACTION_MAIN)");
            startIntent = new Intent(Intent.ACTION_MAIN);
        }
    }
    startIntent.setClassName(targetPackage, mActivityClass.getName());
    startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    Log.d(TAG, String.format("Launching activity %s",
            mActivityClass.getName()));

    beforeActivityLaunched();
    // The following cast is correct because the activity we're creating is of the same type as
    // the one passed in
    mActivity = mActivityClass.cast(mInstrumentation.startActivitySync(startIntent));

    mInstrumentation.waitForIdleSync();

    afterActivityLaunched();
    return mActivity;
}}
Community
  • 1
  • 1
Basim Sherif
  • 5,384
  • 7
  • 48
  • 90

2 Answers2

0

If your test ended up in a different activity than what the ActivityTestRule was initialized as, you'll have to close all the activities in the backstack. This example may help:

https://gist.github.com/patrickhammond/19e584b90d7aae20f8f4

0xMatthewGroves
  • 3,181
  • 3
  • 26
  • 43
0

If you're using activityScenarioRule you can simply wrap the retry rule around it and it will restart the activity for each retry.

that's how I did it:

@get:Rule
var rule: RuleChain =
    RuleChain.outerRule(RetryRule()) //
        .around(hiltAndroidRule) //
        .around(activityScenarioRule<MainActivity>())

The RetryRule class looks like this

class RetryRule(private val retryCount: Int = 3) : TestRule {
    override fun apply(base: Statement, description: Description): Statement = object : Statement() {
        private var caughtThrowable: Throwable? = null

        override fun evaluate() {
            for (i in 1..retryCount) {
                try {
                    base.evaluate()
                    return
                } catch (t: Throwable) {
                    caughtThrowable = t
                    Log.e("RetryRule", "${description.displayName}: test failed, retrying $i of $retryCount")
                }
            }
            throw caughtThrowable!!
        }
    }
}
Sepultura
  • 997
  • 1
  • 9
  • 28