summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-12-28 14:51:06 +0100
committerAlbin <albin@mullvad.net>2023-01-10 15:32:38 +0100
commit332ebf63b4dd6abd54e57043e287865cf81fe713 (patch)
tree4612eeff18d56b552f75944ea8f45743f498a2a2 /android
parent42610bc223085e23181af2e679fce538e4b4b5c8 (diff)
downloadmullvadvpn-332ebf63b4dd6abd54e57043e287865cf81fe713.tar.xz
mullvadvpn-332ebf63b4dd6abd54e57043e287865cf81fe713.zip
Improve test failure screenshot support
This commit improves the test failure auto screenshot on newer devices. It also removes the auto-download of screenshots via gradle as it's rarely used.
Diffstat (limited to 'android')
-rw-r--r--android/app/src/debug/AndroidManifest.xml4
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt122
-rw-r--r--android/test/e2e/build.gradle.kts34
-rw-r--r--android/test/e2e/src/main/AndroidManifest.xml3
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt10
5 files changed, 120 insertions, 53 deletions
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 2a866ff601..e1a8f8be40 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -1,5 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt
index 73d515c501..f5bbe5fa6b 100644
--- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/rule/CaptureScreenshotOnFailedTestRule.kt
@@ -1,30 +1,114 @@
package net.mullvad.mullvadvpn.test.common.rule
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.graphics.Bitmap
+import android.os.Build
+import android.os.Environment
+import android.os.Environment.DIRECTORY_PICTURES
+import android.provider.MediaStore
import android.util.Log
-import androidx.test.runner.screenshot.BasicScreenCaptureProcessor
-import androidx.test.runner.screenshot.ScreenCaptureProcessor
-import androidx.test.runner.screenshot.Screenshot
-import java.time.LocalDateTime
-import java.time.format.DateTimeFormatter
+import androidx.annotation.RequiresApi
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.nio.file.Paths
+import java.time.OffsetDateTime
+import java.time.temporal.ChronoUnit
import org.junit.rules.TestWatcher
import org.junit.runner.Description
-class CaptureScreenshotOnFailedTestRule(private val logTag: String) : TestWatcher() {
- override fun failed(e: Throwable?, description: Description?) {
- Log.d(logTag, "Capturing screenshot of failed test: " + description?.methodName)
- val timestamp = DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now()).replace(":", "")
- val screenshotName = "$timestamp-${description?.methodName}"
- captureScreenshot(screenshotName)
+class CaptureScreenshotOnFailedTestRule(private val testTag: String) : TestWatcher() {
+
+ override fun failed(e: Throwable?, description: Description) {
+ Log.d(testTag, "Capturing screenshot of failed test: " + description.methodName)
+ val timestamp = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS)
+ val screenshotName = "$timestamp-${description.methodName}.jpeg"
+ captureScreenshot(testTag, screenshotName)
+ }
+
+ private fun captureScreenshot(baseDir: String, filename: String) {
+ val contentResolver = getInstrumentation().targetContext.applicationContext.contentResolver
+ val contentValues = createBaseScreenshotContentValues()
+
+ getInstrumentation().uiAutomation.takeScreenshot().apply {
+ if (Build.VERSION.SDK_INT >= 29) {
+ writeToMediaStore(
+ contentValues = contentValues,
+ contentResolver = contentResolver,
+ baseDir = baseDir,
+ filename = filename
+ )
+ } else {
+ writeToExternalStorage(
+ contentValues = contentValues,
+ contentResolver = contentResolver,
+ baseDir = baseDir,
+ filename = filename
+ )
+ }
+ }
+ }
+
+ @RequiresApi(29)
+ private fun Bitmap.writeToMediaStore(
+ contentValues: ContentValues,
+ contentResolver: ContentResolver,
+ baseDir: String,
+ filename: String
+ ) {
+ contentValues.apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
+ put(
+ MediaStore.Images.Media.RELATIVE_PATH,
+ "$DIRECTORY_PICTURES/$baseDir"
+ )
+ }
+
+ val uri =
+ contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
+
+ if (uri != null) {
+ contentResolver.openOutputStream(uri).use {
+ try {
+ this.compress(Bitmap.CompressFormat.JPEG, 50, it)
+ } catch (e: IOException) {
+ Log.e(testTag, "Unable to store screenshot: ${e.message}")
+ }
+ }
+ contentResolver.update(uri, contentValues, null, null)
+ } else {
+ Log.e(testTag, "Unable to store screenshot")
+ }
}
- private fun captureScreenshot(screenShotName: String) {
- try {
- val screenCapture = Screenshot.capture().apply { name = screenShotName }
- val processorSet: MutableSet<ScreenCaptureProcessor> = HashSet()
- processorSet.add(BasicScreenCaptureProcessor())
- screenCapture.process(processorSet)
- } catch (ex: Exception) {
- Log.d(logTag, "Error capturing screenshot: " + ex.message)
+ private fun Bitmap.writeToExternalStorage(
+ contentValues: ContentValues,
+ contentResolver: ContentResolver,
+ baseDir: String,
+ filename: String
+ ) {
+ val screenshotBaseDirectory = Paths.get(
+ Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES).path,
+ baseDir,
+ ).toFile().apply {
+ if (exists().not()) {
+ mkdirs()
+ }
}
+ FileOutputStream(File(screenshotBaseDirectory, filename)).use { outputStream ->
+ try {
+ this.compress(Bitmap.CompressFormat.JPEG, 50, outputStream)
+ } catch (e: IOException) {
+ Log.e(testTag, "Unable to store screenshot: ${e.message}")
+ }
+ }
+ contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
+ }
+
+ private fun createBaseScreenshotContentValues() = ContentValues().apply {
+ put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
+ put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
}
}
diff --git a/android/test/e2e/build.gradle.kts b/android/test/e2e/build.gradle.kts
index fdd9f55593..a7950ef753 100644
--- a/android/test/e2e/build.gradle.kts
+++ b/android/test/e2e/build.gradle.kts
@@ -43,6 +43,7 @@ android {
testInstrumentationRunnerArguments += mutableMapOf<String, String>().apply {
put("clearPackageData", "true")
+ put("useTestStorageService", "true")
addOptionalPropertyAsArgument("valid_test_account_token")
addOptionalPropertyAsArgument("invalid_test_account_token")
}
@@ -62,39 +63,6 @@ android {
}
}
-val localScreenshotPath = "$buildDir/reports/androidTests/connected/screenshots"
-val deviceScreenshotPath = "/sdcard/Pictures/Screenshots"
-
-tasks.register("createDeviceScreenshotDir", Exec::class) {
- executable = android.adbExecutable.toString()
- args = listOf("shell", "mkdir", "-p", deviceScreenshotPath)
-}
-
-tasks.register("createLocalScreenshotDir", Exec::class) {
- executable = "mkdir"
- args = listOf("-p", localScreenshotPath)
-}
-
-tasks.register("clearDeviceScreenshots", Exec::class) {
- executable = android.adbExecutable.toString()
- args = listOf("shell", "rm", "-r", deviceScreenshotPath)
-}
-
-tasks.register("fetchScreenshots", Exec::class) {
- executable = android.adbExecutable.toString()
- args = listOf("pull", "$deviceScreenshotPath/.", localScreenshotPath)
-
- dependsOn(tasks.getByName("createLocalScreenshotDir"))
- finalizedBy(tasks.getByName("clearDeviceScreenshots"))
-}
-
-tasks.whenTaskAdded {
- if (name == "connectedDebugAndroidTest") {
- dependsOn(tasks.getByName("createDeviceScreenshotDir"))
- finalizedBy(tasks.getByName("fetchScreenshots"))
- }
-}
-
configure<org.owasp.dependencycheck.gradle.extension.DependencyCheckExtension> {
// Skip the lintClassPath configuration, which relies on many dependencies that has been flagged
// to have CVEs, as it's related to the lint tooling rather than the project's compilation class
diff --git a/android/test/e2e/src/main/AndroidManifest.xml b/android/test/e2e/src/main/AndroidManifest.xml
index 931f79d291..9ba07f4905 100644
--- a/android/test/e2e/src/main/AndroidManifest.xml
+++ b/android/test/e2e/src/main/AndroidManifest.xml
@@ -1,5 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
<uses-permission android:name="android.permission.INTERNET" />
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt
index 35ba3fbe46..b65c43e23c 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt
@@ -1,7 +1,9 @@
package net.mullvad.mullvadvpn.test.e2e
+import android.Manifest
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.AndroidJUnit4
import androidx.test.uiautomator.UiDevice
import net.mullvad.mullvadvpn.test.common.interactor.AppInteractor
@@ -22,10 +24,16 @@ abstract class EndToEndTest {
@JvmField
val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG)
+ @Rule
+ @JvmField
+ val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ )
+
lateinit var device: UiDevice
lateinit var targetContext: Context
lateinit var app: AppInteractor
- lateinit var web: WebViewInteractor
lateinit var validTestAccountToken: String
lateinit var invalidTestAccountToken: String