[#200][#285] Use Espresso screenshot API

- Also reduced liklihood of timeouts on Firebase test lab for robo tests
 - Fix emulatorwtf run configuration
 - Fix screenshots on older API levels
 - Fix minumum range for emulator.wtf
This commit is contained in:
Carter Jernigan 2022-06-02 11:09:02 -04:00 committed by GitHub
parent adc774a20d
commit 1880b2a43f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 38 additions and 145 deletions

View File

@ -387,7 +387,7 @@ jobs:
with:
name: Release binaries
- name: Robo test
timeout-minutes: 15
timeout-minutes: 20
env:
# Path depends on `release_build` job, plus path of `Download a single artifact` step
BINARIES_ZIP_PATH: binaries.zip

View File

@ -1,17 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="testDebugWithEmulatorWtf" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="scriptParameters" value="testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="testDebugWithEmulatorWtf" />
</list>
<list />
</option>
<option name="vmOptions" />
</ExternalSystemSettings>

View File

@ -101,6 +101,7 @@ android {
listOf(
"**/*.kotlin_metadata",
".readme",
"build-data.properties",
"META-INF/*.kotlin_module",
"META-INF/android.arch**",
"META-INF/androidx**",
@ -110,7 +111,6 @@ android {
"META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor",
"META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar",
"META-INF/services/org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages\$Extension",
"build-data.properties",
"firebase-**.properties",
"kotlin/**",
"play-services-**.properties",
@ -206,27 +206,6 @@ if (googlePlayServiceKeyFilePath.isNotEmpty()) {
}
}
val reportsDirectory = "${buildDir}/reports/androidTests/connected"
// This is coordinated with `EccScreenCaptureProcessor`
val onDeviceScreenshotsDirectory = "/sdcard/Pictures/zcash_screenshots"
val clearScreenshotsTask = tasks.create<Exec>("clearScreenshots") {
executable = project.android.adbExecutable.absolutePath
args = listOf("shell", "rm", "-r", onDeviceScreenshotsDirectory)
}
val fetchScreenshotsTask = tasks.create<Exec>("fetchScreenshots") {
executable = project.android.adbExecutable.absolutePath
args = listOf("pull", onDeviceScreenshotsDirectory, reportsDirectory)
finalizedBy(clearScreenshotsTask)
}
tasks.whenTaskAdded {
if (name == "connectedZcashmainnetDebugAndroidTest") {
finalizedBy(fetchScreenshotsTask)
}
}
fladle {
// Firebase Test Lab has min and max values that might differ from our project's
@ -265,7 +244,7 @@ fladle {
}
)
testTimeout.set("5m")
testTimeout.set("3m")
devices.addAll(
mapOf("model" to "Pixel2", "version" to minSdkVersion),
@ -278,13 +257,19 @@ fladle {
}
emulatorwtf {
// This path needs to be coordinated with the implementation in the app module's tests
directoriesToPull.set(listOf("/sdcard/Pictures/zcash_screenshots"))
directoriesToPull.set(listOf("/sdcard/googletest/test_outputfiles"))
val appMinSdkVersion = run {
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val EMULATOR_WTF_MIN_SDK = 23
val buildMinSdk = project.properties["ANDROID_APP_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(EMULATOR_WTF_MIN_SDK).toString()
}
devices.set(
listOf(
// TODO [#285]: Our screenshot tests don't work on older devices
// mapOf("model" to "Pixel2", "version" to minSdkVersion),
mapOf("model" to "Pixel2", "version" to appMinSdkVersion),
// TODO [#430]: App won't run on API 31 Intel emulators
@Suppress("MagicNumber")
mapOf("model" to "Pixel2", "version" to 30)

View File

@ -1,10 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="co.electriccoin.zcash.test">
<!-- Required to write screenshots -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<manifest package="co.electriccoin.zcash.app.test">
</manifest>

View File

@ -1,8 +1,5 @@
package co.electriccoin.zcash.app
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
@ -18,16 +15,15 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ApplicationProvider
import androidx.test.core.graphics.writeToTestStorage
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.screenshot.captureToBitmap
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.screenshot.Screenshot
import cash.z.ecc.sdk.ext.ui.model.MonetarySeparators
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import co.electriccoin.zcash.app.test.EccScreenCaptureProcessor
import co.electriccoin.zcash.app.test.getStringResource
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
import co.electriccoin.zcash.test.UiTestPrerequisites
@ -39,44 +35,21 @@ import co.electriccoin.zcash.ui.screen.restore.RestoreTag
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
// TODO [#285]: Screenshot tests fail on older devices due to issue granting external storage permission
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
class ScreenshotTest : UiTestPrerequisites() {
companion object {
@BeforeClass
@JvmStatic
fun setupPPlus() {
if (Build.VERSION_CODES.P <= Build.VERSION.SDK_INT) {
val instrumentation = InstrumentationRegistry.getInstrumentation()
if (PackageManager.PERMISSION_DENIED == instrumentation.context.checkCallingOrSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
instrumentation.uiAutomation.grantRuntimePermission(instrumentation.context.packageName, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
}
fun takeScreenshot(screenshotName: String) {
val screenshot = Screenshot.capture().apply {
name = screenshotName
}
screenshot.process(setOf(EccScreenCaptureProcessor.new()))
onView(isRoot())
.captureToBitmap()
.writeToTestStorage(screenshotName)
}
}
private val composeTestRule = createAndroidComposeRule(MainActivity::class.java)
@get:Rule
val ruleChain = if (Build.VERSION_CODES.P <= Build.VERSION.SDK_INT) {
composeTestRule
} else {
val runtimePermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
RuleChain.outerRule(runtimePermissionRule).around(composeTestRule)
}
val composeTestRule = createAndroidComposeRule(MainActivity::class.java)
private fun navigateTo(route: String) = runBlocking {
withContext(Dispatchers.Main) {

View File

@ -1,55 +0,0 @@
package co.electriccoin.zcash.app.test
import android.os.Build
import android.os.Environment
import androidx.test.runner.screenshot.ScreenCapture
import androidx.test.runner.screenshot.ScreenCaptureProcessor
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.UUID
class EccScreenCaptureProcessor private constructor(private val screenshotDir: File) : ScreenCaptureProcessor {
@Throws(IOException::class)
override fun process(capture: ScreenCapture): String {
screenshotDir.checkDirectoryIsWriteable()
val filename = newFilename(
name = capture.name ?: "",
suffix = capture.format.toString().lowercase()
)
BufferedOutputStream(FileOutputStream(File(screenshotDir, filename))).use {
capture.bitmap.compress(capture.format, DEFAULT_QUALITY, it)
it.flush()
}
return filename
}
companion object {
const val DEFAULT_QUALITY = 100
fun new(): EccScreenCaptureProcessor {
// Screenshots need to be stored in a public directory so they won't get cleared by Test Orchestrator
// This path must be coordinated with the build.gradle.kts script which copies these off the device
@Suppress("DEPRECATION")
val screenshotsDirectory = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "zcash_screenshots").also {
it.mkdirs()
}
return EccScreenCaptureProcessor(screenshotsDirectory)
}
private fun newFilename(name: String, suffix: String) = "screenshot-$name-${Build.VERSION.SDK_INT}-${Build.DEVICE}-${UUID.randomUUID()}.$suffix"
}
}
private fun File.checkDirectoryIsWriteable() {
if (!isDirectory && !canWrite()) {
throw IOException("The directory $this does not exist or is not writable.")
}
}

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="co.electriccoin.zcash">
package="co.electriccoin.zcash.app">
<application
android:name=".app.ZcashApplication"
android:name="co.electriccoin.zcash.app.ZcashApplication"
android:allowBackup="false"
android:label="@string/app_name">
@ -11,7 +11,7 @@
Using an alias ensures we can refactor the actual Activity without breaking
clients. -->
<activity-alias
android:name=".LauncherActivity"
android:name="co.electricoin.zcash.LauncherActivity"
android:exported="true"
android:label="@string/app_name"
android:targetActivity="co.electriccoin.zcash.ui.MainActivity">

View File

@ -1,7 +1,6 @@
package co.electriccoin.zcash.app
import android.app.Application
import co.electriccoin.zcash.BuildConfig
import co.electriccoin.zcash.crash.android.CrashReporter
import co.electriccoin.zcash.spackle.StrictModeCompat
import co.electriccoin.zcash.spackle.Twig

View File

@ -2,7 +2,7 @@
// These are determined by `ew-cli --models`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val EMULATOR_WTF_MIN_SDK = 24
val EMULATOR_WTF_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val EMULATOR_WTF_MAX_SDK = 31

View File

@ -97,12 +97,11 @@ fladle {
filesToDownload.set(listOf(
"*/matrix_*/*test_results_merged\\.xml",
"*/matrix_*/*/artifacts/sdcard/Pictures/zcash_screenshots/*\\.png"
"*/matrix_*/*/artifacts/sdcard/googletest/test_outputfiles/*\\.png"
))
directoriesToPull.set(listOf(
// This path needs to be coordinated with the implementation in the app module's tests
"/sdcard/Pictures/zcash_screenshots"
"/sdcard/googletest/test_outputfiles"
))
}

View File

@ -64,7 +64,7 @@ IS_SDK_INCLUDED_BUILD=false
# A lower version on the libraries helps to ensure some degree of backwards compatiblity, for the project
# as a whole. But a higher version on the app ensures that we aren't directly supporting users
# with old devices.
ANDROID_LIB_MIN_SDK_VERSION=23
ANDROID_LIB_MIN_SDK_VERSION=24
ANDROID_APP_MIN_SDK_VERSION=27
ANDROID_TARGET_SDK_VERSION=31
ANDROID_COMPILE_SDK_VERSION=31
@ -91,14 +91,14 @@ ANDROIDX_COMPOSE_MATERIAL3_VERSION=1.0.0-alpha09
ANDROIDX_COMPOSE_VERSION=1.1.1
ANDROIDX_CONSTRAINTLAYOUT_VERSION=1.0.0
ANDROIDX_CORE_VERSION=1.7.0
ANDROIDX_ESPRESSO_VERSION=3.4.0
ANDROIDX_ESPRESSO_VERSION=3.5.0-alpha07
ANDROIDX_LIFECYCLE_VERSION=2.4.1
ANDROIDX_NAVIGATION_COMPOSE_VERSION=2.4.2
ANDROIDX_SECURITY_CRYPTO_VERSION=1.1.0-alpha03
ANDROIDX_SPLASH_SCREEN_VERSION=1.0.0-rc01
ANDROIDX_TEST_JUNIT_VERSION=1.1.3
ANDROIDX_TEST_ORCHESTRATOR_VERSION=1.4.1
ANDROIDX_TEST_CORE_VERSION=1.4.1-alpha06
ANDROIDX_TEST_JUNIT_VERSION=1.1.4-alpha07
ANDROIDX_TEST_ORCHESTRATOR_VERSION=1.4.2-alpha03
ANDROIDX_TEST_CORE_VERSION=1.4.1-alpha07
ANDROIDX_TEST_RUNNER_VERSION=1.5.0-alpha03
ANDROIDX_UI_AUTOMATOR_VERSION=2.2.0-alpha1
ANDROIDX_WORK_MANAGER_VERSION=2.7.1

View File

@ -199,8 +199,8 @@ dependencyResolutionManagement {
//alias("androidx-espresso-contrib", "androidx.test.espresso:espresso-contrib:$androidxEspressoVersion")
library("androidx-espresso-core", "androidx.test.espresso:espresso-core:$androidxEspressoVersion")
library("androidx-espresso-intents", "androidx.test.espresso:espresso-intents:$androidxEspressoVersion")
library("androidx-test-core", "androidx.test:core:$androidxTestCoreVersion")
library("androidx-test-junit", "androidx.test.ext:junit:$androidxTestJunitVersion")
library("androidx-test-core", "androidx.test:core-ktx:$androidxTestCoreVersion")
library("androidx-test-junit", "androidx.test.ext:junit-ktx:$androidxTestJunitVersion")
library("androidx-test-orchestrator", "androidx.test:orchestrator:$androidxTestOrchestratorVersion")
library("androidx-test-runner", "androidx.test:runner:$androidxTestRunnerVersion")
library("androidx-uiAutomator", "androidx.test.uiautomator:uiautomator-v18:$androidxUiAutomatorVersion")