java - 编写自定义 Android ViewAction 以截取屏幕截图

标签 java android testing android-espresso

我想在使用 espresso 在 Android 中执行操作之前截取屏幕截图。

protected T performAction(ViewAction viewAction) {
    ViewAction screenShotAction = new ScreenShotAction();
    viewInteraction.perform(screenShotAction);
    viewInteraction.perform(viewAction);
    return returnGeneric();
}

例如,如果在我的测试中我执行了 click(),那么我会在执行 click() 之前截取设备的屏幕截图。

这是在 ScreenShotAction 类中获取屏幕截图的代码

@Override
public void perform(UiController uiController, View view) {

    View rootView = view.getRootView();
    String state = Environment.getExternalStorageState();
    if(Environment.MEDIA_MOUNTED.equals(state)) {
        File picDir = new File(Environment.getExternalStorageDirectory() + "app_" + "test");

        if (!picDir.exists()) {
            picDir.mkdir();
        }

        rootView.setDrawingCacheEnabled(true);
        rootView.buildDrawingCache(true);
        Bitmap bitmap = rootView.getDrawingCache();
        String fileName = "test.jpg";
        File picFile = new File(picDir + "/" + fileName);

        try {
            picFile.createNewFile();
            FileOutputStream picOut = new FileOutputStream(picFile);
            bitmap = Bitmap.createBitmap(rootView.getWidth(), rootView.getHeight(), Bitmap.Config.ARGB_8888);
            boolean saved = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, picOut);

            if (saved) {
                // good
            } else {
                // error
                throw  new Exception("Image not saved");
            }

            picOut.flush();
            picOut.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        rootView.destroyDrawingCache();
    }
}

我在手机的图片目录或任何其他目录中没有看到任何图像文件。我相信屏幕截图方法是可靠的,但不确定我是否正确调用了该方法。

viewInteraction.perform(screenShotAction) 是调用我的自定义 View 操作的正确方式吗?

请提前帮助并谢谢你。

最佳答案

您可以执行以下操作:

public class CaptureImage {
@SuppressWarnings("unused")
private static final String TAG = CaptureImage.class.getSimpleName();
private static final String NAME_SEPARATOR = "_";
private static final String EXTENSION = ".png";
private static final Object LOCK = new Object();
private static boolean outputNeedsClear = true;
private static final Pattern NAME_VALIDATION = Pattern.compile("[a-zA-Z0-9_-]+");

public static void takeScreenshot(View currentView, String className,
        String methodName, @Nullable String prefix) {
    methodName = methodName.replaceAll("[\\[\\](){}]", "");
    if (!NAME_VALIDATION.matcher(methodName).matches()) {
        throw new IllegalArgumentException(
                "Name must match " + NAME_VALIDATION.pattern() +
                        " and " + methodName + " was received.");
    }
    Context context = InstrumentationRegistry.getTargetContext();
    MyRunnable myRunnable = new MyRunnable(context, currentView, className, methodName, prefix);
    Activity activity =
          ((Application)context.getApplicationContext()).getCurrentActivity();
    activity.runOnUiThread(myRunnable);
}

private static class MyRunnable implements Runnable {
    private View mView;
    private Context mContext;
    private String mClassName;
    private String mMethodName;
    private String mPrefix;

    MyRunnable(Context context, View view, String className, String methodName, String prefix) {
        mContext = context;
        mView = view;
        mClassName = className;
        mMethodName = methodName;
        mPrefix = prefix;
    }

    @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
    public void run() {
        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
        if (uiAutomation == null) {
            return;
        }
        OutputStream out = null;
        Bitmap bitmap = null;
        try {
            String timestamp = new SimpleDateFormat("MM_dd_HH_mm_ss", Locale.ENGLISH)
                    .format(new Date());
            File screenshotDirectory = getScreenshotFolder();
            int statusBarHeight = getStatusBarHeightOnDevice();
            bitmap = uiAutomation.takeScreenshot();
            Bitmap screenshot = Bitmap.createBitmap(bitmap, 0, statusBarHeight,
                    mView.getWidth(), mView.getHeight() - statusBarHeight);
            String screenshotName = mMethodName + NAME_SEPARATOR +
                    (mPrefix != null ? (mPrefix + NAME_SEPARATOR) : "") +
                    timestamp + EXTENSION;
            Log.d("YOUR_TAG", "Screenshot name: " + screenshotName);
            File imageFile = new File(screenshotDirectory, screenshotName);
            out = new FileOutputStream(imageFile);
            screenshot.compress(Bitmap.CompressFormat.PNG, 90, out);
            out.flush();
        } catch (Throwable t) {
            Log.e("YOUR_LOG", "Unable to capture screenshot.", t);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (Exception ignored) {
            }
            if (bitmap != null) {
                bitmap.recycle();
            }
        }
    }

    private int getStatusBarHeightOnDevice() {
        int _StatusBarHeight = 0;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        mView.setDrawingCacheEnabled(true);
        Bitmap screenShot = Bitmap.createBitmap(mView.getDrawingCache());
        mView.setDrawingCacheEnabled(false);
        if (screenShot != null) {
            int StatusColor = screenShot.getPixel(0, 0);
            for (int y = 1; y < (screenShot.getHeight() / 4); y++) {
                if (screenShot.getPixel(0, y) != StatusColor) {
                    _StatusBarHeight = y - 1;
                    break;
                }
            }
        }
        if (_StatusBarHeight == 0) {
            _StatusBarHeight = 50;  // Set a default in case we don't find a difference
        }

        Log.d("YOUR_TAG", "Status Bar was measure at " 
              + _StatusBarHeight + " pixels");
        return _StatusBarHeight;
    }

    private File getScreenshotFolder() throws IllegalAccessException {
        File screenshotsDir;
        if (Build.VERSION.SDK_INT >= 21) {
            // Use external storage.
            screenshotsDir = new File(getExternalStorageDirectory(), 
                                      "screenshots");
        } else {
            // Use internal storage.
            screenshotsDir = new File(mContext.getApplicationContext().getFilesDir(),
                    "screenshots");
        }

        synchronized (LOCK) {
            if (outputNeedsClear) {
                deletePath(screenshotsDir);
                outputNeedsClear = false;
            }
        }

        File dirClass = new File(screenshotsDir, mClassName);
        File dirMethod = new File(dirClass, mMethodName);
        createDir(dirMethod);
        return dirMethod;
    }

    private void createDir(File dir) throws IllegalAccessException {
        File parent = dir.getParentFile();
        if (!parent.exists()) {
            createDir(parent);
        }
        if (!dir.exists() && !dir.mkdirs()) {
            throw new IllegalAccessException(
                    "Unable to create output dir: " + dir.getAbsolutePath());
        }
    }

    private void deletePath(File path) {
        if (path.isDirectory() && path.exists()) {
            File[] children = path.listFiles();
            if (children != null) {
                for (File child : children) {
                    Log.d("YOUR_TAG", "Deleting " + child.getPath());
                    deletePath(child);
                }
            }
        }
        if (!path.delete()) {
            // log message here
        }
    }
}

然后您可以从 ViewAction 或直接从测试用例类调用它:

查看操作类:

class ScreenshotViewAction implements ViewAction {
private final String mClassName;
private final String mMethodName;
private final int mViewId;
private final String mPrefix;

protected ScreenshotViewAction(final int viewId, final String className,
        final String methodName, @Nullable final String prefix) {
    mViewId = viewId;
    mClassName = className;
    mMethodName = methodName;
    mPrefix = prefix;
}

@Override
public Matcher<View> getConstraints() {
    return ViewMatchers.isDisplayed();
}

@Override
public String getDescription() {
    return "Taking a screenshot.";
}

@Override
public void perform(final UiController aUiController, final View aView) {
    aUiController.loopMainThreadUntilIdle();
    final long startTime = System.currentTimeMillis();
    final long endTime = startTime + 2000;
    final Matcher<View> viewMatcher = ViewMatchers.withId(mViewId);
    do {
        for (View child : TreeIterables.breadthFirstViewTraversal(aView)) {
            // found view with required ID
            if (viewMatcher.matches(child)) {
                CaptureImage.takeScreenshot(aView.getRootView(), mClassName,
                        mMethodName, mPrefix);
                return;
            }
        }

        aUiController.loopMainThreadForAtLeast(50);
    }
    while (System.currentTimeMillis() < endTime);
}

现在从您的测试用例类中,创建以下静态方法:

public static void takeScreenshot(int prefix) {
    View currentView = ((ViewGroup)mActivity
            .getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
    String fullClassName = Thread.currentThread().getStackTrace()[3].getClassName();
    String testClassName = fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
    String testMethodName = Thread.currentThread().getStackTrace()[3].getMethodName();
    CaptureImage.takeScreenshot(currentView, testClassName, testMethodName,
            String.valueOf(prefix));
}

public static ViewAction takeScreenshot(@Nullable String prefix) {
    String fullClassName = Thread.currentThread().getStackTrace()[3].getClassName();
    String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
    String methodName = Thread.currentThread().getStackTrace()[3].getMethodName();
    return new ScreenshotViewAction(getDecorView().getId(), className, methodName, prefix);
}

或者您可以从执行 View 操作中调用它:

takeScreenshot(0);
onView(withContentDescription(sContext
       .getString(R.string.abc_action_bar_up_description)))
       .perform(
              ScreenshotViewAction.takeScreenshot(String.valueOf(1)),
              click()
       );

关于java - 编写自定义 Android ViewAction 以截取屏幕截图,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28888635/

相关文章:

java - JApplet 在浏览器中缺乏完整功能

java - 如何让单个线程而不是整个程序等待?

java - SpringBoot Webflux 无法返回 application/xml

java - 高效更新 RecyclerView 网格或以其他方式显示复杂网格

android - 无法在 Android 中导入 widget.CalendarView

java - 有没有类似Android的资源系统的更小规模的?

java - 在 JDBC 中编写 SQL 查询的最佳实践是什么

python - 从管理命令运行时,Django-nose 测试用例给出断言错误

ruby-on-rails - 强化遗留 Rails 应用程序的第一步是什么?

dart - 防止 Dart 测试每秒记录一次