Compare commits

..

1 Commits

Author SHA1 Message Date
f5d1a995a3 ci: Instrumentation tests coverage in ci.
All checks were successful
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 31m1s
2025-03-21 13:00:26 +01:00
9 changed files with 39 additions and 96 deletions

View File

@ -248,21 +248,21 @@ val reportsDirectory = file("$buildDir/reports/androidTests/connected")
val clearScreenshotsTask = val clearScreenshotsTask =
tasks.register<Exec>("clearScreenshots") { tasks.register<Exec>("clearScreenshots") {
println("AMINE : clear") println("AMINE : clear")
commandLine = listOf("adb", "shell", "rm", "-r", "/sdcard/Pictures/selfoss_tests") commandLine = listOf("adb", "shell", "rm", "-r", "/storage/emulated/0//Pictures/selfoss_tests")
} }
val createScreenshotDirectoryTask = val createScreenshotDirectoryTask =
tasks.register<Exec>("createScreenshotDirectory") { tasks.register<Exec>("createScreenshotDirectory") {
println("AMINE : create directory") println("AMINE : create directory")
group = "reporting" group = "reporting"
commandLine = listOf("adb", "shell", "mkdir", "-p", "/sdcard/Pictures/selfoss_tests") commandLine = listOf("adb", "shell", "mkdir", "-p", "/storage/emulated/0//Pictures/selfoss_tests")
} }
tasks.register<Exec>("fetchScreenshots") { tasks.register<Exec>("fetchScreenshots") {
println("AMINE : fetch") println("AMINE : fetch")
group = "reporting" group = "reporting"
executable(android.adbExecutable.toString()) executable(android.adbExecutable.toString())
commandLine = listOf("adb", "pull", "/sdcard/Pictures/selfoss_tests/.", reportsDirectory.toString()) commandLine = listOf("adb", "pull", "/storage/emulated/0//Pictures/selfoss_tests/.", reportsDirectory.toString())
finalizedBy(clearScreenshotsTask) finalizedBy(clearScreenshotsTask)
dependsOn(createScreenshotDirectoryTask) dependsOn(createScreenshotDirectoryTask)

View File

@ -18,7 +18,6 @@ import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
@ -30,13 +29,6 @@ class `1-LoginActivityTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java) val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
@Before @Before
fun registerIdlingResource() { fun registerIdlingResource() {
IdlingRegistry IdlingRegistry

View File

@ -18,7 +18,6 @@ import org.hamcrest.CoreMatchers.not
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
@ -30,13 +29,6 @@ class `2-HomeActivityTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java) val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
@Test @Test
fun testMenu() { fun testMenu() {
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check( onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(

View File

@ -15,7 +15,6 @@ import org.hamcrest.CoreMatchers.not
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -25,13 +24,6 @@ class `3-SettingsActivityTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java) val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
lateinit var context: Context lateinit var context: Context
@Before @Before

View File

@ -25,7 +25,6 @@ import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
@ -37,13 +36,6 @@ class `4-SettingsActivityGeneralTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java) val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
@Before @Before
fun init() { fun init() {
openActionBarOverflowOrOptionsMenu( openActionBarOverflowOrOptionsMenu(

View File

@ -19,7 +19,6 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming") @Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@ -29,13 +28,6 @@ class `5-SettingsActivityReaderTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(SettingsActivity::class.java) val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
lateinit var context: Context lateinit var context: Context
@Before @Before

View File

@ -21,7 +21,6 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming") @Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@ -31,13 +30,6 @@ class `6-SettingsActivityOfflineTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(SettingsActivity::class.java) val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
lateinit var context: Context lateinit var context: Context
@Before @Before

View File

@ -18,7 +18,6 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.util.UUID import java.util.UUID
@ -29,13 +28,6 @@ class `7-SourcesActivityTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java) val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@JvmField
@Rule
val ruleChain: RuleChain =
RuleChain
.outerRule(activityRule)
.around(ScreenshotTakingRule())
lateinit var sourceName: String lateinit var sourceName: String
@Before @Before

View File

@ -1,6 +1,7 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.os.Environment.DIRECTORY_PICTURES import android.os.Environment.DIRECTORY_PICTURES
import android.os.Environment.getExternalStoragePublicDirectory import android.os.Environment.getExternalStoragePublicDirectory
import android.util.Log import android.util.Log
@ -20,16 +21,14 @@ import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.runner.screenshot.BasicScreenCaptureProcessor
import androidx.test.runner.screenshot.Screenshot
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matchers.hasToString import org.hamcrest.Matchers.hasToString
import org.junit.BeforeClass import org.junit.BeforeClass
import org.junit.rules.TestWatcher import java.io.BufferedOutputStream
import org.junit.runner.Description
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.util.Locale import java.util.Locale
@ -142,6 +141,8 @@ fun testAddSourceWithUrl(
@Suppress("detekt:UtilityClassWithPublicConstructor") @Suppress("detekt:UtilityClassWithPublicConstructor")
open class WithANRException { open class WithANRException {
companion object { companion object {
private var testNumber = 0
// Running count of the number of Android Not Responding dialogues to prevent endless dismissal. // Running count of the number of Android Not Responding dialogues to prevent endless dismissal.
private var anrCount = 0 private var anrCount = 0
@ -167,58 +168,56 @@ open class WithANRException {
fun setUpHandler() { fun setUpHandler() {
Espresso.setFailureHandler { error, viewMatcher -> Espresso.setFailureHandler { error, viewMatcher ->
takeScreenshot("test-failures/", testNumber.toString())
if (error.message!!.contains(rootViewWithoutFocusExceptionMsg) && anrCount < 3) { if (error.message!!.contains(rootViewWithoutFocusExceptionMsg) && anrCount < 3) {
anrCount++ anrCount++
handleAnrDialogue() handleAnrDialogue()
} else { // chain all failures down to the default espresso handler } else { // chain all failures down to the default espresso handler
DefaultFailureHandler(getInstrumentation().targetContext).handle(error, viewMatcher) DefaultFailureHandler(getInstrumentation().targetContext).handle(error, viewMatcher)
} }
testNumber++
} }
} }
} }
} }
class MyScreenCaptureProcessor(
parentFolderPath: String,
) : BasicScreenCaptureProcessor() {
init {
this.mDefaultScreenshotPath =
File(
File(
getExternalStoragePublicDirectory(DIRECTORY_PICTURES),
"selfoss_tests",
).absolutePath,
"screenshots/$parentFolderPath",
)
}
override fun getFilename(prefix: String): String = prefix
}
fun takeScreenshot( fun takeScreenshot(
parentFolderPath: String = "", parentFolderPath: String = "",
screenShotName: String, screenShotName: String,
) { ) {
Log.d("Screenshots", "Taking screenshot of '$screenShotName'") Log.d("Screenshots", "Taking screenshot of '$screenShotName'")
val screenCapture = Screenshot.capture()
val processors = setOf(MyScreenCaptureProcessor(parentFolderPath))
try { try {
screenCapture.apply { val bitmap = getInstrumentation().uiAutomation.takeScreenshot()
name = screenShotName
process(processors) val folder =
File(
File(
getExternalStoragePublicDirectory(DIRECTORY_PICTURES),
"selfoss_tests",
).absolutePath,
"screenshots/$parentFolderPath",
)
if (!folder.exists()) {
folder.mkdirs()
}
var out: BufferedOutputStream? = null
try {
out = BufferedOutputStream(FileOutputStream(folder.path + "/" + screenShotName + ".png"))
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
Log.d("Screenshots", "Screenshot taken")
} catch (e: IOException) {
Log.e("Screenshots", "Could not save the screenshot", e)
} finally {
if (out != null) {
try {
out.close()
} catch (e: IOException) {
Log.e("Screenshots", "Could not save the screenshot", e)
}
}
} }
Log.d("Screenshots", "Screenshot taken")
} catch (ex: IOException) { } catch (ex: IOException) {
Log.e("Screenshots", "Could not take the screenshot", ex) Log.e("Screenshots", "Could not take the screenshot", ex)
} }
} }
class ScreenshotTakingRule : TestWatcher() {
override fun failed(
e: Throwable?,
description: Description,
) {
val parentFolderPath = "failures/${description.className}"
takeScreenshot(parentFolderPath = parentFolderPath, screenShotName = description.methodName)
}
}