chore: Run basic integration test on each commit/PR
This commit is contained in:
parent
4d377ce833
commit
b9b7360686
53
.github/workflows/check_tests.yaml
vendored
Normal file
53
.github/workflows/check_tests.yaml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: "✔️ Check Tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check-android-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Set up JDK 17"
|
||||
id: setup-java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: "Enable KVM group perms"
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
- name: "Prepare env"
|
||||
run: |
|
||||
echo ${{ secrets.KEYSTORE_B64 }} | base64 -d | zcat >> androidApp/keyguard-release.keystore
|
||||
echo ${{ secrets.KEYSTORE_PROPS_B64 }} | base64 -d | zcat >> androidApp/keyguard-release.properties
|
||||
echo ${{ secrets.GOOGLE_SERVICES }} | base64 -d | zcat >> androidApp/google-services.json
|
||||
echo ${{ secrets.SERVICE_ACCOUNT_B64 }} | base64 -d | zcat >> service-account-google.json
|
||||
python .github/setup_gradle_properties_release.py tag=${{ github.ref_name }}
|
||||
- name: "Setup Gradle"
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
env:
|
||||
JAVA_HOME: ${{ steps.setup-java.outputs.path }}
|
||||
- name: "Check and Build licenses"
|
||||
run: ./gradlew :androidApp:licenseeAndroidPlayStoreRelease
|
||||
- name: "Move licenses"
|
||||
run: |
|
||||
mv -f androidApp/build/reports/licensee/androidPlayStoreRelease/artifacts.json common/src/commonMain/composeResources/files/licenses.json
|
||||
- name: "Run tests"
|
||||
id: baseline-profiles
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: 30
|
||||
arch: x86_64
|
||||
disable-animations: true
|
||||
disk-size: 4G
|
||||
script: |
|
||||
adb root
|
||||
./gradlew :androidApp:connectedPlayStoreDebugAndroidTest
|
@ -52,6 +52,11 @@ android {
|
||||
versionName = versionInfo.marketingVersion
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
// The following argument makes the Android Test Orchestrator run its
|
||||
// "pm clear" command after each test invocation. This command ensures
|
||||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments["clearPackageData"] = "true"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
@ -67,6 +72,10 @@ android {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
testOptions {
|
||||
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||
}
|
||||
|
||||
androidResources {
|
||||
@Suppress("UnstableApiUsage")
|
||||
generateLocaleConfig = true
|
||||
@ -149,6 +158,20 @@ dependencies {
|
||||
implementation(project(":common"))
|
||||
baselineProfile(project(":androidBenchmark"))
|
||||
coreLibraryDesugaring(libs.android.desugarjdklibs)
|
||||
|
||||
// Android tests
|
||||
androidTestImplementation(project(":androidTest"))
|
||||
androidTestImplementation(libs.androidx.arch.core.testing)
|
||||
androidTestImplementation(libs.androidx.test.runner)
|
||||
androidTestImplementation(libs.androidx.test.rules)
|
||||
androidTestImplementation(libs.androidx.test.core)
|
||||
androidTestImplementation(libs.androidx.test.core.ktx)
|
||||
androidTestImplementation(libs.androidx.test.uiautomator)
|
||||
androidTestImplementation(libs.androidx.test.espresso.core)
|
||||
androidTestImplementation(libs.androidx.test.espresso.web)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit.ktx)
|
||||
androidTestUtil(libs.androidx.test.orchestrator)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
@ -0,0 +1,21 @@
|
||||
package com.artemchep.keyguard.test
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import org.junit.Before
|
||||
|
||||
abstract class BaseTest {
|
||||
lateinit var device: UiDevice
|
||||
|
||||
lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun init() {
|
||||
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
|
||||
device = UiDevice.getInstance(instrumentation)
|
||||
context = ApplicationProvider.getApplicationContext<Context>()
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.artemchep.keyguard.test
|
||||
|
||||
import com.artemchep.keyguard.BuildConfig
|
||||
|
||||
/**
|
||||
* Convenience parameter to use proper package name
|
||||
* with regards to build type and build flavor.
|
||||
*/
|
||||
val PACKAGE_NAME = StringBuilder("com.artemchep.keyguard").apply {
|
||||
val hasSuffix = when (BuildConfig.BUILD_TYPE) {
|
||||
"debug" -> true
|
||||
else -> false
|
||||
}
|
||||
if (hasSuffix) {
|
||||
append(".${BuildConfig.BUILD_TYPE}")
|
||||
}
|
||||
}.toString()
|
@ -0,0 +1,22 @@
|
||||
package com.artemchep.keyguard.test.core
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import com.artemchep.keyguard.test.BaseTest
|
||||
import com.artemchep.keyguard.test.PACKAGE_NAME
|
||||
import com.artemchep.test.feature.coreFeature
|
||||
import com.artemchep.test.feature.ensureMainScreen
|
||||
import com.artemchep.test.feature.launchDefaultActivityAndWait
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class CreateVaultTest : BaseTest() {
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun createVaultTest() {
|
||||
device.coreFeature.launchDefaultActivityAndWait(PACKAGE_NAME)
|
||||
device.coreFeature.ensureMainScreen()
|
||||
}
|
||||
}
|
@ -52,9 +52,10 @@ baselineProfile {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":androidTest"))
|
||||
implementation(libs.androidx.benchmark.macro.junit4)
|
||||
implementation(libs.androidx.espresso.core)
|
||||
implementation(libs.androidx.junit)
|
||||
implementation(libs.androidx.uiautomator)
|
||||
implementation(libs.androidx.test.espresso.core)
|
||||
implementation(libs.androidx.test.ext.junit)
|
||||
implementation(libs.androidx.test.uiautomator)
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.benchmark.macro.junit4.BaselineProfileRule
|
||||
import com.artemchep.macrobenchmark.PACKAGE_NAME
|
||||
import com.artemchep.macrobenchmark.ui.keyguard.createVaultAndWait
|
||||
import com.artemchep.test.feature.coreFeature
|
||||
import com.artemchep.test.feature.ensureMainScreen
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@ -29,6 +30,6 @@ class BaselineProfileGenerator {
|
||||
pressHome()
|
||||
startActivityAndWait()
|
||||
|
||||
createVaultAndWait()
|
||||
device.coreFeature.ensureMainScreen()
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
package com.artemchep.macrobenchmark.ui.keyguard
|
||||
|
||||
import androidx.benchmark.macro.MacrobenchmarkScope
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import java.util.regex.Pattern
|
||||
|
||||
fun MacrobenchmarkScope.waitForMainScreen() = kotlin.run {
|
||||
val search = kotlin.run {
|
||||
val pattern = Pattern.compile("nav:(setup|unlock|main)")
|
||||
val selector = By.res(pattern)
|
||||
Until.findObject(selector)
|
||||
}
|
||||
device.wait(search, 30_000)
|
||||
}
|
||||
|
||||
fun MacrobenchmarkScope.createVaultAndWait() = kotlin.run {
|
||||
val screen = waitForMainScreen()
|
||||
when (screen.resourceName) {
|
||||
"nav:setup",
|
||||
"nav:unlock",
|
||||
-> {
|
||||
// Create or unlock existing vault
|
||||
val password = screen
|
||||
.findObject(By.res("field:password"))
|
||||
password.text = "111111"
|
||||
val btn = screen
|
||||
.findObject(By.res("btn:go"))
|
||||
btn.wait(Until.clickable(true), 1000L)
|
||||
btn.click()
|
||||
// wait till main screen is loaded
|
||||
device.wait(Until.findObject(By.res("nav:main")), 30_000)
|
||||
}
|
||||
else -> screen
|
||||
}
|
||||
}
|
41
androidTest/build.gradle.kts
Normal file
41
androidTest/build.gradle.kts
Normal file
@ -0,0 +1,41 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = libs.versions.androidCompileSdk.get().toInt()
|
||||
namespace = "com.artemchep.test"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.androidMinSdk.get().toInt()
|
||||
targetSdk = libs.versions.androidTargetSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
|
||||
val accountManagementDimension = "accountManagement"
|
||||
flavorDimensions += accountManagementDimension
|
||||
productFlavors {
|
||||
maybeCreate("playStore").apply {
|
||||
dimension = accountManagementDimension
|
||||
}
|
||||
maybeCreate("none").apply {
|
||||
dimension = accountManagementDimension
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.test.espresso.core)
|
||||
implementation(libs.androidx.test.ext.junit)
|
||||
implementation(libs.androidx.test.uiautomator)
|
||||
}
|
2
androidTest/src/main/AndroidManifest.xml
Normal file
2
androidTest/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest></manifest>
|
104
androidTest/src/main/java/com/artemchep/test/feature/screen.kt
Normal file
104
androidTest/src/main/java/com/artemchep/test/feature/screen.kt
Normal file
@ -0,0 +1,104 @@
|
||||
package com.artemchep.test.feature
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import androidx.test.uiautomator.Until
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@JvmInline
|
||||
value class FeatureCore(
|
||||
val device: UiDevice,
|
||||
)
|
||||
|
||||
val UiDevice.coreFeature get() = FeatureCore(this)
|
||||
|
||||
enum class RootScreen(
|
||||
val res: String,
|
||||
) {
|
||||
SETUP("setup"),
|
||||
UNLOCK("unlock"),
|
||||
MAIN("main");
|
||||
|
||||
companion object {
|
||||
const val RES_PREFIX = "nav:"
|
||||
}
|
||||
}
|
||||
|
||||
fun RootScreen.resourceName() = RootScreen.RES_PREFIX + res
|
||||
|
||||
fun FeatureCore.waitForRootScreen(
|
||||
vararg screens: RootScreen,
|
||||
): UiObject2? = kotlin.run {
|
||||
require(screens.isNotEmpty()) {
|
||||
"You must provide at least one screen to wait for!"
|
||||
}
|
||||
|
||||
val selector = kotlin.run {
|
||||
val variants = screens
|
||||
.joinToString(separator = "|") { it.res }
|
||||
val pattern = Pattern.compile("nav:($variants)")
|
||||
val selector = By.res(pattern)
|
||||
Until.findObject(selector)
|
||||
}
|
||||
device.wait(selector, 30_000)
|
||||
}
|
||||
|
||||
fun FeatureCore.ensureMainScreen(): UiObject2 = kotlin.run {
|
||||
val screen = waitForRootScreen(
|
||||
RootScreen.MAIN,
|
||||
RootScreen.UNLOCK,
|
||||
RootScreen.SETUP,
|
||||
)
|
||||
requireNotNull(screen) {
|
||||
"Could not find root screen! " +
|
||||
"Is the app really open?"
|
||||
}
|
||||
|
||||
when (screen.resourceName) {
|
||||
RootScreen.UNLOCK.resourceName(),
|
||||
RootScreen.SETUP.resourceName(),
|
||||
-> {
|
||||
// Create or unlock existing vault
|
||||
val password = screen
|
||||
.findObject(By.res("field:password"))
|
||||
password.text = "111111"
|
||||
val btn = screen
|
||||
.findObject(By.res("btn:go"))
|
||||
btn.wait(Until.clickable(true), 3000L)
|
||||
btn.click()
|
||||
|
||||
// Wait till main screen is loaded
|
||||
val mainScreen = waitForRootScreen(
|
||||
RootScreen.MAIN,
|
||||
)
|
||||
requireNotNull(mainScreen)
|
||||
}
|
||||
|
||||
else -> screen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the default activity of the app, waiting for
|
||||
* it to become visible.
|
||||
*/
|
||||
fun FeatureCore.launchDefaultActivityAndWait(
|
||||
packageName: String,
|
||||
) {
|
||||
// Launch the app
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
val intent = context.packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
|
||||
// Wait for the app to appear
|
||||
device.wait(
|
||||
Until.hasObject(By.pkg(packageName).depth(0)),
|
||||
10_000,
|
||||
)
|
||||
}
|
@ -36,8 +36,13 @@ androidxProfileInstaller = "1.4.1"
|
||||
androidxRoom = "2.6.1"
|
||||
androidxSecurityCryptoKtx = "1.1.0-alpha06"
|
||||
androidxTestEspresso = "3.6.1"
|
||||
androidxTestExtJUnit = "1.2.1"
|
||||
coreTestingVersion = "2.2.0"
|
||||
androidxTestCore = "1.6.1"
|
||||
androidxTestUiAutomator = "2.3.0"
|
||||
androidxTestRunner = "1.6.2"
|
||||
androidxTestRule = "1.6.1"
|
||||
androidxTestJUnit = "1.2.1"
|
||||
androidxTestOrchestrator = "1.5.1"
|
||||
androidxWork = "2.10.0"
|
||||
# https://github.com/harawata/appdirs
|
||||
appDirs = "1.2.2"
|
||||
@ -163,8 +168,6 @@ androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", versi
|
||||
androidx-core-shortcuts = { module = "androidx.core:core-google-shortcuts", version.ref = "androidxCoreShortcuts" }
|
||||
androidx-credentials = { module = "androidx.credentials:credentials", version.ref = "androidxCredentials" }
|
||||
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidxDatastore" }
|
||||
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxTestEspresso" }
|
||||
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJUnit" }
|
||||
androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidxLifecycle" }
|
||||
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" }
|
||||
@ -177,9 +180,20 @@ androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref =
|
||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidxRoom" }
|
||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom" }
|
||||
androidx-security-crypto-ktx = { module = "androidx.security:security-crypto-ktx", version.ref = "androidxSecurityCryptoKtx" }
|
||||
androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidxTestUiAutomator" }
|
||||
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "androidxWork" }
|
||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" }
|
||||
androidx-arch-core-testing = { module = "androidx.arch.core:core-testing", version.ref = "coreTestingVersion" }
|
||||
androidx-test-core = { module = "androidx.test:core", version.ref = "androidxTestCore" }
|
||||
androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidxTestCore" }
|
||||
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidxTestEspresso" }
|
||||
androidx-test-espresso-uiautomator = { module = "androidx.test.espresso:espresso-uiautomator", version.ref = "androidxTestEspresso" }
|
||||
androidx-test-espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "androidxTestEspresso" }
|
||||
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestJUnit" }
|
||||
androidx-test-ext-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidxTestJUnit" }
|
||||
androidx-test-orchestrator = { module = "androidx.test:orchestrator", version.ref = "androidxTestOrchestrator" }
|
||||
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidxTestRule" }
|
||||
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTestRunner" }
|
||||
androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "androidxTestUiAutomator" }
|
||||
arrow-arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
|
||||
arrow-arrow-optics = { module = "io.arrow-kt:arrow-optics", version.ref = "arrow" }
|
||||
arrow-arrow-optics-ksp-plugin = { module = "io.arrow-kt:arrow-optics-ksp-plugin", version.ref = "arrow" }
|
||||
|
@ -27,5 +27,6 @@ include ':common'
|
||||
// apps
|
||||
include ':androidApp'
|
||||
include ':androidBenchmark'
|
||||
include ':androidTest'
|
||||
include ':desktopApp'
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user