Skip to content
Snippets Groups Projects
Commit ea379180 authored by Pierre Nicolas's avatar Pierre Nicolas :joy:
Browse files

test: add screenshot feature

Change-Id: I694037c34df607e3add8d9383fbc6bf6ea4b0070
parent aac4696f
Branches
Tags
No related merge requests found
...@@ -121,6 +121,8 @@ pipeline { ...@@ -121,6 +121,8 @@ pipeline {
errorOccurred = true errorOccurred = true
} }
sh 'cd /jami-client-android/ci && ./download_screenshots.sh'
// Archive tests output save it as Jenkins artifact // Archive tests output save it as Jenkins artifact
sh 'cd /jami-client-android/ci/spoon-output && zip -r ../ui-test-output.zip *' sh 'cd /jami-client-android/ci/spoon-output && zip -r ../ui-test-output.zip *'
archiveArtifacts artifacts: 'ci/ui-test-output.zip', allowEmptyArchive: false archiveArtifacts artifacts: 'ci/ui-test-output.zip', allowEmptyArchive: false
......
#!/bin/bash
# Define the source directory on the Android device (emulator)
screenshotDir="/sdcard/Download/screenshots"
# Define the destination directory on the local system
localDir="/jami-client-android/ci/spoon-output/screenshots"
# Create the local directory if it doesn't exist
mkdir -p "$localDir"
# Download the content of the directory from the device to the local directory
adb pull "$screenshotDir" "$localDir"
# Check if the download was successful
if [ $? -eq 0 ]; then
echo "The content of directory '$screenshotDir' has been downloaded to '$localDir'."
else
echo "Error downloading the directory."
fi
# Set ownership of the local directory and its contents to jenkins
chown -R jenkins:jenkins "$localDir"
package cx.ring
import android.graphics.Bitmap
import android.os.Build
import android.os.Environment
import androidx.test.runner.screenshot.BasicScreenCaptureProcessor
import androidx.test.runner.screenshot.Screenshot
import net.jami.utils.Log
import java.io.File
import java.io.IOException
import java.util.regex.Pattern
object NativeScreenshot {
private var methodName: String? = null
private var className: String? = null
private val SCREENSHOT_NAME_VALIDATION: Pattern = Pattern.compile("[a-zA-Z0-9_-]+")
/**
* Captures screenshot using Androidx Screenshot library and stores in the filesystem.
* Special Cases:
* If the screenshotName contains spaces or does not pass validation, the corresponding
* screenshot is not visible on BrowserStack's Dashboard.
* If there is any runtime exception while capturing screenshot, the method throws
* Exception and the test might fail if the exception is not handled properly.
* @param screenshotName a screenshot identifier
* @return path to the screenshot file
*/
fun capture(screenshotName: String): String {
Log.w("NativeScreenshot", "Capturing screenshot: $screenshotName")
val testClass = findTestClassTraceElement(Thread.currentThread().stackTrace)
className = testClass.className.replace("[^A-Za-z0-9._-]".toRegex(), "_")
methodName = testClass.methodName
val screenCaptureProcessor = EspressoScreenCaptureProcessor()
if (!SCREENSHOT_NAME_VALIDATION.matcher(screenshotName).matches()) {
throw IllegalArgumentException("ScreenshotName must match ${SCREENSHOT_NAME_VALIDATION.pattern()}.")
} else {
val capture = Screenshot.capture()
capture.format = Bitmap.CompressFormat.PNG
capture.name = screenshotName
try {
return screenCaptureProcessor.process(capture)
} catch (e: IOException) {
throw RuntimeException("Unable to capture screenshot.", e)
}
}
}
/**
* Extracts the currently executing test's trace element based on the test runner
* or any framework being used.
* @param trace stacktrace of the currently running test
* @return StackTrace Element corresponding to the current test being executed.
*/
private fun findTestClassTraceElement(trace: Array<StackTraceElement>): StackTraceElement {
for (i in trace.indices.reversed()) {
val element = trace[i]
if ("android.test.InstrumentationTestCase" == element.className && "runMethod" == element.methodName) {
return extractStackElement(trace, i)
}
if ("org.junit.runners.model.FrameworkMethod\$1" == element.className && "runReflectiveCall" == element.methodName) {
return extractStackElement(trace, i)
}
if ("cucumber.runtime.model.CucumberFeature" == element.className && "run" == element.methodName) {
return extractStackElement(trace, i)
}
}
throw IllegalArgumentException("Could not find test class!")
}
/**
* Based on the test runner or framework being used, extracts the exact traceElement.
* @param trace stacktrace of the currently running test
* @param i a reference index
* @return trace element based on the index passed
*/
private fun extractStackElement(trace: Array<StackTraceElement>, i: Int): StackTraceElement {
val testClassTraceIndex = if (Build.VERSION.SDK_INT >= 23) i - 2 else i - 3
return trace[testClassTraceIndex]
}
private class EspressoScreenCaptureProcessor : BasicScreenCaptureProcessor() {
companion object {
private const val SCREENSHOT = "screenshots"
}
init {
val screenshotDir = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString(), SCREENSHOT)
val classDir = File(screenshotDir, className)
Log.w("NativeScreenshot", "Screenshot directory: $classDir")
mDefaultScreenshotPath = File(classDir, methodName)
}
/**
* Converts the filename to a standard path to be stored on device.
* Example: "post_addition" converts to "1648038895211_post_addition"
* which is later suffixed by the file extension i.e. png.
* @param filename a screenshot identifier
* @return custom filename format
*/
override fun getFilename(filename: String): String {
return "${System.currentTimeMillis()}_$filename"
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment