[#190] Pull screenshots on CI

This commit is contained in:
Carter Jernigan 2022-05-24 09:39:56 -04:00 committed by GitHub
parent caf58f963a
commit 94d259d5b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 170 additions and 86 deletions

View File

@ -233,7 +233,7 @@ jobs:
- name: Build
timeout-minutes: 20
run: |
./gradlew assembleDebug assembleAndroidTest
./gradlew assembleDebug assembleAndroidTest assembleZcashmainnetDebug assembleZcashtestnetDebug
- name: Authenticate to Google Cloud for Firebase Test Lab
id: auth_test_lab
uses: google-github-actions/auth@50dbfd0907520dcccbd51e965728eb32e592b8fa
@ -290,7 +290,7 @@ jobs:
env:
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
run: |
./gradlew testDebugWithEmulatorWtf
./gradlew testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf
- name: Collect Artifacts
if: ${{ always() }}
timeout-minutes: 1

View File

@ -4,14 +4,14 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="scriptParameters" value="assemble" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
</option>
<option name="vmOptions" value="" />
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>

View File

@ -4,16 +4,14 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="scriptParameters" value="assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="assembleAndroidTest" />
</list>
<list />
</option>
<option name="vmOptions" value="" />
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>

View File

@ -1,19 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ktlint" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="ktlintFormat" 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="ktlintFormat" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="ktlint" />
</list>
<list />
</option>
<option name="vmOptions" value="" />
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>

View File

@ -101,7 +101,6 @@ android {
listOf(
"**/*.kotlin_metadata",
".readme",
"build-data.properties",
"META-INF/*.kotlin_module",
"META-INF/android.arch**",
"META-INF/androidx**",
@ -111,6 +110,7 @@ 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",
@ -225,49 +225,66 @@ tasks.whenTaskAdded {
}
}
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_SDK = 23
fladle {
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_SDK = 30
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_SDK = 30
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
if (firebaseTestLabKeyPath.isNotBlank()) {
val minSdkVersion = run {
val buildMinSdk =
project.properties["ANDROID_APP_MIN_SDK_VERSION"].toString().toInt()
val buildMinSdk = project.properties["ANDROID_APP_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_SDK).toString()
}
val targetSdkVersion = run {
val buildTargetSdk =
project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
val buildTargetSdk = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_SDK).toString()
}
fladle {
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
val firebaseProject = project.properties["ZCASH_FIREBASE_TEST_LAB_PROJECT"].toString()
if (firebaseTestLabKeyPath.isNotEmpty()) {
serviceAccountCredentials.set(File(firebaseTestLabKeyPath))
} else if (firebaseProject.isNotEmpty()) {
projectId.set(firebaseProject)
}
configs {
create("sanityConfig") {
clearPropertiesForSanityRobo()
configs {
create("sanityConfig") {
clearPropertiesForSanityRobo()
debugApk.set(
project.provider {
"${buildDir}/outputs/universal_apk/zcashmainnetRelease/app-zcashmainnet-release-universal.apk"
}
)
debugApk.set(
project.provider {
"${buildDir}/outputs/universal_apk/zcashmainnetRelease/app-zcashmainnet-release-universal.apk"
}
)
testTimeout.set("5m")
devices.addAll(
mapOf("model" to "Nexus6P", "version" to minSdkVersion),
mapOf("model" to "Pixel2", "version" to targetSdkVersion)
)
testTimeout.set("5m")
flankVersion.set(libs.versions.flank.get())
}
devices.addAll(
mapOf("model" to "Pixel2", "version" to minSdkVersion),
mapOf("model" to "Pixel2", "version" to targetSdkVersion)
)
flankVersion.set(libs.versions.flank.get())
}
}
}
emulatorwtf {
// This path needs to be coordinated with the implementation in the app module's tests
directoriesToPull.set(listOf("/sdcard/Pictures/zcash_screenshots"))
devices.set(
listOf(
// TODO [#285]: Our screenshot tests don't work on older devices
// mapOf("model" to "Pixel2", "version" to minSdkVersion),
// TODO [#430]: App won't run on API 31 Intel emulators
@Suppress("MagicNumber")
mapOf("model" to "Pixel2", "version" to 30)
)
)
}

View File

@ -50,47 +50,60 @@ fun isNonStable(version: String): Boolean {
return unstableKeywords.any { versionLowerCase.contains(it) }
}
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_SDK = 30
fladle {
// Firebase Test Lab has min and max values that might differ from our project's
// These are determined by `gcloud firebase test android models list`
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MIN_SDK = 23
@Suppress("MagicNumber", "PropertyName", "VariableNaming")
val FIREBASE_TEST_LAB_MAX_SDK = 30
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
if (firebaseTestLabKeyPath.isNotBlank()) {
val minSdkVersion = run {
val buildMinSdk = project.properties["ANDROID_LIB_MIN_SDK_VERSION"].toString().toInt()
// Fladle will use the app APK as the additional APK, so we have to
// use the app's minSdkVersion here.
val buildMinSdk = project.properties["ANDROID_APP_MIN_SDK_VERSION"].toString().toInt()
buildMinSdk.coerceAtLeast(FIREBASE_TEST_LAB_MIN_SDK).toString()
}
val targetSdkVersion = run {
val buildTargetSdk = project.properties["ANDROID_TARGET_SDK_VERSION"].toString().toInt()
buildTargetSdk.coerceAtMost(FIREBASE_TEST_LAB_MAX_SDK).toString()
}
fladle {
val firebaseTestLabKeyPath = project.properties["ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH"].toString()
val firebaseProject = project.properties["ZCASH_FIREBASE_TEST_LAB_PROJECT"].toString()
if (firebaseTestLabKeyPath.isNotEmpty()) {
serviceAccountCredentials.set(File(firebaseTestLabKeyPath))
// TODO [#282]: Replace this with NexusLowRes once tests pass on larger screen sizes
devices.addAll(
mapOf("model" to "Nexus6", "version" to minSdkVersion),
mapOf("model" to "Pixel2", "version" to targetSdkVersion)
)
@Suppress("MagicNumber")
flakyTestAttempts.set(2)
if (project.properties["IS_USE_TEST_ORCHESTRATOR"].toString().toBoolean()) {
useOrchestrator.set(true)
environmentVariables.set(mapOf("clearPackageData" to "true"))
} else {
useOrchestrator.set(false)
}
flankVersion.set(libs.versions.flank.get())
filesToDownload.set(listOf(
"*/matrix_*/*test_results_merged\\.xml"
))
} else if (firebaseProject.isNotEmpty()) {
projectId.set(firebaseProject)
}
devices.addAll(
mapOf("model" to "Pixel2", "version" to minSdkVersion),
mapOf("model" to "Pixel2", "version" to targetSdkVersion)
)
@Suppress("MagicNumber")
flakyTestAttempts.set(2)
// Always use orchestrator for Firebase Test Lab.
// Some submodules don't need it, but it is difficult to configure on a per-module basis from here
// since this configuration applies to all modules.
useOrchestrator.set(true)
environmentVariables.set(mapOf("clearPackageData" to "true"))
flankVersion.set(libs.versions.flank.get())
filesToDownload.set(listOf(
"*/matrix_*/*test_results_merged\\.xml",
"*/matrix_*/*/artifacts/sdcard/Pictures/zcash_screenshots/*\\.png"
))
directoriesToPull.set(listOf(
// This path needs to be coordinated with the implementation in the app module's tests
"/sdcard/Pictures/zcash_screenshots"
))
}
// All of this should be refactored to build-conventions

View File

@ -48,32 +48,36 @@ Start by making sure the command line with Gradle works first, because **all the
## Gradle Tasks
A variety of Gradle tasks are set up within the project, and these tasks are also accessible in Android Studio as run configurations.
* `assemble` - Compiles the application but does not deploy it
* `assembleAndroidTest` - Compiles the application and tests, but does not deploy the application or run the tests
* `assembleAndroidTest` - Compiles the application and tests, but does not deploy the application or run the tests. The Android Studio run configuration actually runs all of these tasks because the debug APKs are necessary to run the tests: `assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest`
* `detektAll` - Performs static analysis with Detekt
* `ktlint` - Performs code formatting checks with ktlint
* `ktlintFormat` - Performs code formatting checks with ktlint
* `lint` - Performs static analysis with Android lint
* `dependencyUpdates` - Checks for available dependency updates
* `dependencyUpdates` - Checks for available dependency updates. It will only suggest final releases, unless a particular dependency is already using a non-final release (e.g. alpha, beta, RC).
A few notes on running instrumentation tests on the app module:
- Screenshots are generated automatically and copied to (/app/build/reports/androidTests/connected/zcash_screenshots)[../app/build/reports/androidTests/connected/zcash_screenshots]
- Running the Android tests on the app module will erase the data stored by the app. This is because Test Orchestrator is required to reset app state to successfully perform integration tests.
## Gradle Properties
A variety of Gradle properties can be used to configure the build.
A variety of Gradle properties can be used to configure the build. Most of these properties are optional and help with advanced configuration. If you're just doing local development or making a small pull request contribution, you likely do not need to worry about these.
### Debug Signing
By default, the application is signed by the developers automatically generated debug signing key. In a team of developers, it may be advantageous to share a debug key so that debug builds can access key-restricted services such as Firebase or Google Maps. For such a setup, the path to a shared debug signing key can be set with the property `ZCASH_DEBUG_KEYSTORE_PATH`.
### Release Signing
This section is optional.
To enable release signing, a release keystore needs to be provided during the build. This can be injected securely by setting the following Gradle properties.
* ZCASH_RELEASE_KEYSTORE_PATH
* ZCASH_RELEASE_KEYSTORE_PASSWORD
* ZCASH_RELEASE_KEY_ALIAS
* ZCASH_RELEASE_KEY_ALIAS_PASSWORD
* `ZCASH_RELEASE_KEYSTORE_PATH`
* `ZCASH_RELEASE_KEYSTORE_PASSWORD`
* `ZCASH_RELEASE_KEY_ALIAS`
* `ZCASH_RELEASE_KEY_ALIAS_PASSWORD`
On a developer machine, these might be set under the user's global properties (e.g. `~/.gradle/gradle.properties` on macOS and Linux). On a continuous integration machine, these can also be set using environment variables with the prefix `ORG_GRADLE_PROJECT_` (e.g. `ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH`). DO NOT set these in the gradle.properties inside the Git repository, as this will leak your keystore password.
### Included builds
This section is optional.
To simplify implementation of SDK features in conjunction with changes to the app, a Gradle [Included Build](https://docs.gradle.org/current/userguide/composite_builds.html) can be configured.
1. Check out the SDK to a directory path of `../zcash-android-sdk` relative to the root of this app's repo. For example:
@ -87,4 +91,20 @@ To simplify implementation of SDK features in conjunction with changes to the ap
There are some limitations of included builds:
1. Properties from `secant-android-wallet` will override those set in `zcash-android-sdk` with the same name
1. Modules in each project cannot share the same name. For this reason, build-conventions have different names in each repo (`zcash-android-sdk/build-conventions` vs `secant-android-wallet/build-convention`)
1. Modules in each project cannot share the same name. For this reason, build-conventions have different names in each repo (`zcash-android-sdk/build-conventions` vs `secant-android-wallet/build-convention`)
### Firebase Test Lab
This section is optional.
For Continuous Integration, see [CI.md](CI.md). The rest of this section is regarding local development.
1. Set up a Firebase project, for the purpose of running Firebase Test Lab
1. Set the Firebase Google Cloud project name as a global Gradle property `ZCASH_FIREBASE_TEST_LAB_PROJECT` under `~/.gradle/gradle.properties`
1. Run the Gradle task `flankAuth` to generate a Firebase authentication token on your machine
Tests can now be run on Firebase Test Lab from your local machine.
The Firebase Test Lab tasks DO NOT build the app, so they rely on existing build outputs. This means you should:
1. Build the debug and test APKs: `./gradlew assembleDebug assembleZcashmainnetDebug assembleZcashtestnetDebug assembleAndroidTest`
1. Run the tests: `./gradlew runFlank`

View File

@ -22,8 +22,11 @@ IS_COVERAGE_ENABLED=false
# It is disabled by default, because it causes tests to take about 2x longer to run.
IS_USE_TEST_ORCHESTRATOR=false
# Optional API key for Firebase Test Lab
# Either provide a path to a Firebase Test Lab service key (best for CI)
# OR
# login with `./gradlew flankAuth` and provide the project name (best for local development)
ZCASH_FIREBASE_TEST_LAB_API_KEY_PATH=
ZCASH_FIREBASE_TEST_LAB_PROJECT=
# Optionally disable minification
IS_MINIFY_ENABLED=true

View File

@ -0,0 +1,35 @@
package co.electriccoin.zcash.spackle
import android.content.Context
import android.provider.Settings
/*
* This is not under a test module, because there are some code paths that we might want to alter
* during Emulator WTF tests.
*/
object EmulatorWtfUtil {
private const val EMULATOR_WTF_SETTING = "emulator.wtf" // $NON-NLS
private const val SETTING_TRUE = "true" // $NON-NLS
private val isEmulatorWtfCached = LazyWithArgument<Context, Boolean> {
isEmulatorWtfImpl(it)
}
/**
* @return True if the environment is emulator.wtf
*/
fun isEmulatorWtf(context: Context) = isEmulatorWtfCached.getInstance(context)
private fun isEmulatorWtfImpl(context: Context): Boolean {
// Tested with the benchmark library, this is very fast. There shouldn't be a need to make
// this a suspend function. That said, we'll still cache the result as a just-in-case
// since IPC may be involved.
return runCatching {
SETTING_TRUE == Settings.System.getString(context.contentResolver, EMULATOR_WTF_SETTING)
}.recover {
// Fail-safe in case an error occurs
// 99.9% of the time, it won't be Firebase Test Lab
false
}.getOrThrow()
}
}