[#598] Refactoring ScreenshotTest to a separate test module

* Temporary enable FTL test

* Enable emulator.wft tests for ui-integration-test module on CI

* Test Fladle configuration for ui-integration-test module

* Fix Fladle for ui-integration-test module

* Rename ui-integration-test module flade configuration

* Disable again FTL action from PRs

* Clear support for FTL from ui-integration-test module

* Refactor the emulator.wtf support across our modules.

* [#598] Refactoring ScreenshotTest to a separate test module

- Created a new com.android.test module
- Moved our ScreenShotTest to the separate module
- Enabled the new module for emulator.wtf service
- Cleaned up the app module from ScreenShotTest
- Updated Architecture and Setup documentation

* Exclude pure test modules from Detekt check

* Improved run configurations

- Changed common emulator.wtf run configuration name to align with the rest of our configurations
- Added configuration for the new screenshot tests module
- Updated documentation

* Linked code TODO

* Disabled self-instrumenting

- As it appeared to be the way how to leverage the app module Gradle settings in this new module
- App name is taken from app module settings
- Screenshot tests works with pseudolocales again
This commit is contained in:
Honza Rychnovsky 2022-10-18 15:08:36 +02:00 committed by GitHub
parent 2c14ee8bd3
commit 4eddf4e74b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 172 additions and 57 deletions

View File

@ -294,7 +294,7 @@ jobs:
ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: ""
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
run: |
./gradlew testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf
./gradlew testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf
- name: Collect Artifacts
if: ${{ always() }}
timeout-minutes: 1

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="testOnEmulatorWtf" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="test_with_emulatorWtf" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
@ -13,6 +13,7 @@
<option value="testDebugWithEmulatorWtf" />
<option value=":app:testZcashmainnetDebugWithEmulatorWtf" />
<option value=":ui-integration-test:testZcashmainnetDebugWithEmulatorWtf" />
<option value=":ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf" />
</list>
</option>
<option name="vmOptions" />

View File

@ -0,0 +1,57 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ui-screenshot-test:connectedCheck" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="zcash-android-app.ui-screenshot-test" />
<option name="TESTING_TYPE" value="0" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="RETENTION_ENABLED" value="No" />
<option name="RETENTION_MAX_SNAPSHOTS" value="2" />
<option name="RETENTION_COMPRESS_SNAPSHOTS" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@ -10,9 +10,6 @@ plugins {
val packageName = project.property("ZCASH_RELEASE_PACKAGE_NAME").toString()
// Force orchestrator to be used for this module, because we need cleared state to generate screenshots
val isOrchestratorEnabled = true
val testnetNetworkName = "Testnet"
android {
@ -26,19 +23,9 @@ android {
versionCode = project.property("ZCASH_VERSION_CODE").toString().toInt()
versionName = project.property("ZCASH_VERSION_NAME").toString()
if (isOrchestratorEnabled) {
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
testInstrumentationRunner = "co.electriccoin.zcash.test.ZcashUiTestRunner"
}
if (isOrchestratorEnabled) {
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
@ -182,16 +169,9 @@ dependencies {
implementation(projects.spackleAndroidLib)
implementation(projects.uiLib)
androidTestImplementation(libs.androidx.compose.test.junit)
androidTestImplementation(libs.androidx.navigation.compose)
androidTestImplementation(libs.androidx.uiAutomator)
androidTestImplementation(libs.bundles.androidx.test)
androidTestImplementation(projects.sdkExtLib)
androidTestImplementation(projects.sdkExtUiLib)
androidTestImplementation(projects.spackleLib)
androidTestImplementation(projects.testLib)
if (isOrchestratorEnabled) {
if (project.property("IS_USE_TEST_ORCHESTRATOR").toString().toBoolean()) {
androidTestUtil(libs.androidx.test.orchestrator) {
artifact {
type = "apk"
@ -312,7 +292,3 @@ fladle {
}
}
}
emulatorwtf {
directoriesToPull.set(listOf("/sdcard/googletest/test_outputfiles"))
}

View File

@ -24,6 +24,9 @@ plugins {
id("secant.rosetta-conventions")
}
val uiIntegrationModuleName: String = projects.uiIntegrationTest.name
val uiScreenshotModuleName: String = projects.uiScreenshotTest.name
tasks {
register("detektAll", io.gitlab.arturbosch.detekt.Detekt::class) {
parallel = true
@ -35,6 +38,8 @@ tasks {
exclude("**/commonTest/**")
exclude("**/jvmTest/**")
exclude("**/androidTest/**")
// To exclude the whole pure test modules
exclude(uiIntegrationModuleName, uiScreenshotModuleName)
// To regenerate the config, run the task `detektGenerateConfig`
config.setFrom(files("${rootProject.projectDir}/tools/detekt.yml"))
buildUponDefaultConfig = true
@ -134,5 +139,6 @@ kover {
"ui-design-lib",
"ui-integration-test",
"ui-lib",
"ui-screenshot-test",
)
}

View File

@ -26,20 +26,22 @@ The logical components of the app are implemented as a number of Gradle modules.
* `app` — Compiles all of the modules together into the final application. This module contains minimal actual code. Note that the Java package structure for this module is under `co.electriccoin.zcash.app` while the Android package name is `co.electriccoin.zcash`.
* `build-info-lib` — Collects information from the build environment (e.g. Git SHA, Git commit count) and compiles them into the application. Can also be used for injection of API keys or other secrets.
* `crash` — For collecting and reporting exceptions and crashes
* crash — For collecting and reporting exceptions and crashes
* `crash-lib` — Common crash collection logic for Kotlin and JVM. This is not fully-featured by itself, but the long-term plan is multiplatform support.
* `crash-android-lib` — Android-specific crash collection logic, built on top of the common and JVM implementation in `crash-lib`
* ui
* `ui-design` — Contains UI theme elements only. Besides offering modularization, this allows for hiding of some Material Design components behind our own custom components.
* `ui-lib` — User interface that the user interacts with. This contains 99% of the UI code, along with localizations, icons, and other assets.
* `ui-integration-test` — Is a pure test module dedicated for integration tests only. It has Android Test Orchestrator turned on — it allows us to run each of our tests within its own invocation of Instrumentation, and thus brings us benefits for the testing environment (minimal shared state, crashes are isolated, permissions are reset).
* ui-test
* `ui-integration-test` — Is a pure test module dedicated for integration tests only. It has Android Test Orchestrator turned on — it allows us to run each of our tests within its own invocation of Instrumentation, and thus brings us benefits for the testing environment (minimal shared state, crashes are isolated, permissions are reset).
* `ui-screenshot-test` — Is also a pure test module, whose purpose is to provide a wrapper for the ui screenshot tests. It has the Android Test Orchestrator turned on too.
* preference
* `preference-api-lib` — Multiplatform interfaces for key-value storage of preferences.
* `preference-impl-android-lib` — Android-specific implementation for preference storage.
* sdk
* `sdk-ext-lib` — Contains extensions on top of the to the Zcash SDK. Some of these extensions might be migrated into the SDK eventually, while others might represent Android-centric idioms. Depending on how this module evolves, it could adopt another name such as `wallet-lib` or be split into two.
* `sdk-ext-ui` — Place for Zcash SDK components (same as `sdk-ext-lib`), which are related to the UI (e.g. depend on user locale and thus need to be translated via `strings.xml`).
* `spackle` — Random utilities, to fill in the cracks in the frameworks.
* spackle — Random utilities, to fill in the cracks in the frameworks.
* `spackle-lib` — Multiplatform implementation for Kotlin and JVM
* `spackle-android-lib` — Android-specific additions.
@ -68,11 +70,12 @@ The following diagram shows a rough depiction of dependencies between the module
subgraph ui
uiDesignLib[[ui-design-lib]];
uiLib[[ui-lib]];
uiIntegrationTest[[ui-integration-test]];
end
uiDesignLib[[ui-design-lib]] --> uiLib[[ui-lib]];
uiLib[[ui-lib]] --> uiIntegrationTest[[ui-integration-test]];
uiDesignLib[[ui-design-lib]] --> uiIntegrationTest[[ui-integration-test]];
subgraph ui-test
uiIntegrationTest[[ui-integration-test]];
uiScreenshotTest[[ui-screenshot-test]];
end
subgraph spackle
spackleLib[[spackle-lib]];
spackleAndroidLib[[spackle-android-lib]];
@ -81,6 +84,7 @@ The following diagram shows a rough depiction of dependencies between the module
preference --> ui[[ui]];
sdk --> ui[[ui]];
spackle[[spackle]] --> ui[[ui]];
ui[[ui]] --> ui-test[[ui-test]];
ui[[ui]] --> app{app};
crash[[crash]] --> app{app};
```

View File

@ -65,11 +65,11 @@ Start by making sure the command line with Gradle works first, because **all the
1. Delete the invisible `.idea` in the root directory of the project. This directory is partially ignored by Git, so deleting it will remove the files that are untracked
1. Restore the missing files in `.idea` folder from Git
1. Relaunch Android Studio
2. Clean the individual Gradle project by running `./gradlew clean` which will purge local build outputs.
3. Run Gradle with the argument `--rerun-tasks` which will effectively disable the build cache by re-running tasks and repopulating the cache. E.g. `./gradlew assemble --rerun-tasks`
4. Reboot your computer, which will ensure that Gradle and Kotlin daemons are completely killed and relaunched
5. Delete the global Gradle cache under `~/.gradle/caches`
6. If adding a new dependency or updating a dependency, a warning that a dependency cannot be found may indicate the Maven repository restrictions need adjusting
1. Clean the individual Gradle project by running `./gradlew clean` which will purge local build outputs.
1. Run Gradle with the argument `--rerun-tasks` which will effectively disable the build cache by re-running tasks and repopulating the cache. E.g. `./gradlew assemble --rerun-tasks`
1. Reboot your computer, which will ensure that Gradle and Kotlin daemons are completely killed and relaunched
1. Delete the global Gradle cache under `~/.gradle/caches`
1. If adding a new dependency or updating a dependency, a warning that a dependency cannot be found may indicate the Maven repository restrictions need adjusting
## 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.
@ -82,9 +82,9 @@ A variety of Gradle tasks are set up within the project, and these tasks are als
* `lint` - Performs static analysis with Android lint
* `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.
A few notes on running instrumentation tests on the `ui-screenshot-test` module:
- Screenshots are generated automatically and copied to [/ui-screenshot-test/build/output](../ui-screenshot-test/build/outputs)
- Running the Android tests on the `ui-screenshot-test` 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 Managed Devices are also configured with our build scripts. We have found best results running tests one module at a time, rather than trying to run them all at once. For example: `./gradlew :ui-lib:pixel2TargetDebugAndroidTest` will run the UI tests on a Pixel 2 sized device using our target API version.
@ -160,7 +160,7 @@ For Continuous Integration, see [CI.md](CI.md). The rest of this section is reg
1. If you are an Electric Coin Co team member: We are still setting up a process for this, because emulator.wtf does not yet support individual API tokens
1. If you are an open source contributor: Visit http://emulator.wtf and request an API key
1. Set the emulator.wtf API key as a global Gradle property `ZCASH_EMULATOR_WTF_API_KEY` under `~/.gradle/gradle.properties`
1. Run the Gradle task `./gradlew testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf` (emulator.wtf tasks do build the app, so you don't need to build them beforehand)
1. Run the Gradle task `./gradlew testDebugWithEmulatorWtf :app:testZcashmainnetDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf` (emulator.wtf tasks do build the app, so you don't need to build them beforehand)
## Testnet funds

View File

@ -273,6 +273,7 @@ include("test-lib")
include("ui-design-lib")
include("ui-integration-test")
include("ui-lib")
include("ui-screenshot-test")
val zcashSdkIncludedBuildPath = extra["SDK_INCLUDED_BUILD_PATH"].toString()

View File

@ -36,16 +36,13 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val walletViewModel by viewModels<WalletViewModel>()
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val storageCheckViewModel by viewModels<StorageCheckViewModel>()
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
lateinit var navControllerForTesting: NavHostController
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val configurationOverrideFlow = MutableStateFlow<ConfigurationOverride?>(null)
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -1,7 +1,6 @@
package co.electriccoin.zcash.ui
import android.annotation.SuppressLint
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.NavOptionsBuilder
@ -141,33 +140,23 @@ private fun NavHostController.popBackStackJustOnce(currentRouteToBePopped: Strin
}
object NavigationTargets {
@VisibleForTesting
const val HOME = "home"
@VisibleForTesting
const val PROFILE = "profile"
@VisibleForTesting
const val WALLET_ADDRESS_DETAILS = "wallet_address_details"
@VisibleForTesting
const val SETTINGS = "settings"
@VisibleForTesting
const val SEED = "seed"
@VisibleForTesting
const val REQUEST = "request"
@VisibleForTesting
const val SEND = "send"
@VisibleForTesting
const val SUPPORT = "support"
@VisibleForTesting
const val ABOUT = "about"
@VisibleForTesting
const val SCAN = "scan"
}

View File

@ -7,5 +7,6 @@
<string name="about_build_header">Build</string>
<string name="about_legal_header">Legal</string>
<!-- TODO [#392] Update with real legal info. -->
<!-- TODO [#392] https://github.com/zcash/secant-android-wallet/issues/392 -->
<string name="about_legal_info">GitHub Issue #392</string>
</resources>

View File

@ -0,0 +1,77 @@
plugins {
id("com.android.test")
kotlin("android")
id("secant.android-build-conventions")
id("wtf.emulator.gradle")
id("secant.emulator-wtf-conventions")
}
// Force orchestrator to be used for this module, because we need cleared state to generate screenshots
val isOrchestratorEnabled = true
android {
namespace = "co.electroniccoin.zcash.ui.screenshot"
// Target needs to be set to com.android.application type module
targetProjectPath = ":${projects.app.name}"
defaultConfig {
if (isOrchestratorEnabled) {
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
testInstrumentationRunner = "co.electriccoin.zcash.test.ZcashUiTestRunner"
}
// Define the same flavors as in app module
flavorDimensions.add("network")
productFlavors {
create("zcashtestnet") {
dimension = "network"
}
create("zcashmainnet") {
dimension = "network"
}
}
if (isOrchestratorEnabled) {
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.androidx.compose.compiler.get().versionConstraint.displayName
}
}
dependencies {
implementation(projects.uiLib)
implementation(projects.testLib)
implementation(projects.spackleAndroidLib)
implementation(projects.sdkExtLib)
implementation(projects.sdkExtUiLib)
implementation(libs.bundles.androidx.test)
implementation(libs.bundles.androidx.compose.core)
implementation(libs.bundles.play.core)
implementation(libs.androidx.compose.test.junit)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.uiAutomator)
if (isOrchestratorEnabled) {
androidTestUtil(libs.androidx.test.orchestrator) {
artifact {
type = "apk"
}
}
}
}
emulatorwtf {
directoriesToPull.set(listOf("/sdcard/googletest/test_outputfiles"))
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>

View File

@ -1,4 +1,4 @@
package co.electriccoin.zcash.app
package co.electroniccoin.zcash.ui.screenshot
import android.content.Context
import android.os.Build
@ -132,6 +132,7 @@ class ScreenshotTest : UiTestPrerequisites() {
private fun take_screenshots_for_restore_wallet(resContext: Context, tag: String) {
// TODO [#286]: Screenshot tests fail on Firebase Test Lab
// TODO [#286]: https://github.com/zcash/secant-android-wallet/issues/286
if (FirebaseTestLabUtil.isFirebaseTestLab(ApplicationProvider.getApplicationContext())) {
return
}
@ -213,6 +214,7 @@ class ScreenshotTest : UiTestPrerequisites() {
private fun take_screenshots_for_new_wallet_and_rest_of_app(resContext: Context, tag: String) {
// TODO [#286]: Screenshot tests fail on Firebase Test Lab
// TODO [#286]: https://github.com/zcash/secant-android-wallet/issues/286
if (FirebaseTestLabUtil.isFirebaseTestLab(ApplicationProvider.getApplicationContext())) {
return
}