Fix and improve E2E tests
diff --git a/.github/scripts/run_tests.sh b/.github/scripts/run_tests.sh
new file mode 100755
index 0000000..48a8657
--- /dev/null
+++ b/.github/scripts/run_tests.sh
@@ -0,0 +1,35 @@
+adb root
+sleep 5
+adb remount
+
+echo "Installing Seedvault app..."
+adb shell mkdir -p /system/priv-app/Seedvault
+adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk
+
+echo "Installing Seedvault permissions..."
+adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml
+adb push allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml
+
+echo "Setting Seedvault transport..."
+sleep 10
+adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport
+
+large_test_exit_code=0
+./gradlew --stacktrace -Pinstrumented_test_size=large :app:connectedAndroidTest || large_test_exit_code=$?
+
+adb pull /sdcard/seedvault_test_results
+
+if [ "$large_test_exit_code" -ne 0 ]; then
+ echo 'Large tests failed.'
+ exit 1
+fi
+
+medium_test_exit_code=0
+./gradlew --stacktrace -Pinstrumented_test_size=medium :app:connectedAndroidTest || medium_test_exit_code=$?
+
+if [ "$medium_test_exit_code" -ne 0 ]; then
+ echo 'Medium tests failed.'
+ exit 1
+fi
+
+exit 0
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 82ca5c4..52dd750 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,10 @@
name: Build
on: [push, pull_request]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
jobs:
build:
name: Build
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 925a65b..a624c77 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,14 +13,13 @@
jobs:
instrumentation_tests:
runs-on: macos-11
+ if: github.repository == 'seedvault-app/seedvault'
+ timeout-minutes: 80
strategy:
fail-fast: false
matrix:
- android_target: [33, 34]
- emulator_type: [default, google_apis]
- exclude:
- - android_target: 34
- emulator_type: default
+ android_target: [ 33, 34 ]
+ emulator_type: [ default ]
steps:
- name: Checkout Code
uses: actions/checkout@v3
@@ -31,88 +30,33 @@
java-version: '17'
cache: 'gradle'
- - name: AVD cache
- uses: actions/cache@v3
- id: avd-cache
- with:
- path: |
- ~/.android/avd/*
- ~/.android/adb*
- key: aosp-${{ matrix.emulator_type }}-${{ matrix.android_target }}-${{ runner.os }}
-
- name: Build Release APK
run: ./gradlew :app:assembleRelease
- - name: Create AVD snapshot
- if: steps.avd-cache.outputs.cache-hit != 'true'
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: ${{ matrix.android_target }}
- target: ${{ matrix.emulator_type }}
- arch: x86_64
- force-avd-creation: false
- emulator-options: -writable-system -no-snapshot-load -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- disable-animations: true
- script: |
- ./app/development/scripts/provision_emulator.sh "test" "system-images;android-${{ matrix.android_target }};${{ matrix.emulator_type }};x86_64"
- echo "Generated AVD snapshot for caching."
-
- name: Assemble tests
run: ./gradlew :app:assembleAndroidTest
- name: Run tests
- uses: reactivecircus/android-emulator-runner@v2
+ uses: Wandalen/wretry.action@v1.3.0
with:
- api-level: ${{ matrix.android_target }}
- target: ${{ matrix.emulator_type }}
- arch: x86_64
- force-avd-creation: false
- emulator-options: -writable-system -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- profile: pixel_6a
- heap-size: '512M'
- ram-size: '4096M'
- disk-size: '14G'
- sdcard-path-or-size: '4096M'
- cores: 3
- disable-animations: false
- script: |
- adb root
- sleep 5
- adb remount
+ attempt_limit: 3
+ action: reactivecircus/android-emulator-runner@v2
+ with: |
+ api-level: ${{ matrix.android_target }}
+ target: ${{ matrix.emulator_type }}
+ arch: x86_64
+ force-avd-creation: true
+ emulator-options: -cores 2 -writable-system -no-snapshot-load -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disk-size: '14G'
+ sdcard-path-or-size: '4096M'
+ disable-animations: true
+ script: |
+ ./app/development/scripts/provision_emulator.sh "test" "system-images;android-${{ matrix.android_target }};${{ matrix.emulator_type }};x86_64"
+ ./.github/scripts/run_tests.sh
- echo "Installing Seedvault app..."
- adb shell mkdir -p /system/priv-app/Seedvault
- adb push app/build/outputs/apk/release/app-release.apk /system/priv-app/Seedvault/Seedvault.apk
-
- echo "Installing Seedvault permissions..."
- adb push permissions_com.stevesoltys.seedvault.xml /system/etc/permissions/privapp-permissions-seedvault.xml
- adb push allowlist_com.stevesoltys.seedvault.xml /system/etc/sysconfig/allowlist-seedvault.xml
-
- echo "Setting Seedvault transport..."
- sleep 10
- adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport
-
- wget --quiet https://github.com/seedvault-app/seedvault-test-data/releases/download/1/backup.tar.gz
- adb shell mkdir -p /sdcard/seedvault_baseline
- adb push backup.tar.gz /sdcard/seedvault_baseline
- adb wait-for-device
- adb shell tar xzf /sdcard/seedvault_baseline/backup.tar.gz --directory=/sdcard/seedvault_baseline
- adb shell rm /sdcard/seedvault_baseline/backup.tar.gz
-
- large_test_exit_code=0
- ./gradlew --stacktrace -Pinstrumented_test_size=large :app:connectedAndroidTest || large_test_exit_code=$?
-
- medium_test_exit_code=0
- ./gradlew --stacktrace -Pinstrumented_test_size=medium :app:connectedAndroidTest || medium_test_exit_code=$?
-
- adb pull /sdcard/seedvault_test_videos
-
- if [ $large_test_exit_code -ne 0 ]; then echo 'Gradle test failed.'; exit 0; fi
- if [ $medium_test_exit_code -ne 0 ]; then echo 'Gradle test failed.'; exit 0; fi
-
- - name: Upload screenshots and videos
+ - name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
- name: seedvault_test_videos
- path: seedvault_test_videos/**/*.mp4
+ name: ${{ matrix.emulator_type }}-${{ matrix.android_target }}-results
+ path: seedvault_test_results/**/*
diff --git a/app/build.gradle b/app/build.gradle
index 56508f3..fcb46e4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -162,7 +162,7 @@
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation "io.mockk:mockk-android:$mockk_version"
+ androidTestImplementation "io.mockk:mockk-android:1.13.8"
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
@@ -200,7 +200,7 @@
doFirst {
commandLine "${project.projectDir}/development/scripts/provision_emulator.sh",
"seedvault",
- "system-images;android-34;google_apis;x86_64"
+ "system-images;android-34;default;x86_64"
environment "ANDROID_HOME", android.sdkDirectory.absolutePath
environment "JAVA_HOME", System.properties['java.home']
diff --git a/app/development/scripts/provision_emulator.sh b/app/development/scripts/provision_emulator.sh
index cfdcc88..fef04b8 100755
--- a/app/development/scripts/provision_emulator.sh
+++ b/app/development/scripts/provision_emulator.sh
@@ -20,7 +20,7 @@
ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..
echo "Downloading system image..."
-$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "$SYSTEM_IMAGE"
+yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "$SYSTEM_IMAGE"
# create AVD if it doesn't exist
if $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager list avd | grep -q "$EMULATOR_NAME"; then
diff --git a/app/development/scripts/start_emulator.sh b/app/development/scripts/start_emulator.sh
index 4c07118..b211348 100755
--- a/app/development/scripts/start_emulator.sh
+++ b/app/development/scripts/start_emulator.sh
@@ -14,9 +14,5 @@
EMULATOR_NAME=$1
-SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
-DEVELOPMENT_DIR=$SCRIPT_DIR/..
-ROOT_PROJECT_DIR=$SCRIPT_DIR/../../..
-
echo "Starting emulator..."
nohup $ANDROID_HOME/emulator/emulator -avd "$EMULATOR_NAME" -gpu swiftshader_indirect -writable-system >/dev/null 2>&1 &
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 b386214..86f14a2 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/LargeTestBase.kt
@@ -44,7 +44,7 @@
companion object {
private const val TEST_STORAGE_FOLDER = "seedvault_test"
- private const val TEST_VIDEO_FOLDER = "seedvault_test_videos"
+ private const val TEST_VIDEO_FOLDER = "seedvault_test_results"
}
val externalStorageDir: String get() = Environment.getExternalStorageDirectory().absolutePath
@@ -106,19 +106,23 @@
uiAutomation.executeShellCommand(command).close()
}
+ fun testResultFilename(testName: String): String {
+ val simpleDateFormat = SimpleDateFormat("yyyyMMdd_hhmmss")
+ val timeStamp = simpleDateFormat.format(Calendar.getInstance().time)
+ return "${timeStamp}_${testName.replace(" ", "_")}"
+ }
+
@OptIn(DelicateCoroutinesApi::class)
@WorkerThread
- suspend fun startScreenRecord(
+ suspend fun startRecordingTest(
keepRecordingScreen: AtomicBoolean,
testName: String,
) {
- val simpleDateFormat = SimpleDateFormat("yyyyMMdd_hhmmss")
- val timeStamp = simpleDateFormat.format(Calendar.getInstance().time)
- val fileName = "${timeStamp}_${testName.replace(" ", "_")}"
-
val folder = testVideoPath
runCommand("mkdir -p $folder")
+ val fileName = testResultFilename(testName)
+
// screen record automatically stops after 3 minutes
// we need to block on a loop and split it into multiple files
GlobalScope.launch(Dispatchers.IO) {
@@ -131,10 +135,16 @@
}
@WorkerThread
- fun stopScreenRecord(keepRecordingScreen: AtomicBoolean) {
+ fun stopRecordingTest(
+ keepRecordingScreen: AtomicBoolean,
+ testName: String,
+ ) {
keepRecordingScreen.set(false)
-
runCommand("pkill -2 screenrecord")
+
+ // write logcat to file
+ val fileName = testResultFilename(testName)
+ runCommand("logcat -d -f $testVideoPath/$fileName.log")
}
fun uninstallPackages(packages: Collection<PackageInfo>) {
diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt
index 423c461..2d2be5f 100644
--- a/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt
+++ b/app/src/androidTest/java/com/stevesoltys/seedvault/e2e/SeedvaultLargeTest.kt
@@ -38,13 +38,13 @@
resetApplicationState()
clearTestBackups()
- startScreenRecord(keepRecordingScreen, name.methodName)
+ startRecordingTest(keepRecordingScreen, name.methodName)
restoreBaselineBackup()
}
@After
open fun tearDown() {
- stopScreenRecord(keepRecordingScreen)
+ stopRecordingTest(keepRecordingScreen, name.methodName)
}
/**
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 fdcdd67..bc13414 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
@@ -11,6 +11,10 @@
abstract class UiDeviceScreen<T> {
+ companion object {
+ private const val SELECTOR_TIMEOUT = 180000L
+ }
+
operator fun invoke(function: T.() -> Unit) {
function.invoke(this as T)
}
@@ -18,8 +22,11 @@
fun UiObject.scrollTo(
scrollSelector: UiSelector = UiSelector().className(ScrollView::class.java),
): UiObject {
- UiScrollable(scrollSelector).scrollIntoView(this)
- waitForExists(15000)
+ val uiScrollable = UiScrollable(scrollSelector)
+ uiScrollable.waitForExists(SELECTOR_TIMEOUT)
+ uiScrollable.scrollIntoView(this)
+ waitForExists(SELECTOR_TIMEOUT)
+
sleep(2000)
return this
}
@@ -32,6 +39,6 @@
private fun device() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.also {
- Configurator.getInstance().waitForSelectorTimeout = 60000
+ Configurator.getInstance().waitForSelectorTimeout = SELECTOR_TIMEOUT
}
}