diff --git a/.gitea/workflows/on_pr.yml b/.gitea/workflows/on_pr.yml index 57a1b31..3b46f4c 100644 --- a/.gitea/workflows/on_pr.yml +++ b/.gitea/workflows/on_pr.yml @@ -48,7 +48,7 @@ jobs: adb logcat -G 16M ./gradlew JacocoDebugCodeCoverage || true ./gradlew androidApp:fetchScreenshots - adb logcat 'InputReader:S' 'chatty:S' 'audio_hw_generic:S' '*:I' -d > ./androidApp/build/reports/androidTests/connected/screenshots/logs.txt + adb logcat 'InputReader:S' 'chatty:S' 'audio_hw_generic:S' 'LogApiCalls:D' '*:I' -d > ./androidApp/build/reports/androidTests/connected/screenshots/logs.txt - uses: actions/upload-artifact@v3 if: always() with: diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/1-LoginActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/1-LoginActivityTest.kt index 2dea4a4..746d3bb 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/1-LoginActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/1-LoginActivityTest.kt @@ -93,5 +93,6 @@ class `1-LoginActivityTest` : WithANRException() { performLogin() onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed())) onView(withText("OK")).perform(click()) + checkHomeLoadingDone() } } diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt index 9f19d07..c3351f7 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt @@ -15,6 +15,7 @@ import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.base.DefaultFailureHandler +import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isNotChecked @@ -24,6 +25,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.CoreMatchers.not import org.hamcrest.Matchers.hasToString import org.junit.BeforeClass import java.io.BufferedOutputStream @@ -139,6 +141,10 @@ fun testAddSourceWithUrl( onView(withText(sourceName)).check(matches(isDisplayed())) } +fun checkHomeLoadingDone() { + onView(withId(R.id.swipeRefreshLayout)).inRoot(not(isDialog())).perform(waitUntilNotLoading(300000)) +} + @Suppress("detekt:UtilityClassWithPublicConstructor") open class WithANRException { companion object { diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt index 207afcf..2b926a7 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt @@ -8,9 +8,13 @@ import android.widget.RelativeLayout import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.graphics.drawable.toBitmap +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu +import androidx.test.espresso.PerformException import androidx.test.espresso.Root +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup import androidx.test.espresso.matcher.ViewMatchers.hasSibling import androidx.test.espresso.matcher.ViewMatchers.withChild @@ -19,11 +23,15 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withParent import androidx.test.espresso.matcher.ViewMatchers.withResourceName import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.util.HumanReadables +import androidx.test.espresso.util.TreeIterables import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.CoreMatchers.any import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.Matchers import org.hamcrest.TypeSafeMatcher +import java.util.concurrent.TimeoutException fun withError( @StringRes id: Int, @@ -44,6 +52,47 @@ fun withError( } } +fun waitUntilNotLoading(millis: Long): ViewAction { + return object : ViewAction { + override fun getConstraints(): Matcher = any(View::class.java) + + override fun getDescription(): String = "wait for a specific view is not loading during $millis millis." + + override fun perform( + uiController: UiController, + view: View?, + ) { + uiController.loopMainThreadUntilIdle() + val startTime = System.currentTimeMillis() + val endTime = startTime + millis + + do { + // either the empty view is displayed + for (child in TreeIterables.breadthFirstViewTraversal(view)) { + // found view with required ID + if (withId(R.id.emptyText).matches(child) && !child.isShown) { + return + } + } + + // or the refresh layout is refreshing + if (view is SwipeRefreshLayout && !view.isRefreshing) { + return + } + uiController.loopMainThreadForAtLeast(100) + } while (System.currentTimeMillis() < endTime) + + // timeout happens + throw PerformException + .Builder() + .withActionDescription(this.description) + .withViewDescription(HumanReadables.describe(view)) + .withCause(TimeoutException()) + .build() + } + } +} + fun isPopupWindow(): Matcher = isPlatformPopup() fun withDrawable(