Compare commits

..

1 Commits

Author SHA1 Message Date
7b3f65be93 ci: Instrumentation tests coverage in ci.
Some checks failed
Check PR code / EspressoReports (pull_request) Has been cancelled
2025-03-19 21:41:08 +01:00
10 changed files with 160 additions and 1 deletions

View File

@ -34,6 +34,14 @@ jobs:
api-level: 29 api-level: 29
arch: x86_64 arch: x86_64
script: ./gradlew androidApp:connectedAndroidTest script: ./gradlew androidApp:connectedAndroidTest
- uses: actions/upload-artifact@v3
if: failure()
with:
name: failure-espresso
path: build/reports/androidTests/connected/screenshots
retention-days: 2
overwrite: true
include-hidden-files: true
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: coverage-espresso name: coverage-espresso

View File

@ -112,6 +112,9 @@ android {
getByName("debug") { getByName("debug") {
isTestCoverageEnabled = true isTestCoverageEnabled = true
enableAndroidTestCoverage = true enableAndroidTestCoverage = true
installation {
installOptions("-g", "-r")
}
} }
} }
flavorDimensions.add("build") flavorDimensions.add("build")
@ -240,3 +243,40 @@ aboutLibraries {
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
} }
// Screenshot failure handling
val reportsDirectory = file("$buildDir/reports/androidTests/connected")
val clearScreenshotsTask =
tasks.register<Exec>("clearScreenshots") {
println("AMINE : clear")
commandLine = listOf("adb", "shell", "rm", "-r", "/sdcard/Pictures/selfoss_tests")
}
val createScreenshotDirectoryTask =
tasks.register<Exec>("createScreenshotDirectory") {
println("AMINE : create directory")
group = "reporting"
commandLine = listOf("adb", "shell", "mkdir", "-p", "/sdcard/Pictures/selfoss_tests")
}
val fetchScreenshotsTask =
tasks.register<Exec>("fetchScreenshots") {
println("AMINE : fetch")
group = "reporting"
executable(android.adbExecutable.toString())
commandLine = listOf("adb", "pull", "/sdcard/Pictures/selfoss_tests/.", reportsDirectory.toString())
finalizedBy(clearScreenshotsTask)
dependsOn(createScreenshotDirectoryTask)
doFirst {
reportsDirectory.mkdirs()
}
}
tasks.whenTaskAdded {
if (this.name == "connectedGithubConfigDebugAndroidTest") {
this.finalizedBy(fetchScreenshotsTask)
}
}

View File

@ -1,6 +1,9 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import android.os.Environment.DIRECTORY_PICTURES
import android.os.Environment.getExternalStoragePublicDirectory
import android.util.Log
import androidx.annotation.ArrayRes import androidx.annotation.ArrayRes
import androidx.test.espresso.Espresso import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onData
@ -17,11 +20,17 @@ 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 org.junit.runner.Description
import java.io.File
import java.io.IOException
import java.util.Locale import java.util.Locale
fun performLogin(someUrl: String? = null) { fun performLogin(someUrl: String? = null) {
@ -164,3 +173,48 @@ open class WithANRException {
} }
} }
} }
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(
parentFolderPath: String = "",
screenShotName: String,
) {
Log.d("Screenshots", "Taking screenshot of '$screenShotName'")
val screenCapture = Screenshot.capture()
val processors = setOf(MyScreenCaptureProcessor(parentFolderPath))
try {
screenCapture.apply {
name = screenShotName
process(processors)
}
Log.d("Screenshots", "Screenshot taken")
} catch (ex: IOException) {
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)
}
}

View File

@ -18,6 +18,7 @@ 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)
@ -26,6 +27,13 @@ class HomeActivityTest : 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 init() { fun init() {
loginAndInitHome() loginAndInitHome()
@ -33,7 +41,7 @@ class HomeActivityTest : WithANRException() {
@Test @Test
fun testMenu() { fun testMenu() {
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check( onView(withId(R.id.action_search)).check(matches(not(isDisplayed()))).check(
matches( matches(
isClickable(), isClickable(),
), ),

View File

@ -17,6 +17,7 @@ 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
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -25,6 +26,13 @@ class 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

@ -24,6 +24,7 @@ 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)
@ -32,6 +33,13 @@ class SettingsActivityGeneralTest : 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 init() { fun init() {
loginAndInitHome() loginAndInitHome()

View File

@ -19,6 +19,7 @@ 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)
@ -27,6 +28,13 @@ class SettingsActivityOfflineTest : 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())
lateinit var context: Context lateinit var context: Context
@Before @Before

View File

@ -17,6 +17,7 @@ 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,6 +26,13 @@ class SettingsActivityReaderTest : 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())
lateinit var context: Context lateinit var context: Context
@Before @Before

View File

@ -15,6 +15,7 @@ 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)
@ -22,6 +23,14 @@ import org.junit.runner.RunWith
class SettingsActivityTest : WithANRException() { class SettingsActivityTest : 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())
lateinit var context: Context lateinit var context: Context
@Before @Before

View File

@ -18,6 +18,7 @@ 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
@ -27,6 +28,13 @@ class SourcesActivityTest : 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())
lateinit var sourceName: String lateinit var sourceName: String
@Before @Before