diff --git a/build.gradle b/build.gradle index 41d04e17..6ad319be 100644 --- a/build.gradle +++ b/build.gradle @@ -40,12 +40,14 @@ buildscript { 'espressoContrib': 'com.android.support.test.espresso:espresso-contrib:2.2.2', 'runner': 'com.android.support.test:runner:0.5', 'rules': 'com.android.support.test:rules:0.5', + 'uiautomator': 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1', + ], ], ] dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta7' + classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.4' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" diff --git a/spoon-client/src/main/java/com/squareup/spoon/SpoonRule.java b/spoon-client/src/main/java/com/squareup/spoon/SpoonRule.java index ecefa661..4292420f 100644 --- a/spoon-client/src/main/java/com/squareup/spoon/SpoonRule.java +++ b/spoon-client/src/main/java/com/squareup/spoon/SpoonRule.java @@ -1,10 +1,26 @@ package com.squareup.spoon; +import static android.content.Context.MODE_WORLD_READABLE; +import static android.graphics.Bitmap.CompressFormat.PNG; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Environment.getExternalStorageDirectory; + +import static com.squareup.spoon.Chmod.chmodPlusR; +import static com.squareup.spoon.Chmod.chmodPlusRWX; +import static com.squareup.spoon.internal.Constants.NAME_SEPARATOR; +import static com.squareup.spoon.internal.Constants.SPOON_FILES; +import static com.squareup.spoon.internal.Constants.SPOON_SCREENSHOTS; + import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.util.Log; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; @@ -14,20 +30,6 @@ import java.io.IOException; import java.io.OutputStream; import java.util.regex.Pattern; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import static android.content.Context.MODE_WORLD_READABLE; -import static android.graphics.Bitmap.CompressFormat.PNG; -import static android.os.Build.VERSION.SDK_INT; -import static android.os.Build.VERSION_CODES.LOLLIPOP; -import static android.os.Environment.getExternalStorageDirectory; -import static com.squareup.spoon.Chmod.chmodPlusR; -import static com.squareup.spoon.Chmod.chmodPlusRWX; -import static com.squareup.spoon.internal.Constants.NAME_SEPARATOR; -import static com.squareup.spoon.internal.Constants.SPOON_FILES; -import static com.squareup.spoon.internal.Constants.SPOON_SCREENSHOTS; /** * A test rule which captures screenshots and associates them with the test class and test method @@ -72,6 +74,33 @@ public File screenshot(Activity activity, String tag) { return screenshotFile; } + public File screenshot(Context context,String tag,File file) { + if (!TAG_VALIDATION.matcher(tag).matches()) { + throw new IllegalArgumentException("Tag must match " + TAG_VALIDATION.pattern() + "."); + } + + if (file == null || !file.exists() || !file.isFile()) { + throw new IllegalArgumentException("Can't find any file at: " + file); + } + + if(!file.getName().endsWith(EXTENSION)) { + throw new IllegalArgumentException("Not a png file: " + file); + } + + File screenshotDirectory = null; + + try { + screenshotDirectory = obtainDirectory(context, className, methodName, SPOON_SCREENSHOTS); + String screenshotName = System.currentTimeMillis() + NAME_SEPARATOR + tag + EXTENSION; + File screenshotFile = new File(screenshotDirectory, screenshotName); + copyFile(file, screenshotFile); + Log.d(TAG, "copied screenshot file " + file); + return screenshotFile; + } catch (IOException e) { + throw new RuntimeException("Couldn't copy file " + file + " to " + screenshotDirectory, e); + } + } + private static void writeBitmapToFile(Bitmap bitmap, File file) { OutputStream fos = null; try { diff --git a/spoon-client/src/test/java/com/squareup/spoon/SpoonRuleTest.java b/spoon-client/src/test/java/com/squareup/spoon/SpoonRuleTest.java index 5d03af06..56f9a2bc 100644 --- a/spoon-client/src/test/java/com/squareup/spoon/SpoonRuleTest.java +++ b/spoon-client/src/test/java/com/squareup/spoon/SpoonRuleTest.java @@ -1,14 +1,90 @@ // Copyright 2012 Square, Inc. package com.squareup.spoon; +import org.junit.After; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; +import java.io.File; +import java.io.IOException; + public final class SpoonRuleTest { @Rule public final SpoonRule spoon = new SpoonRule(); + private static final String className = SpoonRuleTest.class.getName(); + private static final String dirPath = className+"_random/SpoonRuleTest/"; + private static final String pngName = dirPath+className+"_some.png"; + private static final String xmlName = dirPath+className+"_some.xml"; + @After + public void tearDown() { + safeDelete(new File(xmlName)); + safeDelete(new File(dirPath)); + safeDelete(new File(pngName)); + } + + private void safeDelete(File file) { + if(file.exists()) { + file.delete(); + } + } @Test(expected = IllegalArgumentException.class) public void invalidTagThrowsException() { spoon.screenshot(null, "!@#$%^&*()"); } + + @Test(expected = IllegalArgumentException.class) + public void screenshotWithFileThrowsExceptionWhenInvalidTag() { + spoon.screenshot(null, "!@#$%^&*()",null); + } + + @Test(expected = IllegalArgumentException.class) + public void screenshotWithFileThrowsExceptionWhenNullFile() { + spoon.screenshot(null, "SOME_TAG",null); + } + + @Test(expected = IllegalArgumentException.class) + public void screenshotWithFileThrowsExceptionWhenFileDoesNotExist() { + spoon.screenshot(null, "SOME_TAG",new File(pngName)); + } + + @Test(expected = IllegalArgumentException.class) + public void screenshotWithFileThrowsExceptionWhenFileExtentionNotPNG() throws IOException { + + File file = new File(xmlName); + file.getParentFile().mkdirs(); + file.createNewFile(); + + Assert.assertTrue(file.exists()); + Assert.assertTrue(file.isFile()); + spoon.screenshot(null, "SOME_TAG",file); + + } + + @Test(expected = IllegalArgumentException.class) + public void screenshotWithFileThrowsExceptionWhenFileIsDir() throws IOException { + + File file = new File(dirPath); + file.mkdirs(); + + Assert.assertTrue(file.exists()); + Assert.assertTrue(file.isDirectory()); + spoon.screenshot(null, "SOME_TAG",file); + + } + + @Test(expected = NullPointerException.class) + public void screenshotWithFileThrowsExceptionWhenContextIsNull() throws IOException { + + File file = new File(pngName); + file.getParentFile().mkdirs(); + file.createNewFile(); + + Assert.assertTrue(file.exists()); + Assert.assertTrue(file.isFile()); + spoon.screenshot(null, "SOME_TAG",file); + + } + + } diff --git a/test-app/build.gradle b/test-app/build.gradle index fd14eca7..80c46b0e 100644 --- a/test-app/build.gradle +++ b/test-app/build.gradle @@ -15,7 +15,7 @@ android { } defaultConfig { - minSdkVersion versions.minSdk + minSdkVersion 18 targetSdkVersion versions.compileSdk applicationId 'com.example.boxup.bucks' @@ -46,6 +46,7 @@ dependencies { androidTestImplementation deps.support.test.rules androidTestImplementation deps.support.test.espresso androidTestImplementation deps.support.test.espressoContrib + androidTestImplementation deps.support.test.uiautomator androidTestImplementation project(':spoon-client') } diff --git a/test-app/src/androidTest/java/com/example/boxup/bucks/BucksSendingTest.java b/test-app/src/androidTest/java/com/example/boxup/bucks/BucksSendingTest.java index 85faca2a..ff52c6d6 100644 --- a/test-app/src/androidTest/java/com/example/boxup/bucks/BucksSendingTest.java +++ b/test-app/src/androidTest/java/com/example/boxup/bucks/BucksSendingTest.java @@ -1,13 +1,22 @@ package com.example.boxup.bucks; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +import android.content.Context; +import android.os.Build; +import android.os.Environment; +import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; +import android.support.test.uiautomator.UiDevice; + import com.squareup.spoon.SpoonRule; + import org.junit.Rule; import org.junit.Test; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.action.ViewActions.click; -import static android.support.test.espresso.matcher.ViewMatchers.withText; +import java.io.File; public final class BucksSendingTest { @Rule public final SpoonRule spoon = new SpoonRule(); @@ -23,6 +32,36 @@ public final class BucksSendingTest { onView(withText("Send")).perform(click()); spoon.screenshot(amountActivityRule.getActivity(), "send_clicked"); + File fullscreenshot = takeFullScreenshot(); + spoon.screenshot(InstrumentationRegistry.getTargetContext(), "full_device_screenshot",fullscreenshot); + fullscreenshot.delete(); + } + + private File takeFullScreenshot() { + Context context = InstrumentationRegistry.getTargetContext().getApplicationContext(); + File file = getFile(System.currentTimeMillis() + "_full_screenshot.png", + context.getPackageName() + "_automator", context); + if (UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).takeScreenshot(file)) { + return file; + } else { + return null; + } + } + + private File getFile(String filename,String dirname,Context context) { + File file = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Use external storage. + file = new File(new File(Environment.getExternalStorageDirectory(), dirname), filename); + } else { + // Use internal storage. + file = new File(context.getDir(dirname, Context.MODE_WORLD_READABLE), filename); + } + + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + return file; } @Test