How to take a screenshot at the point where the test fails in Espresso?

I’m looking for a way to take a screenshot of the device after the test failed and before closing it.

+7
source share
6 answers

The easiest way I found:

@Rule public TestRule watcher = new TestWatcher() { @Override protected void failed(Throwable e, Description description) { // Save to external storage (usually /sdcard/screenshots) File path = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/screenshots/" + getTargetContext().getPackageName()); if (!path.exists()) { path.mkdirs(); } // Take advantage of UiAutomator screenshot method UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); String filename = description.getClassName() + "-" + description.getMethodName() + ".png"; device.takeScreenshot(new File(path, filename)); } }; 
+13
source

Another improvement to previous answers. I am using an experimental API screenshot

 public class ScreenshotTestRule extends TestWatcher { @Override protected void failed(Throwable e, Description description) { super.failed(e, description); takeScreenshot(description); } private void takeScreenshot(Description description) { String filename = description.getTestClass().getSimpleName() + "-" + description.getMethodName(); ScreenCapture capture = Screenshot.capture(); capture.setName(filename); capture.setFormat(CompressFormat.PNG); HashSet<ScreenCaptureProcessor> processors = new HashSet<>(); processors.add(new CustomScreenCaptureProcessor()); try { capture.process(processors); } catch (IOException e) { e.printStackTrace(); } } } 

I created CustomScreenCaptureProcessor because BasicScreenCaptureProcessor uses the / sdcard / Pictures / folder and I encountered an IOException on some devices when creating the folder / image. Please note that you need to put your processor in the same package

 package android.support.test.runner.screenshot; public class CustomScreenCaptureProcessor extends BasicScreenCaptureProcessor { public CustomScreenCaptureProcessor() { super( new File( InstrumentationRegistry.getTargetContext().getExternalFilesDir(DIRECTORY_PICTURES), "espresso_screenshots" ) ); } } 

Then in your base test class, espresso just add

 @Rule public ScreenshotTestRule screenshotTestRule = new ScreenshotTestRule(); 

If you want to use some kind of protected folder, this helped the emulator, but it didn’t work on the physical device.

 @Rule public RuleChain screenshotRule = RuleChain .outerRule(GrantPermissionRule.grant(permission.WRITE_EXTERNAL_STORAGE)) .around(new ScreenshotTestRule()); 
+6
source

I have not used screenshots in testing Android yet, but I know some solutions that may be useful:

The spoon

the best way to do this is to use the Emma or Spoon framework.

Here you will find useful information on how to do this: http://elekslabs.com/2014/05/creating-test-reports-for-android-with-spoon-and-emma.html

Visit the official Spoon website of the official Github website: https://github.com/square/spoon and its Gradle plugin: https://github.com/stanfy/spoon-gradle-plugin

and check the related topic: How to get Spoon for screenshots for Espresso tests?

screenshot-tests-for-android /

You can also try this Facebook library: https://facebook.imtqy.com/screenshot-tests-for-android/

Robotium ScreenshotTaker

As I already knew, with Robotium test environment you can create a test with screenshots. Check: The right way to take a screenshot with Robotium and Cucumber

If you do not want to use any library, check the source code of the Robotium framework class called ScreenshotTaker.java [click the link to see] and write your own ScreenshotTaker class.

Hope this helps.

+5
source

Writing your own TestWatcher, as in the other answers explained, is the way to go.

BUT (and it took us a long time to notice this) there is a warning: the rule may work too late, that is, after your activity has already been destroyed. This leaves you with a screenshot of the device’s home screen, and not from a malfunction.

You can solve this using RuleChain : instead of writing

 @Rule public final ActivityTestRule<MainActivity> _activityRule = new ActivityTestRule<>(MainActivity.class); @Rule public ScreenshotTestWatcher _screenshotWatcher = new ScreenshotTestWatcher(); 

You must write:

 private final ActivityTestRule<MainActivity> _activityRule = new ActivityTestRule<>(MainActivity.class); @Rule public final TestRule activityAndScreenshotRule = RuleChain .outerRule(_activityRule) .around(new ScreenshotTestWatcher()); 

This ensures that a screenshot is taken first and then the action is destroyed.

+1
source

I made some improvements to this answer. There is no need to add an additional dependency for UiAutomator, and also works below api level 18.

 public class ScreenshotTestWatcher extends TestWatcher { private static Activity currentActivity; @Override protected void failed(Throwable e, Description description) { Bitmap bitmap; if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { bitmap = getInstrumentation().getUiAutomation().takeScreenshot(); } else { // only in-app view-elements are visible. bitmap = Screenshot.capture(getCurrentActivity()).getBitmap(); } // Save to external storage '/storage/emulated/0/Android/data/[package name app]/cache/screenshots/'. File folder = new File(getTargetContext().getExternalCacheDir().getAbsolutePath() + "/screenshots/"); if (!folder.exists()) { folder.mkdirs(); } storeBitmap(bitmap, folder.getPath() + "/" + getFileName(description)); } private String getFileName(Description description) { String className = description.getClassName(); String methodName = description.getMethodName(); String dateTime = Calendar.getInstance().getTime().toString(); return className + "-" + methodName + "-" + dateTime + ".png"; } private void storeBitmap(Bitmap bitmap, String path) { BufferedOutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(path)); bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static Activity getCurrentActivity() { getInstrumentation().runOnMainSync(new Runnable() { public void run() { Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage( RESUMED); if (resumedActivities.iterator().hasNext()) { currentActivity = (Activity) resumedActivities.iterator().next(); } } }); return currentActivity; } } 

Then include the following line in the test class:

 @Rule public TestRule watcher = new ScreenshotTestWatcher(); 
0
source

@Maragues answer ported to Kotlin:

Supporting classes:

 package utils import android.graphics.Bitmap import android.os.Environment.DIRECTORY_PICTURES import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.runner.screenshot.BasicScreenCaptureProcessor import androidx.test.runner.screenshot.ScreenCaptureProcessor import androidx.test.runner.screenshot.Screenshot import org.junit.rules.TestWatcher import org.junit.runner.Description import java.io.File import java.io.IOException class IDTScreenCaptureProcessor : BasicScreenCaptureProcessor() { init { mTag = "IDTScreenCaptureProcessor" mFileNameDelimiter = "-" mDefaultFilenamePrefix = "Giorgos" mDefaultScreenshotPath = getNewFilename() } private fun getNewFilename(): File? { val context = getInstrumentation().getTargetContext().getApplicationContext() return context.getExternalFilesDir(DIRECTORY_PICTURES) } } class ScreenshotTestRule : TestWatcher() { override fun finished(description: Description?) { super.finished(description) val className = description?.testClass?.simpleName ?: "NullClassname" val methodName = description?.methodName ?: "NullMethodName" val filename = "$className - $methodName" val capture = Screenshot.capture() capture.name = filename capture.format = Bitmap.CompressFormat.PNG val processors = HashSet<ScreenCaptureProcessor>() processors.add(IDTScreenCaptureProcessor()) try { capture.process(processors) } catch (ioException: IOException) { ioException.printStackTrace() } } } 

Using:

 import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.rule.ActivityTestRule import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import utils.ScreenshotTestRule @RunWith(AndroidJUnit4::class) @LargeTest class DialogActivityTest { @get:Rule val activityRule = ActivityTestRule(DialogActivity::class.java) @get:Rule val screenshotTestRule = ScreenshotTestRule() @Test fun dialogLaunch_withTitleAndBody_displaysDialog() { // setup val title = "title" val body = "body" // assert onView(withText(title)).check(matches(isCompletelyDisplayed())) onView(withText(body)).check(matches(isCompletelyDisplayed())) } } 

Libraries declared in the build.gradle application:

 androidTestImplementation "androidx.test.espresso:espresso-core:3.1.1" androidTestImplementation "androidx.test.espresso:espresso-intents:3.1.1" androidTestImplementation "androidx.test.ext:junit:1.1.0" androidTestImplementation "androidx.test:runner:1.1.1" androidTestImplementation "androidx.test:rules:1.1.1" 

This setting saves a screenshot every time the test completes in the folder: /sdcard/Android/data/your.package.name/files/Pictures Go there using the file /sdcard/Android/data/your.package.name/files/Pictures Android Studio device (on the right side panel)

If you want to save screenshots only for failed tests, override the failed TestWatcher method instead of finished

0
source

All Articles