Skip to content

Commit

Permalink
Add e2e test
Browse files Browse the repository at this point in the history
  • Loading branch information
stevesoltys committed Sep 11, 2023
1 parent eaf4e6d commit 91a96e1
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 3 deletions.
6 changes: 3 additions & 3 deletions .idea/runConfigurations/app_emulator.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ dependencies {
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "io.mockk:mockk-android:$mockk_version"

androidTestImplementation 'com.kaspersky.android-components:kaspresso:1.5.3'
}

apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"
Expand Down Expand Up @@ -210,3 +212,15 @@ tasks.register('installEmulatorRelease', Exec) {
environment "JAVA_HOME", System.properties['java.home']
}
}


tasks.register('clearEmulatorAppData', Exec) {
group("emulator")

doFirst {
commandLine "${project.projectDir}/development/scripts/clear_app_data.sh"

environment "ANDROID_SDK_HOME", android.sdkDirectory.absolutePath
environment "JAVA_HOME", System.properties['java.home']
}
}
22 changes: 22 additions & 0 deletions app/development/scripts/clear_app_data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

# assert ANDROID_HOME is set
if [ -z "$ANDROID_SDK_HOME" ]; then
echo "ANDROID_SDK_HOME is not set"
exit 1
fi

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
DEVELOPMENT_DIR=$SCRIPT_DIR/..
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..

EMULATOR_DEVICE_NAME=$($ANDROID_SDK_HOME/platform-tools/adb devices | grep emulator | cut -f1)

if [ -z "$EMULATOR_DEVICE_NAME" ]; then
echo "Emulator device name not found"
exit 1
fi

ADB="$ANDROID_SDK_HOME/platform-tools/adb -s $EMULATOR_DEVICE_NAME"

$ADB shell pm clear com.stevesoltys.seedvault
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.stevesoltys.seedvault.e2e

import com.stevesoltys.seedvault.transport.backup.PackageService
import org.junit.Test
import org.koin.core.component.inject
import java.lang.Thread.sleep

class BackupRestoreTest : LargeTestBase() {

private val packageService: PackageService by inject()

@Test
fun `back up and restore applications`() = run {
launchBackupActivity()
verifyCode()
chooseBackupLocation()
waitUntilIdle()

val packagesBeforeRestore = packageService.userApps
.map { it.packageName }.toSet()

packagesBeforeRestore.forEach {
val intent = device.targetContext.packageManager.getLaunchIntentForPackage(it)

device.targetContext.startActivity(intent)
waitUntilIdle()
}

runBackup()
sleep(60000)

packagesBeforeRestore.forEach { runCommand("pm uninstall $it") }

launchRestoreActivity()
sleep(60000)

val packagesAfterRestore = packageService.userApps
.map { it.packageName }.toSet()

assert(packagesBeforeRestore == packagesAfterRestore)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.stevesoltys.seedvault.e2e

import android.view.KeyEvent.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.stevesoltys.seedvault.e2e.screen.BackupScreen
import com.stevesoltys.seedvault.e2e.screen.DocumentPickerScreen
import com.stevesoltys.seedvault.e2e.screen.RecoveryCodeScreen
import org.junit.Before
import org.junit.runner.RunWith
import org.koin.core.component.KoinComponent
import java.lang.Thread.sleep

@LargeTest
@RunWith(AndroidJUnit4::class)
abstract class LargeTestBase : TestCase(), KoinComponent {

@Before
fun setUp() {
// reset document picker state, and delete old backups
runCommand("pm clear com.google.android.documentsui")
runCommand("rm -Rf /sdcard/seedvault")
}

protected fun launchBackupActivity() = run {
runCommand("am start -n com.stevesoltys.seedvault/.settings.SettingsActivity")
device.uiDevice.waitForIdle()
}

protected fun launchRestoreActivity() = run {
runCommand("am start -n com.stevesoltys.seedvault/.restore.RestoreActivity")
waitUntilIdle()
}

protected fun waitUntilIdle() {
device.uiDevice.waitForIdle()
sleep(3000)
}

protected fun verifyCode() = run {
RecoveryCodeScreen {
step("Confirm code") {
confirmCodeButton.isVisible()
confirmCodeButton.click()
}
step("Verify code") {
verifyCodeButton.isVisible()
verifyCodeButton.scrollTo()
verifyCodeButton.click()
}
}
}

protected fun chooseBackupLocation() = run {
DocumentPickerScreen {
step("Click new folder button") { createNewFolderButton.click() }

step("Create new folder") {
waitUntilIdle()
type("Seedvault")
device.uiDevice.pressEnter()
}

step("Click use this folder button") { useThisFolderButton.click() }

step("Click allow button") {
waitUntilIdle()
device.uiDevice.pressKeyCode(KEYCODE_TAB)
device.uiDevice.pressKeyCode(KEYCODE_TAB)
device.uiDevice.pressEnter()
}
}
}

protected fun runBackup() = run {
launchBackupActivity()

BackupScreen {
waitUntilIdle()
device.uiDevice.pressMenu()
waitUntilIdle()
backupNowButton.click()
}
}

protected fun runCommand(command: String) {
InstrumentationRegistry.getInstrumentation().uiAutomation
.executeShellCommand(command)
.close()
}

private fun type(text: String) = device.uiDevice.apply {
text.lowercase().toCharArray().map {
when (it) {
'a' -> KEYCODE_A
'b' -> KEYCODE_B
'c' -> KEYCODE_C
'd' -> KEYCODE_D
'e' -> KEYCODE_E
'f' -> KEYCODE_F
'g' -> KEYCODE_G
'h' -> KEYCODE_H
'i' -> KEYCODE_I
'j' -> KEYCODE_J
'k' -> KEYCODE_K
'l' -> KEYCODE_L
'm' -> KEYCODE_M
'n' -> KEYCODE_N
'o' -> KEYCODE_O
'p' -> KEYCODE_P
'q' -> KEYCODE_Q
'r' -> KEYCODE_R
's' -> KEYCODE_S
't' -> KEYCODE_T
'u' -> KEYCODE_U
'v' -> KEYCODE_V
'w' -> KEYCODE_W
'x' -> KEYCODE_X
'y' -> KEYCODE_Y
'z' -> KEYCODE_Z
else -> throw IllegalArgumentException("Unsupported character: $it")
}
}.forEach {
device.uiDevice.pressKeyCode(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.stevesoltys.seedvault.e2e.screen

import com.kaspersky.kaspresso.screens.KScreen
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsFragment
import io.github.kakaocup.kakao.text.KButton

object BackupScreen : KScreen<BackupScreen>() {

override val layoutId: Int? = null
override val viewClass: Class<*>? = null

val backupNowButton = KButton { withText("Backup now") }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.stevesoltys.seedvault.e2e.screen

import com.kaspersky.components.kautomator.component.text.UiButton
import com.kaspersky.components.kautomator.component.text.UiTextView
import com.kaspersky.components.kautomator.screen.UiScreen

private const val PACKAGE_NAME = "com.android.documentsui.picker"

object DocumentPickerScreen : UiScreen<DocumentPickerScreen>() {

override val packageName = PACKAGE_NAME

val createNewFolderButton = UiTextView { textStartsWith("CREATE NEW FOLDER") }

val useThisFolderButton = UiButton { withText("USE THIS FOLDER") }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.stevesoltys.seedvault.e2e.screen

import com.kaspersky.kaspresso.screens.KScreen
import com.stevesoltys.seedvault.R
import io.github.kakaocup.kakao.text.KButton

object RecoveryCodeScreen : KScreen<RecoveryCodeScreen>() {

override val layoutId: Int? = null
override val viewClass: Class<*>? = null

val confirmCodeButton = KButton { withId(R.id.confirmCodeButton) }

val verifyCodeButton = KButton { withId(R.id.doneButton) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.stevesoltys.seedvault.e2e.screen

import com.kaspersky.kaspresso.screens.KScreen
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsFragment
import io.github.kakaocup.kakao.text.KButton

object RestoreScreen : KScreen<RestoreScreen>() {

override val layoutId: Int? = null
override val viewClass: Class<*>? = null

val backupNowButton = KButton { withId(R.id.action_restore) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.stevesoltys.seedvault.e2e.screen

import com.kaspersky.components.kautomator.component.text.UiTextView
import com.kaspersky.components.kautomator.screen.UiScreen
import com.kaspersky.kaspresso.device.Device

private const val PACKAGE_NAME = "com.android.settings"

object SettingsScreen : UiScreen<SettingsScreen>() {

override val packageName: String = PACKAGE_NAME

val backupButton = UiTextView {
textStartsWith("Backup")
}
}

0 comments on commit 91a96e1

Please sign in to comment.