Android 如何在浓缩咖啡测试失败时截图?

Android 如何在浓缩咖啡测试失败时截图?,android,testing,automated-tests,android-espresso,Android,Testing,Automated Tests,Android Espresso,我正在寻找一种方法,在测试失败后和关闭前拍摄设备的屏幕截图。我还没有在Android测试中使用屏幕截图,但我知道一些可能有用的解决方案: 勺子 最好的方法是使用Emma或Spoon框架 在这里,您可以找到一些有用的信息,了解如何做到这一点: 还可以访问Spoon的官方Github网站:及其Gradle插件: 并查看相关主题: android的屏幕截图测试/ 您也可以尝试此Facebook的库: 机器人截屏机 正如我已经知道的,使用Robotiumtestframework可以创建带有屏幕截图的

我正在寻找一种方法,在测试失败后和关闭前拍摄设备的屏幕截图。

我还没有在Android测试中使用屏幕截图,但我知道一些可能有用的解决方案:

勺子 最好的方法是使用
Emma
Spoon
框架

在这里,您可以找到一些有用的信息,了解如何做到这一点:

还可以访问
Spoon
的官方Github网站:及其Gradle插件:

并查看相关主题:

android的屏幕截图测试/ 您也可以尝试此Facebook的库:

机器人截屏机 正如我已经知道的,使用
Robotium
testframework可以创建带有屏幕截图的测试。检查:

如果您不想使用任何库,请检查名为[单击链接查看]的
Robotium
框架类的源代码,并编写自己的
ScreenshotTaker


希望能有所帮助。

我找到的最简单的方法是:

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

我对答案做了一些改进。无需为UiAutomator添加额外的依赖项,它也可以在api级别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;
   }
}
然后在测试类中包括以下行:

@Rule
public TestRule watcher = new ScreenshotTestWatcher();

对先前答案的另一个改进。我用的是实验性的

然后,在你的基础浓缩咖啡测试课程中,只需添加

@Rule
public ScreenshotTestRule screenshotTestRule = new ScreenshotTestRule();
如果您希望使用某个受保护的文件夹,这在模拟器上起到了作用,尽管它在物理设备上不起作用

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

编写一个定制的TestWatcher,就像前面解释的其他答案一样,是一个不错的选择

但是(我们花了很长时间才注意到)有一个警告:规则可能启动得太晚,即在您的活动已被破坏之后。这将为您留下设备主屏幕的屏幕截图,而不是失败活动的屏幕截图

你可以用:代替书写来解决这个问题

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

@Rule
public ScreenshotTestWatcher _screenshotWatcher = new ScreenshotTestWatcher();
@规则
公共最终ActivityTestRule _activityRule=新ActivityTestRule(MainActivity.class);
@统治
公共ScreenshotTestWatcher_screenshotWatcher=新ScreenshotTestWatcher();
你必须写:

private final ActivityTestRule<MainActivity> _activityRule = new ActivityTestRule<>(MainActivity.class);

@Rule
public final TestRule activityAndScreenshotRule = RuleChain
        .outerRule(_activityRule)
        .around(new ScreenshotTestWatcher());
private final ActivityTestRule\u activityRule=new ActivityTestRule(MainActivity.class);
@统治
公共最终测试规则活动和屏幕快照规则=规则链
.outerRule(_activityRule)
.around(新的ScreenshotTestWatcher());
这将确保首先拍摄屏幕截图,然后销毁活动

将答案移植到Kotlin:

助手类:

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()
        }
    }
}
在应用程序的
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"
每次在以下文件夹中完成测试时,此设置都会保存一个屏幕截图:
/sdcard/Android/data/your.package.name/files/Pictures
通过Android Studio的设备文件资源管理器(位于右侧边栏)在那里导航


如果您希望仅保存失败测试的屏幕截图,请覆盖
TestWatcher
failed
方法,而不是
finished

您可以使用Spoon()我最终遇到了以下错误:未能将屏幕截图保存到文件java.io.FileNotFoundException:/storage/emulated/0/screenshots/com.myappname/asdffdsa.png(没有这样的文件或目录)通过授予写外部存储权限修复了上述问题。现在我的问题是,当测试失败时,上面的代码不会被调用。我在一次手动调用中尝试了这段代码,它成功了,但我无法让它在发生故障时自动工作。@ZeekAran没有使用
TestWatcher
尝试注册espresso故障处理程序,正如这里所述,我最后这样做:@After public void tearDown(场景){if(Scenario.isFailed()){
}PostTestCleanup.tearDown();}@如果不这样做,屏幕截图将存储在设备上。这确实有效,但我必须使用
CustomScreenCaptureProcessor
。否则我会得到以下信息:
java.io.IOException:目录/storage/emulated/0/Pictures/screenshots不存在,无法创建或无法写入。
。您能用详细信息更新我的答案吗?我也时不时地体验到这一点,但我还没有时间去研究它。嗨@Maragues,我不确定我是否理解你想要什么。我刚刚使用了您提供的
CustomScreenCaptureProcessor
。提示:截图保存在存储/模拟/0/Android/data/{package}/files/Pictures/espresso_截图中。我花了一段时间才找到他们!您可以使用Android Studio设备文件资源管理器(右键单击文件夹->另存为…)一次性获取所有文件。更多详细信息,请停止从internet@markw. 您好,这是2016年8月的答案,没有。。。我没有从网上复制粘贴的内容。我检查了工具并提出了答案;-)
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()
        }
    }
}
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()))
    }


}
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"