Issue#104 Create Test verifying screenshot pulling works

This commit is contained in:
Gergely Hegedus 2022-07-13 21:33:45 +03:00
parent 69f5f15c3a
commit bae8c0fc96
8 changed files with 212 additions and 80 deletions

View file

@ -74,60 +74,60 @@ jobs:
path: ./**/build/reports/tests/**/index.html path: ./**/build/reports/tests/**/index.html
retention-days: 1 retention-days: 1
run-tests-on-emulator: # run-tests-on-emulator:
runs-on: macos-latest # runs-on: macos-latest
strategy: # strategy:
matrix: # matrix:
api-level: [ 21, 30 ] # api-level: [ 21, 30 ]
fail-fast: false # fail-fast: false
steps: # steps:
- name: checkout # - name: checkout
uses: actions/checkout@v3 # uses: actions/checkout@v3
- name: Setup Java # - name: Setup Java
uses: actions/setup-java@v2 # uses: actions/setup-java@v2
with: # with:
distribution: 'adopt' # distribution: 'adopt'
java-version: '11' # java-version: '11'
- name: Gradle cache # - name: Gradle cache
uses: gradle/gradle-build-action@v2 # uses: gradle/gradle-build-action@v2
- name: AVD cache # - name: AVD cache
uses: actions/cache@v3 # uses: actions/cache@v3
id: avd-cache # id: avd-cache
with: # with:
path: | # path: |
~/.android/avd/* # ~/.android/avd/*
~/.android/adb* # ~/.android/adb*
key: avd-${{ matrix.api-level }} # key: avd-${{ matrix.api-level }}
- name: create AVD and generate snapshot for caching # - name: create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true' # if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2 # uses: reactivecircus/android-emulator-runner@v2
with: # with:
arch: 'x86_64' # arch: 'x86_64'
api-level: ${{ matrix.api-level }} # api-level: ${{ matrix.api-level }}
force-avd-creation: false # force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false # disable-animations: false
script: echo "Generated AVD snapshot for caching." # script: echo "Generated AVD snapshot for caching."
- name: Run Android Tests # - name: Run Android Tests
uses: reactivecircus/android-emulator-runner@v2 # uses: reactivecircus/android-emulator-runner@v2
with: # with:
arch: 'x86_64' # arch: 'x86_64'
api-level: ${{ matrix.api-level }} # api-level: ${{ matrix.api-level }}
force-avd-creation: false # force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none # emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true # disable-animations: true
script: ./gradlew connectedDebugAndroidTest # script: ./gradlew connectedDebugAndroidTest
- name: Upload Test Results # - name: Upload Test Results
uses: actions/upload-artifact@v2 # uses: actions/upload-artifact@v2
if: always() # if: always()
with: # with:
name: Emulator-Test-Results-${{ matrix.api-level }} # name: Emulator-Test-Results-${{ matrix.api-level }}
path: ./**/build/reports/androidTests/**/*.html # path: ./**/build/reports/androidTests/**/*.html
retention-days: 1 # retention-days: 1
- name: Upload Test Screenshots # - name: Upload Test Screenshots
uses: actions/upload-artifact@v2 # uses: actions/upload-artifact@v2
if: always() # if: always()
with: # with:
name: Emulator-Test-Results-${{ matrix.api-level }} # name: Emulator-Test-Results-${{ matrix.api-level }}
path: ./**/build/testscreenshots/* # path: ./**/build/testscreenshots/*
retention-days: 1 # retention-days: 1

55
.github/workflows/screenshot-tests.yml vendored Normal file
View file

@ -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

View file

@ -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<SplashActivity>
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)
}
}

13
app/verifyfiles.sh Executable file
View file

@ -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

View file

@ -29,47 +29,64 @@ def packageName = propertyOrNull("screenshotsPackageName") ?: "$android.defaultC
def screenshotDirectory = propertyOrNull("screenshotsDirectory") ?: "test-screenshots" def screenshotDirectory = propertyOrNull("screenshotsDirectory") ?: "test-screenshots"
def savePath = propertyOrNull("screenshotsSavePath") ?: "build/testscreenshots/" def savePath = propertyOrNull("screenshotsSavePath") ?: "build/testscreenshots/"
def adb = propertyOrNull("adbPath") ?: findAdbFromLocal() def adb = propertyOrNull("adbPath") ?: findAdbFromLocal()
def internalFullPath = "/sdcard/Android/data/$packageName/files/Pictures/$screenshotDirectory/" def contextInternalFullPath = "/data/data/$packageName/files/$screenshotDirectory/"
def deprecatedFullPath = "/sdcard/Pictures/$packageName/$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' group = 'Test-Screenshots'
description = 'Pull screenshots From internal Storage' description = 'Pull screenshots From context.external Storage'
ignoreExitValue(true) ignoreExitValue(true)
commandLine "$adb", 'pull', "$internalFullPath", "$savePath/" commandLine "$adb", 'pull', "$contextInternalFullPath", "$savePath/"
} }
task pullScreenshotsDeprecated(type: Exec) { task pullScreenshotsContextExternal(type: Exec) {
group = 'Test-Screenshots' group = 'Test-Screenshots'
description = 'Pull screenshots From deprecated External Storage' description = 'Pull screenshots From context.external Storage'
ignoreExitValue(true) 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' group = 'Test-Screenshots'
description = 'Pull screenshots From Device' description = 'Pull screenshots From Device'
} }
task removeScreenshotsFromDeviceInternal(type: Exec) { task removeScreenshotsFromDeviceContextInternal(type: Exec) {
group = 'Test-Screenshots' group = 'Test-Screenshots'
description = 'Remove screenshots From internal Storage' description = 'Remove screenshots From context.internal Storage'
ignoreExitValue(true) 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' group = 'Test-Screenshots'
description = 'Remove screenshots From deprecated External Storage' description = 'Remove screenshots From context.external Storage'
ignoreExitValue(true) 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' group = 'Test-Screenshots'
description = 'Remove screenshots From Device' description = 'Remove screenshots From Device'
} }
@ -89,9 +106,12 @@ task showLogcat(type: Exec) {
commandLine "$adb", 'logcat', '-d' commandLine "$adb", 'logcat', '-d'
} }
task hasScreenshots(type: Exec) {
commandLine "sh", "./verifyfiles.sh", "$savePath"
}
afterEvaluate { afterEvaluate {
connectedDebugAndroidTest.finalizedBy showLogcat connectedDebugAndroidTest.finalizedBy showLogcat
showLogcat.finalizedBy pullScreenshots showLogcat.finalizedBy pullScreenshots
pullScreenshots.finalizedBy removeScreenshotsFromDevice
clean.dependsOn(removeLocalScreenshots) clean.dependsOn(removeLocalScreenshots)
} }

3
pullscreenshottest.sh Executable file
View file

@ -0,0 +1,3 @@
./gradlew clean
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.fnives.test.showcase.rule.ScreenshotTest
./gradlew app:hasScreenshots

View file

@ -54,6 +54,7 @@ class ScreenshotRule(
capture.format = Bitmap.CompressFormat.JPEG capture.format = Bitmap.CompressFormat.JPEG
try { try {
capture.process(setOf(processor)) capture.process(setOf(processor))
Log.d(TAG, "Saved image: $filename")
} catch (e: IOException) { } catch (e: IOException) {
Log.d(TAG, "Couldn't save image: $e") Log.d(TAG, "Couldn't save image: $e")
e.printStackTrace() e.printStackTrace()

View file

@ -10,7 +10,7 @@ import java.io.File
fun basicScreenCaptureProcessor(subDir: String = "test-screenshots"): ScreenCaptureProcessor { fun basicScreenCaptureProcessor(subDir: String = "test-screenshots"): ScreenCaptureProcessor {
val directory = File(getTestPicturesDir(), subDir) 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)) return basicScreenCaptureProcessor(File(getTestPicturesDir(), subDir))
} }
@ -22,15 +22,19 @@ fun basicScreenCaptureProcessor(subDir: String = "test-screenshots"): ScreenCapt
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun getTestPicturesDir(): File? = fun getTestPicturesDir(): File? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S || Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Log.d(ScreenshotRule.TAG, "internal folder") Log.d(ScreenshotRule.TAG, "context.internal folder")
InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES) InstrumentationRegistry.getInstrumentation().targetContext.filesDir
} else { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val packageName = InstrumentationRegistry.getInstrumentation().targetContext.packageName val packageName = InstrumentationRegistry.getInstrumentation().targetContext.packageName
val environmentFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) val environmentFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val externalFolder = File(environmentFolder, packageName) val externalFolder = File(environmentFolder, packageName)
Log.d(ScreenshotRule.TAG, "external folder") Log.d(ScreenshotRule.TAG, "environment.external folder")
externalFolder externalFolder
} else {
Log.d(ScreenshotRule.TAG, "context.external folder")
InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
} }