Created
January 30, 2020 09:43
-
-
Save wellavelino/efdf0aa364f512ccdaa83e9d831ae46e to your computer and use it in GitHub Desktop.
Custom failure handle for espresso
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Environment.DIRECTORY_DOCUMENTS | |
import android.os.Environment.getExternalStoragePublicDirectory | |
import android.util.Log | |
import android.view.View | |
import androidx.test.espresso.AmbiguousViewMatcherException | |
import androidx.test.espresso.EspressoException | |
import androidx.test.espresso.FailureHandler | |
import androidx.test.espresso.InjectEventSecurityException | |
import androidx.test.espresso.PerformException | |
import androidx.test.espresso.core.internal.deps.guava.base.Throwables.throwIfUnchecked | |
import androidx.test.platform.app.InstrumentationRegistry | |
import androidx.test.uiautomator.UiDevice | |
import junit.framework.AssertionFailedError | |
import org.hamcrest.Matcher | |
import org.junit.runner.Description | |
import java.io.File | |
import java.io.IOException | |
class CustomFailureHandler(private val description: Description?) : FailureHandler { | |
private val device: UiDevice | |
by lazy { UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) } | |
override fun handle(error: Throwable, viewMatcher: Matcher<View>) { | |
if (error is EspressoException | |
|| error is AssertionFailedError | |
|| error is AssertionError) { | |
throwIfUnchecked(getUserFriendlyError(error, viewMatcher, description)) | |
throw RuntimeException(getUserFriendlyError(error, viewMatcher, description)) | |
} else { | |
throwIfUnchecked(error) | |
throw RuntimeException(error) | |
} | |
} | |
private fun getUserFriendlyError(cause: Throwable, viewMatcher: Matcher<View>, description: Description?): Throwable { | |
dumpViewHierarchyToXml(description) | |
val error = when (cause) { | |
is PerformException -> handlePerformException(cause, viewMatcher) | |
is AssertionError -> handleAssertionException(cause) | |
is AmbiguousViewMatcherException -> handleAmbiguousViewMatcherException(cause) | |
is InjectEventSecurityException -> handleInjectEventSecurityException(cause, viewMatcher) | |
else -> cause | |
} | |
error.stackTrace = Thread.currentThread().stackTrace | |
return error | |
} | |
private fun handlePerformException(cause: PerformException, viewMatcher: Matcher<View>): Throwable { | |
val sb = StringBuilder() | |
sb.append(viewMatcher.toString()) | |
// Re-throw the exception with the viewMatcher (used to locate the view) as the view | |
// description (makes the error more readable). The reason we do this here: not all creators | |
// of PerformException have access to the viewMatcher. | |
return PerformException.Builder() | |
.from(cause) | |
.withViewDescription(sb.toString()) | |
.withActionDescription(sb.toString()) | |
.build() | |
} | |
private fun handleAmbiguousViewMatcherException(cause: AmbiguousViewMatcherException): Throwable { | |
return AmbiguousViewMatcherException.Builder().from(cause) | |
.includeViewHierarchy(true).build() | |
} | |
private fun handleAssertionException(cause: Throwable): Throwable { | |
// reports Failure instead of Error. | |
// assertThat(...) throws an AssertionFailedError. | |
return AssertionFailedWithCauseError(cause.message, cause) | |
} | |
private fun handleInjectEventSecurityException(cause: InjectEventSecurityException, viewMatcher: Matcher<View>): Throwable { | |
val sb = StringBuilder() | |
sb.append("Verify your keyboard -" + | |
"It seems it was in front of your layout during an interaction, but it shouldn't") | |
sb.append(viewMatcher.toString()) | |
return InjectEventSecurityException(sb.toString(), cause) | |
} | |
private class AssertionFailedWithCauseError(message: String?, cause: Throwable) : AssertionFailedError(message) { | |
init { | |
initCause(cause) | |
} | |
} | |
private fun dumpViewHierarchyToXml(description: Description?) { | |
val dump = File( | |
getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS), | |
"${description?.methodName}.xml") | |
if (dump.exists()) { | |
dump.delete() | |
} | |
dump.createNewFile() | |
try { | |
Log.e(TAG, "Saving view hierarchy into XML") | |
device.dumpWindowHierarchy(dump) | |
Log.e(TAG, dump.absolutePath) | |
} catch (exception: IOException) { | |
Log.e(TAG, "Couldn't save view hierarchy dump", exception) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment