Skip to content Skip to sidebar Skip to footer

How To Take Screenshot At The Point Where Test Fail In Espresso?

I am looking for a way to take a screenshot of device after test failed and before get closed.

Solution 1:

Easiest way that I found:

@RulepublicTestRulewatcher=newTestWatcher() {
  @Overrideprotectedvoidfailed(Throwable e, Description description) {
    // Save to external storage (usually /sdcard/screenshots)Filepath=newFile(Environment.getExternalStorageDirectory().getAbsolutePath()
        + "/screenshots/" + getTargetContext().getPackageName());
    if (!path.exists()) {
      path.mkdirs();
    }

    // Take advantage of UiAutomator screenshot methodUiDevicedevice= UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    Stringfilename= description.getClassName() + "-" + description.getMethodName() + ".png";
    device.takeScreenshot(newFile(path, filename));
  }
};

Solution 2:

Another improvement to previous answers. I'm using the experimental Screenshot API

publicclassScreenshotTestRuleextendsTestWatcher {

  @Overrideprotectedvoidfailed(Throwable e, Description description) {
    super.failed(e, description);

    takeScreenshot(description);
  }

  privatevoidtakeScreenshot(Description description) {
    Stringfilename= description.getTestClass().getSimpleName() + "-" + description.getMethodName();

    ScreenCapturecapture= Screenshot.capture();
    capture.setName(filename);
    capture.setFormat(CompressFormat.PNG);

    HashSet<ScreenCaptureProcessor> processors = newHashSet<>();
    processors.add(newCustomScreenCaptureProcessor());

    try {
      capture.process(processors);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

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

package android.support.test.runner.screenshot;

publicclassCustomScreenCaptureProcessorextendsBasicScreenCaptureProcessor {    
  publicCustomScreenCaptureProcessor() {
    super(
        newFile(
            InstrumentationRegistry.getTargetContext().getExternalFilesDir(DIRECTORY_PICTURES),
            "espresso_screenshots"
        )
    );
  }
}

Then, in your base Espresso test class just add

@RulepublicScreenshotTestRulescreenshotTestRule=newScreenshotTestRule();

If you wish to use some protected folder, this did the trick on an emulator, tho it didn't work on a physical device

@RulepublicRuleChainscreenshotRule= RuleChain
      .outerRule(GrantPermissionRule.grant(permission.WRITE_EXTERNAL_STORAGE))
      .around(newScreenshotTestRule());

Solution 3:

Writing a custom TestWatcher like the other answers explained is the way to go.

BUT (and it took us a long time to notice it) there is a caveat: The rule might fire too late, i.e. after your activity was already destroyed. This leaves you with a screenshot of the device's home screen and not from the failing activity.

You can solve this using RuleChain: Instead of writing

@Rulepublicfinal ActivityTestRule<MainActivity> _activityRule = newActivityTestRule<>(MainActivity.class);

@RulepublicScreenshotTestWatcher_screenshotWatcher=newScreenshotTestWatcher();

You have to write:

privatefinal ActivityTestRule<MainActivity> _activityRule = newActivityTestRule<>(MainActivity.class);

@RulepublicfinalTestRuleactivityAndScreenshotRule= RuleChain
        .outerRule(_activityRule)
        .around(newScreenshotTestWatcher());

This makes sure that the screenshot is taken first and then the activity is destroyed

Solution 4:

@Maragues answer ported to Kotlin:

Helper 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

classIDTScreenCaptureProcessor : BasicScreenCaptureProcessor() {
    init {
        mTag = "IDTScreenCaptureProcessor"
        mFileNameDelimiter = "-"
        mDefaultFilenamePrefix = "Giorgos"
        mDefaultScreenshotPath = getNewFilename()
    }

    privatefungetNewFilename(): File? {
        val context = getInstrumentation().getTargetContext().getApplicationContext()
        return context.getExternalFilesDir(DIRECTORY_PICTURES)
    }
}

classScreenshotTestRule : TestWatcher() {
    overridefunfinished(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()
        }
    }
}

Usage:

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)@LargeTestclassDialogActivityTest{

    @get:Ruleval activityRule = ActivityTestRule(DialogActivity::class.java)

    @get:Ruleval screenshotTestRule = ScreenshotTestRule()

    @TestfundialogLaunch_withTitleAndBody_displaysDialog() {
        // setupval title = "title"val body = "body"// assert
        onView(withText(title)).check(matches(isCompletelyDisplayed()))
        onView(withText(body)).check(matches(isCompletelyDisplayed()))
    }


}

Libraries declared in app's build.gradle:

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 setup saves a screenshot every time a test finished in the folder: /sdcard/Android/data/your.package.name/files/Pictures Navigate there via Android Studio's Device File Explorer (on the right sidebar)

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

Solution 5:

I made some improvements on this answer. No need to add an extra dependency for UiAutomator and it also works below api level 18.

publicclassScreenshotTestWatcherextendsTestWatcher
{
   privatestaticActivity currentActivity;

   @Overrideprotectedvoidfailed(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 = newFile(getTargetContext().getExternalCacheDir().getAbsolutePath() + "/screenshots/");
      if (!folder.exists())
      {
         folder.mkdirs();
      }

      storeBitmap(bitmap, folder.getPath() + "/" + getFileName(description));
   }

   privateStringgetFileName(Description description)
   {
      String className = description.getClassName();
      String methodName = description.getMethodName();
      String dateTime = Calendar.getInstance().getTime().toString();

      return className + "-" + methodName + "-" + dateTime + ".png";
   }

   privatevoidstoreBitmap(Bitmap bitmap, String path)
   {
      BufferedOutputStream out = null;
      try
      {
         out = newBufferedOutputStream(newFileOutputStream(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();
            }
         }
      }
   }

   privatestaticActivitygetCurrentActivity()
   {
      getInstrumentation().runOnMainSync(newRunnable()
         {
            publicvoidrun()
            {
               Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
                     RESUMED);
               if (resumedActivities.iterator().hasNext())
               {
                  currentActivity = (Activity) resumedActivities.iterator().next();
               }
            }
         });

      return currentActivity;
   }
}

Then include the following line in your test class:

@RulepublicTestRulewatcher=newScreenshotTestWatcher();

Post a Comment for "How To Take Screenshot At The Point Where Test Fail In Espresso?"