Features

Capturing Espresso Screenshots

Espresso does not have a built-in way to capture screenshots during test automation. You can however achieve this by using two options:

  • Native Screenshot grabbing

    Use native Java or Kotlin code to take a screenshot of the device, and save it on the device.

    Once the test is completed, you can fetch the image through ADB, or if you run it via TestingBot, the screenshot will appear on the test result page.

  • Using the Spoon library

    The Square Screenshots feature is part of the Spoon Library.

    It will take screenshots during your Espresso tests, but only for Android devices with a version of Android 10 or lower.

  1. Add the native screenshots file to your project

    You can see the implementation of Native Screenshots in the TestingBot Espresso Demo App.

    Copy code
    package com.testingbot.calculator;
    
    import android.graphics.Bitmap;
    import android.os.Build;
    import android.os.Environment;
    
    import androidx.test.runner.screenshot.BasicScreenCaptureProcessor;
    import androidx.test.runner.screenshot.ScreenCapture;
    import androidx.test.runner.screenshot.Screenshot;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.regex.Pattern;
    
    public final class NativeScreenshot {
    
        private static String methodName;
        private static String className;
        private static final Pattern SCREENSHOT_NAME_VALIDATION = Pattern.compile("[a-zA-Z0-9_-]+");
    
        private NativeScreenshot() {}
    
        /**
         * Captures screenshot using Androidx Screenshot library and stores in the filesystem.
         * If there is any runtime exception while capturing screenshot, the method throws
         * Exception and the test might fail if exception is not handled properly.
         * @param screenshotName    a screenshot identifier
         * @return path to the screenshot file
         */
        public static String capture(String screenshotName) {
            StackTraceElement testClass = findTestClassTraceElement(Thread.currentThread().getStackTrace());
            className = testClass.getClassName().replaceAll("[^A-Za-z0-9._-]", "_");
            methodName = testClass.getMethodName();
            EspressoScreenCaptureProcessor screenCaptureProcessor = new EspressoScreenCaptureProcessor();
    
            if (!SCREENSHOT_NAME_VALIDATION.matcher(screenshotName).matches()) {
                throw new IllegalArgumentException("ScreenshotName must match " + SCREENSHOT_NAME_VALIDATION.pattern() + ".");
            } else {
                ScreenCapture capture = Screenshot.capture();
                capture.setFormat(Bitmap.CompressFormat.PNG);
                capture.setName(screenshotName);
    
                try {
                    return screenCaptureProcessor.process(capture);
                } catch (IOException e) {
                    throw new RuntimeException("Unable to capture screenshot.", e);
                }
            }
        }
    
        /**
         * Extracts the currently executing test's trace element based on the test runner
         * or any framework being used.
         * @param trace stacktrace of the currently running test
         * @return StackTrace Element corresponding to the current test being executed.
         */
        private static StackTraceElement findTestClassTraceElement(StackTraceElement[] trace) {
            for(int i = trace.length - 1; i >= 0; --i) {
                StackTraceElement element = trace[i];
                if ("android.test.InstrumentationTestCase".equals(element.getClassName()) && "runMethod".equals(element.getMethodName())) {
                    return extractStackElement(trace, i);
                }
    
                if ("org.junit.runners.model.FrameworkMethod$1".equals(element.getClassName()) && "runReflectiveCall".equals(element.getMethodName())) {
                    return extractStackElement(trace, i);
                }
    
                if ("cucumber.runtime.model.CucumberFeature".equals(element.getClassName()) && "run".equals(element.getMethodName())) {
                    return extractStackElement(trace, i);
                }
            }
    
            throw new IllegalArgumentException("Could not find test class!");
        }
    
        /**
         * Based on the test runner or framework being used, extracts the exact traceElement.
         * @param trace stacktrace of the currently running test
         * @param i a reference index
         * @return trace element based on the index passed
         */
        private static StackTraceElement extractStackElement(StackTraceElement[] trace, int i) {
            int testClassTraceIndex = Build.VERSION.SDK_INT >= 23 ? i - 2 : i - 3;
            return trace[testClassTraceIndex];
        }
    
        private static class EspressoScreenCaptureProcessor extends BasicScreenCaptureProcessor {
            private static final String SCREENSHOT = "screenshots";
    
            EspressoScreenCaptureProcessor() {
                File screenshotDir = new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)), SCREENSHOT);
                File classDir = new File(screenshotDir, className);
                mDefaultScreenshotPath = new File(classDir, methodName);
            }
    
            /**
             * Converts the filename to a standard path to be stored on device.
             * Example: "post_addition" converts to "1648038895211_post_addition"
             * which is later suffixed by the file extension i.e. png.
             * @param filename  a screenshot identifier
             * @return custom filename format
             */
            @Override
            protected String getFilename(String filename) {
                return System.currentTimeMillis() + "_" + filename;
            }
        }
    }
  2. Modify your Espresso test scripts to take screenshots

    You can call the NativeScreenshot.capture("tag") method to capture screenshots in your Espresso tests.
    The tag can be used to provide a name to the screenshot (usually you would use the testname for this).

    Copy code
    @Test
    public void ensureCalculatorWorks() {
        onView(withId(R.id.buttonOne)).perform(click());
        onView(withId(R.id.buttonAdd)).perform(click());
        onView(withId(R.id.buttonTwo)).perform(click());
        onView(withId(R.id.editText)).check(matches(withText("3")));
    
        NativeScreenshot.capture(mainActivity, "ensureCalculatorWorks");
    }
  3. Android 11 and higher

    For Android 11 and higher, make sure to add the following RuleChain to your Espresso test:

    Copy code
    @Rule
    public RuleChain screenshotRule = RuleChain
      .outerRule(GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE))
      .around(activityScenarioRule);
  1. Add the required dependencies to your project

    See the Spoon Documentation on how to add Spoon to your project.
    You can either use a JAR or use Maven.

  2. Change storage permissions

    If you do not yet have the WRITE_EXTERNAL_STORAGE permission in your AndroidManifest.xml, please add it.
    It will allow Spoon to save the screenshots on your device.

    Copy code
    <manifest>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    ...
    </manifest>

    If the targetSdk for your app is using Android 10 (level 29), please make sure to add android:requestLegacyExternalStorage="true" in the Application tag of your AndroidManifest.xml file.

  3. Modify your Espresso test scripts to take screenshots

    You can call the Spoon.screenshot(activityName, tag) method to capture screenshots in your Espresso tests.
    The tag can be used to provide a name to the screenshot (usually you would use the testname for this).

    Copy code
    @Test
    public void ensureCalculatorWorks() {
        onView(withId(R.id.buttonOne)).perform(click());
        onView(withId(R.id.buttonAdd)).perform(click());
        onView(withId(R.id.buttonTwo)).perform(click());
        onView(withId(R.id.editText)).check(matches(withText("3")));
    
        Spoon.screenshot(mainActivity, "ensureCalculatorWorks");
    }

Viewing Espresso Screenshots

The Espresso tests that you run on TestingBot will appear automatically in the TestingBot dashboard.
If screenshots were generated during the test, they will be added on the Espresso test result page.

Additionally, these screenshots will also be available for download through our REST API.