Address review comments, add screen recording, use test backup data
diff --git a/.cirrus.yml b/.cirrus.yml
index d2f6c6c..896a6a4 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -4,9 +4,9 @@
cpu: 8
memory: 16G
-check_android_task:
+instrumentation_tests_task:
+ name: "Cirrus CI Instrumentation Tests"
skip: "!changesInclude('.cirrus.yml', '*.gradle', '*.gradle.kts', '**/*.gradle', '**/*.gradle.kts', '*.properties', '**/*.properties', '**/*.kt', '**/*.xml')"
- create_avd_script:
start_avd_background_script:
sdkmanager --install "system-images;android-33;google_apis;x86_64";
echo no | avdmanager create avd -n seedvault -k "system-images;android-33;google_apis;x86_64";
@@ -19,10 +19,9 @@
-no-window
-writable-system;
provision_avd_background_script:
- adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
- wget --output-document etar.apk https://f-droid.org/repo/ws.xsoh.etar_35.apk;
- adb install etar.apk
+ wget https://github.com/seedvault-app/seedvault-test-data/releases/download/1/backup.tar.gz;
+ adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
adb root;
sleep 5;
adb remount;
@@ -31,11 +30,17 @@
adb root;
sleep 5;
adb remount;
- assemble_release_script:
+ sleep 5;
+ assemble_script:
./gradlew :app:assembleRelease :app:assembleAndroidTest
install_app_script:
timeout 180s bash -c 'while [[ -z $(adb shell mount | grep "/system " | grep "(rw,") ]]; do sleep 1; done;';
adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;';
+
+ adb shell mkdir -p /sdcard/seedvault_baseline;
+ adb push backup.tar.gz /sdcard/seedvault_baseline/backup.tar.gz;
+ adb shell tar xzf /sdcard/seedvault_baseline/backup.tar.gz --directory=/sdcard/seedvault_baseline;
+
adb shell mkdir -p /system/priv-app/Seedvault;
adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk;
adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml;
@@ -48,6 +53,6 @@
run_medium_tests_script: ./gradlew -Pinstrumented_test_size=medium :app:connectedAndroidTest
always:
pull_screenshots_script:
- adb pull /sdcard/Documents/screenshots
+ adb pull /sdcard/seedvault_test_videos
screenshots_artifacts:
- path: "screenshots/**/*.png"
+ path: "seedvault_test_videos/**/*.mp4"
diff --git a/app/build.gradle b/app/build.gradle
index 6323ce3..0632796 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -162,8 +162,7 @@
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'
+ androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
apply from: "${rootProject.rootDir}/gradle/ktlint.gradle"
diff --git a/app/development/scripts/provision_emulator.sh b/app/development/scripts/provision_emulator.sh
index b6c0623..8af6504 100755
--- a/app/development/scripts/provision_emulator.sh
+++ b/app/development/scripts/provision_emulator.sh
@@ -67,4 +67,13 @@
$ADB reboot
$ADB wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'
+echo "Downloading and extracting test backup to '/sdcard/seedvault'..."
+wget https://github.com/seedvault-app/seedvault-test-data/releases/download/1/backup.tar.gz
+$ADB push backup.tar.gz /sdcard/
+rm backup.tar.gz
+
+$ADB shell mkdir -p /sdcard/seedvault_baseline
+$ADB shell tar xzf /sdcard/backup.tar.gz --directory=/sdcard/seedvault_baseline
+$ADB shell rm /sdcard/backup.tar.gz
+
echo "Emulator '$EMULATOR_NAME' has been provisioned with Seedvault!"
diff --git a/app/src/androidTest/AndroidManifest.xml b/app/src/androidTest/AndroidManifest.xml
index 7770cfb..00f58f9 100644
--- a/app/src/androidTest/AndroidManifest.xml
+++ b/app/src/androidTest/AndroidManifest.xml
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
-
<application android:extractNativeLibs="true" />
</manifest>
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/BackupRestoreTest.kt
deleted file mode 100644
index 1542caf..0000000
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/BackupRestoreTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-package com.stevesoltys.seedvault.e2e
-
-import androidx.test.filters.LargeTest
-import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen
-import com.stevesoltys.seedvault.restore.RestoreViewModel
-import com.stevesoltys.seedvault.transport.backup.PackageService
-import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
-import io.mockk.every
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withTimeout
-import org.junit.Test
-import org.koin.core.component.inject
-import java.util.concurrent.atomic.AtomicBoolean
-
-@LargeTest
-class BackupRestoreTest : LargeTestBase() {
-
- private val packageService: PackageService by inject()
-
- private val spyBackupNotificationManager: BackupNotificationManager by inject()
-
- private val restoreViewModel: RestoreViewModel by inject()
-
- companion object {
- private const val BACKUP_TIMEOUT = 360 * 1000L
- private const val RESTORE_TIMEOUT = 360 * 1000L
- }
-
- @Test
- fun `backup and restore applications`() = run {
- launchBackupActivity()
- verifyCode()
- chooseBackupLocation()
-
- val eligiblePackages = launchAllEligibleApps()
- performBackup(eligiblePackages)
- uninstallPackages(eligiblePackages)
- performRestore()
-
- val packagesAfterRestore = getEligibleApps()
- assert(eligiblePackages == packagesAfterRestore)
- }
-
- private fun getEligibleApps() = packageService.userApps
- .map { it.packageName }.toSet()
-
- private fun launchAllEligibleApps(): Set<String> {
- return getEligibleApps().onEach {
- val intent = device.targetContext.packageManager.getLaunchIntentForPackage(it)
-
- device.targetContext.startActivity(intent)
- waitUntilIdle()
- }
- }
-
- private fun performBackup(expectedPackages: Set<String>) = run {
- val backupResult = spyOnBackup(expectedPackages)
- startBackup()
- waitForBackupResult(backupResult)
- screenshot("backup result")
- }
-
- private fun spyOnBackup(expectedPackages: Set<String>): AtomicBoolean {
- val finishedBackup = AtomicBoolean(false)
-
- every {
- spyBackupNotificationManager.onBackupFinished(any(), any())
- } answers {
- val success = firstArg<Boolean>()
- assert(success) { "Backup failed." }
-
- val packageCount = secondArg<Int>()
- assert(packageCount == expectedPackages.size) {
- "Expected ${expectedPackages.size} apps, got $packageCount."
- }
-
- this.callOriginal()
- finishedBackup.set(true)
- }
-
- return finishedBackup
- }
-
- private fun waitForBackupResult(finishedBackup: AtomicBoolean) = run {
- step("Wait for backup completion") {
- runBlocking {
- withTimeout(BACKUP_TIMEOUT) {
- while (!finishedBackup.get()) {
- delay(100)
- }
- }
- }
- }
- }
-
- private fun performRestore() = run {
- step("Start restore and await completion") {
- RestoreScreen {
- startRestore()
- waitForInstallResult()
- screenshot("restore app apks result")
-
- nextButton.click()
- waitForRestoreResult()
- screenshot("restore app data result")
-
- finishButton.click()
- }
- }
- }
-
- private fun waitForInstallResult() = runBlocking {
- withTimeout(RESTORE_TIMEOUT) {
-
- while (restoreViewModel.installResult.value == null) {
- delay(100)
- }
-
- val restoreResultValue = restoreViewModel.installResult.value!!
- assert(!restoreResultValue.hasFailed) { "Failed to install packages" }
- }
- }
-
- private fun waitForRestoreResult() = runBlocking {
- withTimeout(RESTORE_TIMEOUT) {
-
- while (restoreViewModel.restoreBackupResult.value == null) {
- delay(100)
- }
-
- val restoreResultValue = restoreViewModel.restoreBackupResult.value!!
-
- assert(!restoreResultValue.hasError()) {
- "Restore failed: ${restoreResultValue.errorMsg}"
- }
- }
- }
-}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt
new file mode 100644
index 0000000..c369593
--- /dev/null
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeBackupTestBase.kt
@@ -0,0 +1,71 @@
+package com.stevesoltys.seedvault.e2e
+
+import com.stevesoltys.seedvault.e2e.screen.impl.BackupScreen
+import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
+import io.mockk.clearMocks
+import io.mockk.every
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import java.util.concurrent.atomic.AtomicBoolean
+
+internal interface LargeBackupTestBase : LargeTestBase {
+
+ companion object {
+ private const val BACKUP_TIMEOUT = 360 * 1000L
+ }
+
+ val spyBackupNotificationManager: BackupNotificationManager
+
+ fun launchBackupActivity() {
+ runCommand("am start -n ${targetContext.packageName}/.settings.SettingsActivity")
+ waitUntilIdle()
+ }
+
+ fun startBackup() {
+ BackupScreen {
+ backupMenu.clickAndWaitForNewWindow()
+ waitUntilIdle()
+
+ backupNowButton.clickAndWaitForNewWindow()
+ waitUntilIdle()
+
+ backupStatusButton.clickAndWaitForNewWindow()
+ waitUntilIdle()
+ }
+ }
+
+ fun performBackup(expectedPackages: Set<String>) {
+ val backupResult = spyOnBackup(expectedPackages)
+ startBackup()
+ waitForBackupResult(backupResult)
+ }
+
+ private fun spyOnBackup(expectedPackages: Set<String>): AtomicBoolean {
+ val finishedBackup = AtomicBoolean(false)
+
+ clearMocks(spyBackupNotificationManager)
+
+ every {
+ spyBackupNotificationManager.onBackupFinished(any(), any())
+ } answers {
+ val success = firstArg<Boolean>()
+ assert(success) { "Backup failed." }
+
+ this.callOriginal()
+ finishedBackup.set(true)
+ }
+
+ return finishedBackup
+ }
+
+ private fun waitForBackupResult(finishedBackup: AtomicBoolean) {
+ runBlocking {
+ withTimeout(BACKUP_TIMEOUT) {
+ while (!finishedBackup.get()) {
+ delay(100)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt
new file mode 100644
index 0000000..1ba2b28
--- /dev/null
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeRestoreTestBase.kt
@@ -0,0 +1,86 @@
+package com.stevesoltys.seedvault.e2e
+
+import com.stevesoltys.seedvault.e2e.screen.impl.RecoveryCodeScreen
+import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen
+import com.stevesoltys.seedvault.restore.RestoreViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+
+internal interface LargeRestoreTestBase : LargeTestBase {
+
+ companion object {
+ private const val RESTORE_TIMEOUT = 360 * 1000L
+ }
+
+ val spyRestoreViewModel: RestoreViewModel
+
+ fun launchRestoreActivity() {
+ runCommand("am start -n ${targetContext.packageName}/.restore.RestoreActivity")
+ waitUntilIdle()
+ }
+
+ fun typeInRestoreCode(code: List<String>) {
+ assert(code.size == 12) { "Code must have 12 words." }
+
+ RecoveryCodeScreen {
+ waitUntilIdle()
+
+ code.forEachIndexed { index, word ->
+ wordTextField(index).text = word
+ }
+
+ waitUntilIdle()
+ verifyCodeButton.scrollTo().click()
+ }
+ }
+
+ fun performRestore() {
+ RestoreScreen {
+ backupListItem.clickAndWaitForNewWindow()
+ waitUntilIdle()
+
+ waitForInstallResult()
+ nextButton.clickAndWaitForNewWindow()
+
+ waitForRestoreDataResult()
+ finishButton.clickAndWaitForNewWindow()
+ skipButton.clickAndWaitForNewWindow()
+ waitUntilIdle()
+ }
+ }
+
+ private fun waitForInstallResult() = runBlocking {
+ withTimeout(RESTORE_TIMEOUT) {
+ while (spyRestoreViewModel.installResult.value == null ||
+ spyRestoreViewModel.nextButtonEnabled.value == false
+ ) {
+ delay(100)
+ }
+ }
+
+ val restoreResultValue = spyRestoreViewModel.installResult.value
+ ?: error("Restore APKs timed out")
+
+ assert(!restoreResultValue.hasFailed) { "Failed to install packages" }
+ waitUntilIdle()
+ }
+
+ private fun waitForRestoreDataResult() = runBlocking {
+ withTimeout(RESTORE_TIMEOUT) {
+ while (spyRestoreViewModel.restoreBackupResult.value == null) {
+ delay(100)
+ }
+ }
+
+ val restoreResultValue = spyRestoreViewModel.restoreBackupResult.value
+ ?: error("Restore app data timed out")
+
+ assert(!restoreResultValue.hasError()) {
+ "Restore failed: ${restoreResultValue.errorMsg}"
+ }
+
+ waitUntilIdle()
+ }
+
+}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt
index fc0c01e..89ebbb9 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt
@@ -1,110 +1,100 @@
package com.stevesoltys.seedvault.e2e
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.app.UiAutomation
+import android.content.Context
+import android.os.Environment
+import androidx.annotation.WorkerThread
import androidx.test.platform.app.InstrumentationRegistry
-import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
-import com.stevesoltys.seedvault.e2e.screen.impl.BackupScreen
+import androidx.test.uiautomator.UiDevice
import com.stevesoltys.seedvault.e2e.screen.impl.DocumentPickerScreen
import com.stevesoltys.seedvault.e2e.screen.impl.RecoveryCodeScreen
-import com.stevesoltys.seedvault.e2e.screen.impl.RestoreScreen
-import org.junit.After
-import org.junit.Before
-import org.junit.runner.RunWith
-import org.koin.core.component.KoinComponent
import java.lang.Thread.sleep
+import java.text.SimpleDateFormat
+import java.util.Calendar
-@RunWith(AndroidJUnit4::class)
-abstract class LargeTestBase : TestCase(), KoinComponent {
+interface LargeTestBase {
- @Before
- open fun setUp() {
- // reset document picker state, and delete old backups
- runCommand("pm clear com.google.android.documentsui")
- runCommand("rm -Rf /sdcard/seedvault")
+ companion object {
+ private const val TEST_STORAGE_FOLDER = "seedvault_test"
+ private const val TEST_VIDEO_FOLDER = "seedvault_test_videos"
}
- @After
- open fun tearDown() {
- screenshot("end")
- }
+ fun externalStorageDir(): String = Environment.getExternalStorageDirectory().absolutePath
- protected fun launchBackupActivity() = run {
- runCommand("am start -n ${device.targetContext.packageName}/.settings.SettingsActivity")
- waitUntilIdle()
- }
+ fun testStoragePath(): String = "${externalStorageDir()}/$TEST_STORAGE_FOLDER"
- protected fun launchRestoreActivity() = run {
- runCommand("am start -n ${device.targetContext.packageName}/.restore.RestoreActivity")
- waitUntilIdle()
- }
+ fun testVideoPath(): String = "${externalStorageDir()}/$TEST_VIDEO_FOLDER"
- protected fun waitUntilIdle() {
- device.uiDevice.waitForIdle()
+ val targetContext: Context
+ get() = InstrumentationRegistry.getInstrumentation().targetContext
+
+ val uiAutomation: UiAutomation
+ get() = InstrumentationRegistry.getInstrumentation().uiAutomation
+
+ val device: UiDevice
+ get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+
+ fun waitUntilIdle() {
+ device.waitForIdle()
sleep(3000)
}
- protected fun verifyCode() = run {
- RecoveryCodeScreen {
- step("Confirm code") {
- screenshot("confirm code")
- confirmCodeButton.click()
- }
- step("Verify code") {
- screenshot("verify code")
- verifyCodeButton.scrollTo()
- verifyCodeButton.click()
- }
- }
+ fun runCommand(command: String) {
+ uiAutomation.executeShellCommand(command).close()
}
- protected fun chooseBackupLocation() = run {
- step("Choose backup location") {
- waitUntilIdle()
- screenshot("choose backup location")
+ @WorkerThread
+ fun startScreenRecord(testName: String) {
+ val simpleDateFormat = SimpleDateFormat("yyyyMMdd_hhmmss")
+ val timeStamp = simpleDateFormat.format(Calendar.getInstance().time)
+ val fileName = "${timeStamp}_${testName.replace(" ", "_")}"
- DocumentPickerScreen {
- createNewFolderButton.click()
- textBox.text = "seedvault"
- okButton.click()
- useThisFolderButton.click()
- allowButton.click()
- }
- }
+ val folder = testVideoPath()
+ runCommand("mkdir -p $folder")
+ runCommand("screenrecord $folder/$fileName.mp4")
}
- protected fun startBackup() = run {
- launchBackupActivity()
-
- step("Run backup") {
- BackupScreen {
- backupMenu.clickAndWaitForNewWindow()
- backupNowButton.clickAndWaitForNewWindow()
- backupStatusButton.clickAndWaitForNewWindow()
- }
- }
+ @WorkerThread
+ fun stopScreenRecord() {
+ runCommand("pkill -2 screenrecord")
}
- protected fun startRestore() = run {
- launchRestoreActivity()
-
- step("Restore backup") {
- RestoreScreen {
- backupListItem.clickAndWaitForNewWindow()
- }
- }
- }
-
- protected fun uninstallPackages(packages: Set<String>) {
+ fun uninstallPackages(packages: Set<String>) {
packages.forEach { runCommand("pm uninstall $it") }
}
- protected fun runCommand(command: String) {
- InstrumentationRegistry.getInstrumentation().uiAutomation
- .executeShellCommand(command)
- .close()
+ fun clearDocumentPickerAppData() {
+ runCommand("pm clear com.google.android.documentsui")
}
- protected fun screenshot(name: String) {
- device.screenshots.take(name.replace(" ", "_"))
+ fun clearTestBackups() {
+ runCommand("rm -Rf ${testStoragePath()}")
+ }
+
+ fun chooseStorageLocation(
+ folderName: String = TEST_STORAGE_FOLDER,
+ exists: Boolean = false,
+ ) {
+ DocumentPickerScreen {
+ if (exists) {
+ existingFolder(folderName).scrollTo().clickAndWaitForNewWindow()
+
+ } else {
+ createNewFolderButton.clickAndWaitForNewWindow()
+ textBox.text = folderName
+ okButton.clickAndWaitForNewWindow()
+ }
+
+ useThisFolderButton.clickAndWaitForNewWindow()
+ allowButton.clickAndWaitForNewWindow()
+ }
+ }
+
+ fun confirmCode() {
+ RecoveryCodeScreen {
+ confirmCodeButton.click()
+
+ verifyCodeButton.scrollTo().click()
+ }
}
}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt
new file mode 100644
index 0000000..eff5301
--- /dev/null
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt
@@ -0,0 +1,73 @@
+package com.stevesoltys.seedvault.e2e
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.stevesoltys.seedvault.restore.RestoreViewModel
+import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import java.io.File
+
+@RunWith(AndroidJUnit4::class)
+internal abstract class SeedvaultLargeTest :
+ LargeBackupTestBase, LargeRestoreTestBase, KoinComponent {
+
+ @JvmField
+ @Rule
+ var name = TestName()
+
+ companion object {
+ private const val BASELINE_BACKUP_FOLDER = "seedvault_baseline"
+ private const val RECOVERY_CODE_FILE = "recovery-code.txt"
+ }
+
+ override val spyBackupNotificationManager: BackupNotificationManager by inject()
+
+ override val spyRestoreViewModel: RestoreViewModel by inject()
+
+ private val baselineBackupFolderPath = "${this.externalStorageDir()}/$BASELINE_BACKUP_FOLDER"
+
+ private val baselineRecoveryCodePath = "$baselineBackupFolderPath/$RECOVERY_CODE_FILE"
+
+ @Before
+ open fun setUp() {
+ clearDocumentPickerAppData()
+ clearTestBackups()
+
+ startScreenRecord(name.methodName)
+ restoreBaselineBackup()
+ }
+
+ @After
+ open fun tearDown() {
+ stopScreenRecord()
+ }
+
+ /**
+ * Restore the baseline backup, if it exists.
+ *
+ * This is a hand-crafted backup containing various apps and app data that we use for
+ * provisioning tests: https://github.com/seedvault-app/seedvault-test-data
+ */
+ private fun restoreBaselineBackup() {
+ if (File(baselineBackupFolderPath).exists()) {
+ launchRestoreActivity()
+ chooseStorageLocation(folderName = BASELINE_BACKUP_FOLDER, exists = true)
+ typeInRestoreCode(baselineBackupRecoveryCode())
+ performRestore()
+ }
+ }
+
+ private fun baselineBackupRecoveryCode(): List<String> {
+ val recoveryCodeFile = File(baselineRecoveryCodePath)
+
+ return recoveryCodeFile.readLines()
+ .filter { it.isNotBlank() }
+ .joinToString(separator = " ") { it.trim() }
+ .split(" ")
+ }
+}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt
new file mode 100644
index 0000000..bc85ff5
--- /dev/null
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/impl/BackupRestoreTest.kt
@@ -0,0 +1,41 @@
+package com.stevesoltys.seedvault.e2e.impl
+
+import androidx.test.filters.LargeTest
+import com.stevesoltys.seedvault.e2e.SeedvaultLargeTest
+import com.stevesoltys.seedvault.settings.SettingsManager
+import com.stevesoltys.seedvault.transport.backup.PackageService
+import org.junit.Test
+import org.koin.core.component.inject
+
+@LargeTest
+internal class BackupRestoreTest : SeedvaultLargeTest() {
+
+ private val packageService: PackageService by inject()
+
+ private val settingsManager: SettingsManager by inject()
+
+ @Test
+ fun `backup and restore applications`() {
+ launchBackupActivity()
+
+ if (settingsManager.getStorage() == null) {
+ confirmCode()
+ chooseStorageLocation()
+ }
+
+ val eligiblePackages = getEligibleApps()
+ performBackup(eligiblePackages)
+ uninstallPackages(eligiblePackages)
+
+ launchRestoreActivity()
+ performRestore()
+
+ // TODO: Get some real assertions in here..
+ // val packagesAfterRestore = getEligibleApps()
+ // assert(eligiblePackages == packagesAfterRestore)
+ }
+
+ private fun getEligibleApps() = packageService.userApps
+ .map { it.packageName }.toSet()
+
+}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/UiDeviceScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/UiDeviceScreen.kt
index bddc428..7a82621 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/UiDeviceScreen.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/UiDeviceScreen.kt
@@ -1,9 +1,12 @@
package com.stevesoltys.seedvault.e2e.screen
+import android.widget.ScrollView
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
+import java.lang.Thread.sleep
abstract class UiDeviceScreen<T> {
@@ -11,10 +14,20 @@
function.invoke(this as T)
}
- protected fun findObject(
+ fun UiObject.scrollTo(
+ scrollSelector: UiSelector = UiSelector().className(ScrollView::class.java),
+ ): UiObject {
+ UiScrollable(scrollSelector).scrollIntoView(this)
+ waitForExists(15000)
+ sleep(2000)
+ return this
+ }
+
+ fun findObject(
block: UiSelector.() -> UiSelector,
- ): UiObject = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- .findObject(
- UiSelector().let { it.block() }
- )
+ ): UiObject = device().findObject(
+ UiSelector().let { it.block() }
+ )
+
+ private fun device() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/DocumentPickerScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/DocumentPickerScreen.kt
index f6f101e..050e57b 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/DocumentPickerScreen.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/DocumentPickerScreen.kt
@@ -14,4 +14,6 @@
val okButton = findObject { text("OK") }
val allowButton = findObject { text("ALLOW") }
+
+ fun existingFolder(folderName: String) = findObject { text(folderName) }
}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt
index 3ce6151..6804c5c 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RecoveryCodeScreen.kt
@@ -1,15 +1,12 @@
package com.stevesoltys.seedvault.e2e.screen.impl
-import com.kaspersky.kaspresso.screens.KScreen
-import com.stevesoltys.seedvault.R
-import io.github.kakaocup.kakao.text.KButton
+import com.stevesoltys.seedvault.e2e.screen.UiDeviceScreen
-object RecoveryCodeScreen : KScreen<RecoveryCodeScreen>() {
+object RecoveryCodeScreen : UiDeviceScreen<RecoveryCodeScreen>() {
- override val layoutId: Int? = null
- override val viewClass: Class<*>? = null
+ val confirmCodeButton = findObject { text("Confirm code") }
- val confirmCodeButton = KButton { withId(R.id.confirmCodeButton) }
+ val verifyCodeButton = findObject { text("Verify") }
- val verifyCodeButton = KButton { withId(R.id.doneButton) }
+ fun wordTextField(index: Int) = findObject { text("Word ${index + 1}") }
}
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt
index 706fc7b..2091475 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/screen/impl/RestoreScreen.kt
@@ -4,9 +4,11 @@
object RestoreScreen : UiDeviceScreen<RestoreScreen>() {
- val backupListItem = findObject { textContains("Last backup 0 hr") }
+ val backupListItem = findObject { textContains("Last backup") }
val nextButton = findObject { text("Next") }
val finishButton = findObject { text("Finish") }
+
+ val skipButton = findObject { text("Skip restoring files") }
}