Merge pull request #105 from fknives/issue#100-fix-screenshot-rule-api-21
Issue#100 Issue#104 Fix screenshot rule for API 21
This commit is contained in:
commit
54b3414771
9 changed files with 200 additions and 31 deletions
7
.github/workflows/pull-request-jobs.yml
vendored
7
.github/workflows/pull-request-jobs.yml
vendored
|
|
@ -130,4 +130,11 @@ jobs:
|
|||
with:
|
||||
name: Emulator-Test-Results-${{ matrix.api-level }}
|
||||
path: ./**/build/testscreenshots/*
|
||||
retention-days: 1
|
||||
- name: Upload Logcat Logs
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: Emulator-Logcat-Logs-${{ matrix.api-level }}
|
||||
path: ./**/build/logcat.txt
|
||||
retention-days: 1
|
||||
54
.github/workflows/screenshot-tests.yml
vendored
Normal file
54
.github/workflows/screenshot-tests.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
name: Verify Screenshots can be created and pulled
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
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.testutils.ReloadKoinModulesIfNecessaryTestRule
|
||||
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(ReloadKoinModulesIfNecessaryTestRule())
|
||||
.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
13
app/verifyfiles.sh
Executable 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
|
||||
|
|
@ -23,7 +23,6 @@ afterEvaluate {
|
|||
from components.release
|
||||
|
||||
groupId "$testUtilGroupId"
|
||||
println("$testUtilArtifactId")
|
||||
version "$testUtilVersion"
|
||||
artifactId "$testUtilArtifactId"
|
||||
artifact sourcesJar
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ def findAdbFromLocal = {
|
|||
def sdkDir = properties.getProperty('sdk.dir')
|
||||
return "$sdkDir/platform-tools/adb"
|
||||
} else {
|
||||
System.err.println("WARNING: SDK dir not found by local properties, returning static: $System.env.HOME/Library/Android/sdk/platform-tools/adb")
|
||||
println("WARNING: SDK dir not found by local properties, returning static: $System.env.HOME/Library/Android/sdk/platform-tools/adb")
|
||||
return "$System.env.HOME/Library/Android/sdk/platform-tools/adb"
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
|
@ -81,8 +98,22 @@ task removeLocalScreenshots(type: Delete) {
|
|||
delete files("$savePath")
|
||||
}
|
||||
|
||||
task saveLogcatLogs(type: Exec) {
|
||||
group = 'Test-Screenshots'
|
||||
description = 'Show Logcat'
|
||||
|
||||
doFirst {
|
||||
standardOutput = new FileOutputStream("${buildDir}/logcat.txt")
|
||||
}
|
||||
commandLine "$adb", 'logcat', '-d'
|
||||
}
|
||||
|
||||
task hasScreenshots(type: Exec) {
|
||||
commandLine "sh", "./verifyfiles.sh", "$savePath"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
connectedDebugAndroidTest.finalizedBy pullScreenshots
|
||||
pullScreenshots.finalizedBy removeScreenshotsFromDevice
|
||||
connectedDebugAndroidTest.finalizedBy saveLogcatLogs
|
||||
saveLogcatLogs.finalizedBy pullScreenshots
|
||||
clean.dependsOn(removeLocalScreenshots)
|
||||
}
|
||||
3
pullscreenshottest.sh
Executable file
3
pullscreenshottest.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
./gradlew clean
|
||||
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=org.fnives.test.showcase.rule.ScreenshotTest
|
||||
./gradlew app:hasScreenshots
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package org.fnives.test.showcase.android.testutil.screenshot
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import androidx.test.runner.screenshot.ScreenCapture
|
||||
import androidx.test.runner.screenshot.ScreenCaptureProcessor
|
||||
import androidx.test.runner.screenshot.Screenshot
|
||||
|
|
@ -38,28 +39,30 @@ class ScreenshotRule(
|
|||
}
|
||||
}
|
||||
|
||||
fun takeScreenshot(prefix: String = this.prefix, baseName: String) {
|
||||
fun takeScreenshot(prefix: String = this.prefix, baseName: String, capture: ScreenCapture = Screenshot.capture()) {
|
||||
val fileName = if (timestampSuffix) {
|
||||
"$prefix-$baseName-${System.currentTimeMillis()}"
|
||||
} else {
|
||||
"$prefix-$baseName"
|
||||
}
|
||||
takeScreenshot(filename = fileName)
|
||||
takeScreenshot(filename = fileName, capture = capture)
|
||||
}
|
||||
|
||||
@Suppress("PrintStackTrace")
|
||||
private fun takeScreenshot(filename: String) {
|
||||
val capture: ScreenCapture = Screenshot.capture()
|
||||
private fun takeScreenshot(filename: String, capture: ScreenCapture) {
|
||||
capture.name = filename
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "Screenshot Rule"
|
||||
val Description.testScreenshotName get() = "${testClass.simpleName}-$methodName"
|
||||
val Description.beforeTestScreenshotName get() = "$testScreenshotName-BEFORE"
|
||||
val Description.successTestScreenshotName get() = "$testScreenshotName-SUCCESS"
|
||||
|
|
|
|||
|
|
@ -2,18 +2,39 @@ package org.fnives.test.showcase.android.testutil.screenshot
|
|||
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.screenshot.ScreenCaptureProcessor
|
||||
import androidx.test.runner.screenshot.basicScreenCaptureProcessor
|
||||
import java.io.File
|
||||
|
||||
fun basicScreenCaptureProcessor(subDir: String = "test-screenshots") =
|
||||
basicScreenCaptureProcessor(File(getTestPicturesDir(), subDir))
|
||||
fun basicScreenCaptureProcessor(subDir: String = "test-screenshots"): ScreenCaptureProcessor {
|
||||
val directory = File(getTestPicturesDir(), subDir)
|
||||
Log.d(ScreenshotRule.TAG, "directory to save screenshots = ${directory.absolutePath}")
|
||||
return basicScreenCaptureProcessor(File(getTestPicturesDir(), subDir))
|
||||
}
|
||||
|
||||
/**
|
||||
* BasicScreenCaptureProcessor seems to work differently on API versions,
|
||||
* based on where we have access to save and pull the images from.
|
||||
*
|
||||
* see example issue: https://github.com/android/android-test/issues/818
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
fun getTestPicturesDir() =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
|
||||
} else {
|
||||
fun getTestPicturesDir(): File? =
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
Log.d(ScreenshotRule.TAG, "context.internal folder")
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.filesDir
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val packageName = InstrumentationRegistry.getInstrumentation().targetContext.packageName
|
||||
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), packageName)
|
||||
val environmentFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
val externalFolder = File(environmentFolder, packageName)
|
||||
Log.d(ScreenshotRule.TAG, "environment.external folder")
|
||||
|
||||
externalFolder
|
||||
} else {
|
||||
Log.d(ScreenshotRule.TAG, "context.external folder")
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue