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

后端 未结 6 1270
花落未央
花落未央 2021-01-04 05:59

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

相关标签:
6条回答
  • 2021-01-04 06:28

    I made some improvements on this answer. No need to add an extra dependency for UiAutomator and it 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 your test class:

    @Rule
    public TestRule watcher = new ScreenshotTestWatcher();
    
    0 讨论(0)
  • 2021-01-04 06:39

    I haven't used screenshots in my Android testing yet, but I know some solutions, which might be useful:

    Spoon

    the best way to do it would be use Emma or Spoon framework.

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

    Visit also Spoon's official Github site: https://github.com/square/spoon and its Gradle's plugin: https://github.com/stanfy/spoon-gradle-plugin

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

    screenshot-tests-for-android/

    You can also try this Facebook's library: https://facebook.github.io/screenshot-tests-for-android/

    Robotium's ScreenshotTaker

    As I've already know, with Robotium test framework is possible to create test with screenshots. Check: Correct way to take screenshot with Robotium and Cucumber

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

    Hope it will help.

    0 讨论(0)
  • 2021-01-04 06:41

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

    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'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;
    
    public class CustomScreenCaptureProcessor extends BasicScreenCaptureProcessor {    
      public CustomScreenCaptureProcessor() {
        super(
            new File(
                InstrumentationRegistry.getTargetContext().getExternalFilesDir(DIRECTORY_PICTURES),
                "espresso_screenshots"
            )
        );
      }
    }
    

    Then, in your base Espresso test class just add

    @Rule
    public ScreenshotTestRule screenshotTestRule = new ScreenshotTestRule();
    

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

    @Rule
    public RuleChain screenshotRule = RuleChain
          .outerRule(GrantPermissionRule.grant(permission.WRITE_EXTERNAL_STORAGE))
          .around(new ScreenshotTestRule());
    
    0 讨论(0)
  • 2021-01-04 06:41

    @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
    
    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()
            }
        }
    }
    

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

    0 讨论(0)
  • 2021-01-04 06:46

    Easiest way that 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));
      }
    };
    
    0 讨论(0)
  • 2021-01-04 06:48

    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

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

    You have to write:

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

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

    0 讨论(0)
提交回复
热议问题