Compare commits

..

1 Commits

Author SHA1 Message Date
798c400dd2 ci: Instrumentation tests coverage in ci.
All checks were successful
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 48m39s
2025-03-20 19:52:47 +01:00
11 changed files with 72 additions and 91 deletions

View File

@ -46,7 +46,7 @@ jobs:
./gradlew androidApp:connectedAndroidTest || true
./gradlew androidApp:fetchScreenshots
- uses: actions/upload-artifact@v3
if: always()
if: failure()
with:
name: failure-espresso
path: build/reports/androidTests/connected/screenshots

View File

@ -95,6 +95,7 @@ android {
// tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
testInstrumentationRunnerArguments["useTestStorageService"] = "true"
}
packaging {
@ -126,6 +127,7 @@ android {
namespace = "bou.amine.apps.readerforselfossv2.android"
testOptions {
animationsDisabled = true
execution = "ANDROIDX_TEST_ORCHESTRATOR"
unitTests {
isIncludeAndroidResources = true
}
@ -206,6 +208,7 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
implementation("androidx.test.espresso:espresso-idling-resource:3.6.1")
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
androidTestUtil("androidx.test:orchestrator:1.6.0-alpha02")
androidTestUtil("androidx.test.services:test-services:1.6.0-alpha02")
testImplementation("org.robolectric:robolectric:4.14.1")
testImplementation("androidx.test:core-ktx:1.7.0-alpha01")

View File

@ -45,6 +45,12 @@ fun performLogin(someUrl: String? = null) {
onView(withId(R.id.signInButton)).perform(click())
}
fun loginAndInitHome() {
performLogin()
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
onView(withText("OK")).perform(click())
}
fun changeAndCancelSetting(
oldValue: String,
newValue: String,
@ -110,12 +116,6 @@ fun testPreferencesFromArray(
}
}
fun goToSources() {
openMenu()
onView(withText(R.string.menu_home_sources))
.perform(click())
}
fun testAddSourceWithUrl(
url: String,
sourceName: String,
@ -139,7 +139,6 @@ fun testAddSourceWithUrl(
onView(withText(sourceName)).check(matches(isDisplayed()))
}
@Suppress("detekt:UtilityClassWithPublicConstructor")
open class WithANRException {
companion object {
// Running count of the number of Android Not Responding dialogues to prevent endless dismissal.

View File

@ -15,20 +15,17 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.hamcrest.CoreMatchers.not
import org.junit.FixMethodOrder
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
class `2-HomeActivityTest` : WithANRException() {
class HomeActivityTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
@ -37,6 +34,11 @@ class `2-HomeActivityTest` : WithANRException() {
.outerRule(activityRule)
.around(ScreenshotTakingRule())
@Before
fun init() {
loginAndInitHome()
}
@Test
fun testMenu() {
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(

View File

@ -15,18 +15,14 @@ import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.junit.After
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
class `1-LoginActivityTest` : WithANRException() {
class LoginActivityTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@ -52,7 +48,7 @@ class `1-LoginActivityTest` : WithANRException() {
}
@Test
fun `1-viewIsInitialized`() {
fun viewIsInitialized() {
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
onView(withId(R.id.selfSigned))
.check(matches(isDisplayed()))
@ -69,28 +65,28 @@ class `1-LoginActivityTest` : WithANRException() {
}
@Test
fun `2-urlError`() {
fun urlError() {
performLogin("10.0.2.2:8888")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
}
@Test
fun `3-urlSlashError`() {
performLogin("https://google.fr/toto")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
}
@Test
fun `4-connectError`() {
fun connectError() {
performLogin("http://10.0.2.2:8889")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
}
@Test
fun `5-multiError`() {
fun urlSlashError() {
performLogin("https://google.fr/toto")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
}
@Test
fun multiError() {
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.signInButton)).perform(click())
@ -98,9 +94,8 @@ class `1-LoginActivityTest` : WithANRException() {
}
@Test
fun `6-connect`() {
fun connect() {
performLogin()
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
onView(withText("OK")).perform(click())
}
}

View File

@ -22,20 +22,16 @@ import androidx.test.filters.LargeTest
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
class `4-SettingsActivityGeneralTest` : WithANRException() {
class SettingsActivityGeneralTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
@ -46,6 +42,7 @@ class `4-SettingsActivityGeneralTest` : WithANRException() {
@Before
fun init() {
loginAndInitHome()
openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext(),
)

View File

@ -1,35 +1,32 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.isNotEnabled
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@LargeTest
class `6-SettingsActivityOfflineTest` : WithANRException() {
class SettingsActivityOfflineTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
@ -45,14 +42,14 @@ class `6-SettingsActivityOfflineTest` : WithANRException() {
activityRule.scenario.onActivity { activity ->
context = activity.window.context
}
loginAndInitHome()
openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext(),
)
onView(withText(R.string.title_activity_settings)).perform(click())
onView(withText(R.string.pref_header_offline)).perform(click())
}
@After
fun back() {
onView(isRoot()).perform(ViewActions.pressBack())
}
@Suppress("detekt:LongMethod")
@Test
fun testOffline() {

View File

@ -1,33 +1,30 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@LargeTest
class `5-SettingsActivityReaderTest` : WithANRException() {
class SettingsActivityReaderTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
@ -43,14 +40,14 @@ class `5-SettingsActivityReaderTest` : WithANRException() {
activityRule.scenario.onActivity { activity ->
context = activity.window.context
}
loginAndInitHome()
openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext(),
)
onView(withText(R.string.title_activity_settings)).perform(click())
onView(withText(R.string.pref_header_viewer)).perform(click())
}
@After
fun back() {
onView(isRoot()).perform(ViewActions.pressBack())
}
@Test
fun testReader() {
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).check(

View File

@ -20,10 +20,9 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@LargeTest
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
class `3-SettingsActivityTest` : WithANRException() {
class SettingsActivityTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
@ -39,6 +38,7 @@ class `3-SettingsActivityTest` : WithANRException() {
activityRule.scenario.onActivity { activity ->
context = activity.window.context
}
loginAndInitHome()
openMenu()
onView(withText(R.string.title_activity_settings)).perform(click())
}
@ -77,9 +77,6 @@ class `3-SettingsActivityTest` : WithANRException() {
changeAndSaveSetting("", "10") {
onView(withText(R.string.pref_api_timeout)).perform(click())
}
changeAndSaveSetting("", "60") {
onView(withText(R.string.pref_api_timeout)).perform(click())
}
}
@Test

View File

@ -22,12 +22,11 @@ import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import java.util.UUID
@Suppress("ktlint:standard:class-naming", "detekt:ClassNaming")
@RunWith(AndroidJUnit4::class)
@LargeTest
class `7-SourcesActivityTest` : WithANRException() {
class SourcesActivityTest : WithANRException() {
@get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java)
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@JvmField
@Rule
@ -42,6 +41,7 @@ class `7-SourcesActivityTest` : WithANRException() {
fun init() {
sourceName = UUID.randomUUID().toString().substring(0, 15)
loginAndInitHome()
goToSources()
}
@ -83,4 +83,10 @@ class `7-SourcesActivityTest` : WithANRException() {
onView(withId(android.R.id.button1)).perform(click())
onView(withText(sourceName)).check(doesNotExist())
}
private fun goToSources() {
openMenu()
onView(withText(R.string.menu_home_sources))
.perform(click())
}
}

View File

@ -224,25 +224,16 @@ class Repository(
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
if (shouldFetch && connectivityService.isNetworkAvailable()) {
sources = sourceDetails(isDatabaseEnabled)
val apiSources = api.sourcesDetailed()
if (apiSources.success && apiSources.data != null) {
fetchedSources = true
sources = apiSources.data
if (isDatabaseEnabled) {
resetDBSourcesWithData(sources)
}
}
} else if (isDatabaseEnabled) {
sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail>
if (sources.isEmpty() && !connectivityService.isNetworkAvailable() && !fetchedSources) {
sources = sourceDetails(isDatabaseEnabled)
}
}
return sources
}
private suspend fun sourceDetails(isDatabaseEnabled: Boolean): ArrayList<SelfossModel.SourceDetail> {
var sources = ArrayList<SelfossModel.SourceDetail>()
val apiSources = api.sourcesDetailed()
if (apiSources.success && apiSources.data != null) {
fetchedSources = true
sources = apiSources.data
if (isDatabaseEnabled) {
resetDBSourcesWithData(sources)
}
}
return sources
}
@ -380,7 +371,6 @@ class Repository(
): Boolean {
var response = false
if (connectivityService.isNetworkAvailable()) {
fetchedSources = false
response = api
.createSourceForVersion(
title,
@ -402,7 +392,6 @@ class Repository(
): Boolean {
var response = false
if (connectivityService.isNetworkAvailable()) {
fetchedSources = false
response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
}
@ -417,7 +406,6 @@ class Repository(
if (connectivityService.isNetworkAvailable()) {
val response = api.deleteSource(id)
success = response.isSuccess
fetchedSources = false
}
// We filter on success or if the network isn't available