diff --git a/.github/workflows/pull-request-jobs.yml b/.github/workflows/pull-request-jobs.yml index c226468..0993b4a 100644 --- a/.github/workflows/pull-request-jobs.yml +++ b/.github/workflows/pull-request-jobs.yml @@ -74,60 +74,60 @@ jobs: path: ./**/build/reports/tests/**/index.html retention-days: 1 - run-tests-on-emulator: - runs-on: macos-latest - strategy: - matrix: - api-level: [ 21, 30 ] - fail-fast: false - steps: - - name: checkout - uses: actions/checkout@v3 - - name: Setup Java - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '11' - - name: Gradle cache - uses: gradle/gradle-build-action@v2 - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - name: create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - arch: 'x86_64' - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - name: Run Android Tests - uses: reactivecircus/android-emulator-runner@v2 - with: - arch: 'x86_64' - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: ./gradlew connectedDebugAndroidTest - - name: Upload Test Results - uses: actions/upload-artifact@v2 - if: always() - with: - name: Emulator-Test-Results-${{ matrix.api-level }} - path: ./**/build/reports/androidTests/**/*.html - retention-days: 1 - - name: Upload Test Screenshots - uses: actions/upload-artifact@v2 - if: always() - with: - name: Emulator-Test-Results-${{ matrix.api-level }} - path: ./**/build/testscreenshots/* - retention-days: 1 \ No newline at end of file +# run-tests-on-emulator: +# runs-on: macos-latest +# strategy: +# matrix: +# api-level: [ 21, 30 ] +# fail-fast: false +# steps: +# - name: checkout +# uses: actions/checkout@v3 +# - name: Setup Java +# uses: actions/setup-java@v2 +# with: +# distribution: 'adopt' +# java-version: '11' +# - name: Gradle cache +# uses: gradle/gradle-build-action@v2 +# - name: AVD cache +# uses: actions/cache@v3 +# id: avd-cache +# with: +# path: | +# ~/.android/avd/* +# ~/.android/adb* +# key: avd-${{ matrix.api-level }} +# - name: create AVD and generate snapshot for caching +# if: steps.avd-cache.outputs.cache-hit != 'true' +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# arch: 'x86_64' +# api-level: ${{ matrix.api-level }} +# force-avd-creation: false +# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: false +# script: echo "Generated AVD snapshot for caching." +# - name: Run Android Tests +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# arch: 'x86_64' +# api-level: ${{ matrix.api-level }} +# force-avd-creation: false +# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: true +# script: ./gradlew connectedDebugAndroidTest +# - name: Upload Test Results +# uses: actions/upload-artifact@v2 +# if: always() +# with: +# name: Emulator-Test-Results-${{ matrix.api-level }} +# path: ./**/build/reports/androidTests/**/*.html +# retention-days: 1 +# - name: Upload Test Screenshots +# uses: actions/upload-artifact@v2 +# if: always() +# with: +# name: Emulator-Test-Results-${{ matrix.api-level }} +# path: ./**/build/testscreenshots/* +# retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/screenshot-tests.yml b/.github/workflows/screenshot-tests.yml new file mode 100644 index 0000000..6acc288 --- /dev/null +++ b/.github/workflows/screenshot-tests.yml @@ -0,0 +1,55 @@ +name: Verify Screenshots can be created and pulled + +on: + pull_request: + branches: + - develop + +env: + GITHUB_USERNAME: "fknives" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + run-screenshot-test-on-emulator: + runs-on: macos-latest + strategy: + matrix: + api-level: [ 21, 23, 24, 26, 28, 29, 30, 31 ] + fail-fast: false + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '11' + - name: Gradle cache + uses: gradle/gradle-build-action@v2 + - name: AVD cache + uses: actions/cache@v3 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + arch: 'x86_64' + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + - name: Run Android Tests + uses: reactivecircus/android-emulator-runner@v2 + with: + arch: 'x86_64' + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: ./pullscreenshottest.sh \ No newline at end of file diff --git a/app/src/androidTest/java/org/fnives/test/showcase/rule/ScreenshotTest.kt b/app/src/androidTest/java/org/fnives/test/showcase/rule/ScreenshotTest.kt new file mode 100644 index 0000000..b8975d9 --- /dev/null +++ b/app/src/androidTest/java/org/fnives/test/showcase/rule/ScreenshotTest.kt @@ -0,0 +1,36 @@ +package org.fnives.test.showcase.rule + +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.fnives.test.showcase.android.testutil.activity.SafeCloseActivityRule +import org.fnives.test.showcase.android.testutil.screenshot.ScreenshotRule +import org.fnives.test.showcase.android.testutil.synchronization.MainDispatcherTestRule +import org.fnives.test.showcase.ui.splash.SplashActivity +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith +import org.koin.test.KoinTest + +@Suppress("TestFunctionName") +@RunWith(AndroidJUnit4::class) +class ScreenshotTest : KoinTest { + + private lateinit var activityScenario: ActivityScenario + + private val mainDispatcherTestRule = MainDispatcherTestRule() + + @Rule + @JvmField + val ruleOrder: RuleChain = RuleChain.outerRule(mainDispatcherTestRule) + .around(SafeCloseActivityRule { activityScenario }) + .around(ScreenshotRule(prefix = "screenshot-rule", takeOnSuccess = true)) + + /** GIVEN loggedInState WHEN opened after some time THEN MainActivity is started */ + @Test + fun screenshot() { + activityScenario = ActivityScenario.launch(SplashActivity::class.java) + activityScenario.moveToState(Lifecycle.State.RESUMED) + } +} diff --git a/app/verifyfiles.sh b/app/verifyfiles.sh new file mode 100755 index 0000000..f8685fd --- /dev/null +++ b/app/verifyfiles.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +# if given folder is empty throws error +if [ -d "$1" ]; then + if [ $(ls -A $1 | wc -l) ]; + then + echo "" + else + exit 1 + fi +else + exit 1 +fi diff --git a/gradlescripts/pull-screenshots.gradle b/gradlescripts/pull-screenshots.gradle index ae0b2f5..157cde0 100644 --- a/gradlescripts/pull-screenshots.gradle +++ b/gradlescripts/pull-screenshots.gradle @@ -29,47 +29,64 @@ def packageName = propertyOrNull("screenshotsPackageName") ?: "$android.defaultC def screenshotDirectory = propertyOrNull("screenshotsDirectory") ?: "test-screenshots" def savePath = propertyOrNull("screenshotsSavePath") ?: "build/testscreenshots/" def adb = propertyOrNull("adbPath") ?: findAdbFromLocal() -def internalFullPath = "/sdcard/Android/data/$packageName/files/Pictures/$screenshotDirectory/" -def deprecatedFullPath = "/sdcard/Pictures/$packageName/$screenshotDirectory/" +def contextInternalFullPath = "/data/data/$packageName/files/$screenshotDirectory/" +def contextExternalFullPath = "/sdcard/Android/data/$packageName/files/Pictures/$screenshotDirectory/" +def environmentExternalFullPath = "/sdcard/Pictures/$packageName/$screenshotDirectory/" -task pullScreenshotsInternal(type: Exec) { +task pullScreenshotsContextInternal(type: Exec) { group = 'Test-Screenshots' - description = 'Pull screenshots From internal Storage' + description = 'Pull screenshots From context.external Storage' ignoreExitValue(true) - commandLine "$adb", 'pull', "$internalFullPath", "$savePath/" + commandLine "$adb", 'pull', "$contextInternalFullPath", "$savePath/" } -task pullScreenshotsDeprecated(type: Exec) { +task pullScreenshotsContextExternal(type: Exec) { group = 'Test-Screenshots' - description = 'Pull screenshots From deprecated External Storage' + description = 'Pull screenshots From context.external Storage' ignoreExitValue(true) - commandLine "$adb", 'pull', "$deprecatedFullPath", "$savePath/" + commandLine "$adb", 'pull', "$contextExternalFullPath", "$savePath/" } -task pullScreenshots(dependsOn: [pullScreenshotsInternal, pullScreenshotsDeprecated]) { +task pullScreenshotsEnvironmentExternal(type: Exec) { + group = 'Test-Screenshots' + description = 'Pull screenshots From environment.external Storage' + + ignoreExitValue(true) + commandLine "$adb", 'pull', "$environmentExternalFullPath", "$savePath/" +} + +task pullScreenshots(dependsOn: [pullScreenshotsContextInternal, pullScreenshotsContextExternal, pullScreenshotsEnvironmentExternal]) { group = 'Test-Screenshots' description = 'Pull screenshots From Device' } -task removeScreenshotsFromDeviceInternal(type: Exec) { +task removeScreenshotsFromDeviceContextInternal(type: Exec) { group = 'Test-Screenshots' - description = 'Remove screenshots From internal Storage' + description = 'Remove screenshots From context.internal Storage' ignoreExitValue(true) - commandLine "$adb", 'shell', 'rm', '-r', "$internalFullPath" + commandLine "$adb", 'shell', 'rm', '-r', "$contextInternalFullPath" } -task removeScreenshotsFromDeviceDeprecated(type: Exec) { +task removeScreenshotsFromDeviceContextExternal(type: Exec) { group = 'Test-Screenshots' - description = 'Remove screenshots From deprecated External Storage' + description = 'Remove screenshots From context.external Storage' ignoreExitValue(true) - commandLine "$adb", 'shell', 'rm', '-r', "$deprecatedFullPath" + commandLine "$adb", 'shell', 'rm', '-r', "$contextExternalFullPath" } -task removeScreenshotsFromDevice(dependsOn: [removeScreenshotsFromDeviceInternal, removeScreenshotsFromDeviceDeprecated]) { +task removeScreenshotsFromDeviceEnvironmentExternal(type: Exec) { + group = 'Test-Screenshots' + description = 'Remove screenshots From environment.external Storage' + + ignoreExitValue(true) + commandLine "$adb", 'shell', 'rm', '-r', "$environmentExternalFullPath" +} + +task removeScreenshotsFromDevice(dependsOn: [removeScreenshotsFromDeviceContextInternal, removeScreenshotsFromDeviceContextExternal, removeScreenshotsFromDeviceEnvironmentExternal]) { group = 'Test-Screenshots' description = 'Remove screenshots From Device' } @@ -89,9 +106,12 @@ task showLogcat(type: Exec) { commandLine "$adb", 'logcat', '-d' } +task hasScreenshots(type: Exec) { + commandLine "sh", "./verifyfiles.sh", "$savePath" +} + afterEvaluate { connectedDebugAndroidTest.finalizedBy showLogcat showLogcat.finalizedBy pullScreenshots - pullScreenshots.finalizedBy removeScreenshotsFromDevice clean.dependsOn(removeLocalScreenshots) } \ No newline at end of file diff --git a/pullscreenshottest.sh b/pullscreenshottest.sh new file mode 100755 index 0000000..3208173 --- /dev/null +++ b/pullscreenshottest.sh @@ -0,0 +1,3 @@ +./gradlew clean +./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.fnives.test.showcase.rule.ScreenshotTest +./gradlew app:hasScreenshots \ No newline at end of file diff --git a/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/ScreenshotRule.kt b/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/ScreenshotRule.kt index 20165bd..b27ab27 100644 --- a/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/ScreenshotRule.kt +++ b/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/ScreenshotRule.kt @@ -54,6 +54,7 @@ class ScreenshotRule( capture.format = Bitmap.CompressFormat.JPEG try { capture.process(setOf(processor)) + Log.d(TAG, "Saved image: $filename") } catch (e: IOException) { Log.d(TAG, "Couldn't save image: $e") e.printStackTrace() diff --git a/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/basicScreenCaptureProcessor.kt b/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/basicScreenCaptureProcessor.kt index c829768..9d2685f 100644 --- a/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/basicScreenCaptureProcessor.kt +++ b/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/screenshot/basicScreenCaptureProcessor.kt @@ -10,7 +10,7 @@ import java.io.File fun basicScreenCaptureProcessor(subDir: String = "test-screenshots"): ScreenCaptureProcessor { val directory = File(getTestPicturesDir(), subDir) - Log.d(ScreenshotRule.TAG, "directory to save screenshots = $directory") + Log.d(ScreenshotRule.TAG, "directory to save screenshots = ${directory.absolutePath}") return basicScreenCaptureProcessor(File(getTestPicturesDir(), subDir)) } @@ -22,15 +22,19 @@ fun basicScreenCaptureProcessor(subDir: String = "test-screenshots"): ScreenCapt */ @Suppress("DEPRECATION") fun getTestPicturesDir(): File? = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - Log.d(ScreenshotRule.TAG, "internal folder") + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + Log.d(ScreenshotRule.TAG, "context.internal folder") - InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - } else { + InstrumentationRegistry.getInstrumentation().targetContext.filesDir + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val packageName = InstrumentationRegistry.getInstrumentation().targetContext.packageName val environmentFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) val externalFolder = File(environmentFolder, packageName) - Log.d(ScreenshotRule.TAG, "external folder") + Log.d(ScreenshotRule.TAG, "environment.external folder") externalFolder + } else { + Log.d(ScreenshotRule.TAG, "context.external folder") + + InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) }