Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3975c3f
- espresso fixes
Williamrai Nov 25, 2025
f574a41
- adds an optional action to scroll to in ListActions.kt
Williamrai Nov 26, 2025
8497ece
Merge branch 'main' into espresso-fixes
Williamrai Nov 26, 2025
c31286f
- fixes AboutSettingsTest
Williamrai Nov 26, 2025
b8f0c44
Merge remote-tracking branch 'origin/espresso-fixes' into espresso-fixes
Williamrai Nov 26, 2025
861cae3
Merge branch 'main' into espresso-fixes
Williamrai Nov 26, 2025
c2e9a87
Merge branch 'main' into espresso-fixes
Williamrai Nov 26, 2025
8a0bf68
- updates github actions workflow, adds TestSuite to smoke_test workf;pw
Williamrai Nov 26, 2025
1b22cee
Merge remote-tracking branch 'origin/espresso-fixes' into espresso-fixes
Williamrai Nov 26, 2025
897bf18
- updates github workflow to use ubuntu runners
Williamrai Nov 26, 2025
e703b6f
- fixes permission issue for api below 33
Williamrai Dec 1, 2025
7aa423a
Merge branch 'main' into espresso-fixes
Williamrai Dec 1, 2025
3202bb0
- update workflow
Williamrai Dec 1, 2025
09c8243
Merge remote-tracking branch 'origin/espresso-fixes' into espresso-fixes
Williamrai Dec 1, 2025
cf0f481
- add "error code" to test espresso github action workflow
Williamrai Dec 1, 2025
1c2d2f5
- revert code to original
Williamrai Dec 1, 2025
ca37a12
Merge branch 'main' into espresso-fixes
Williamrai Dec 2, 2025
9392ba8
- fixes changLanguage test and NavigationItemTest
Williamrai Dec 2, 2025
eee3bb2
Merge remote-tracking branch 'origin/espresso-fixes' into espresso-fixes
Williamrai Dec 2, 2025
b5f4ede
Merge branch 'main' into espresso-fixes
Williamrai Dec 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions .github/workflows/android_offline_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@ env:

jobs:
instrumentation-tests:
runs-on: macos-latest
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- api-level: 23
target: google_apis
arch: x86_64

- api-level: 36
target: google_apis
arch: x86_64

steps:
- uses: actions/checkout@v6
Expand All @@ -19,15 +30,24 @@ jobs:
cache: 'gradle'
- uses: gradle/gradle-build-action@v3

- name: Instrumentation Tests
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: run instrumentation tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
disable-animations: true
script: ./gradlew connectedDevDebugAndroidTest --stacktrace --no-daemon --info -P android.testInstrumentationRunnerArguments.class=org.wikipedia.tests.offline.SavedArticleOnlineOfflineTest

- name: Upload results
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: instrumentation-test-results ${{ matrix.api-level }}
name: instrumentation-test-results${{ matrix.api-level }}
path: ./**/build/reports/androidTests/connected/**
28 changes: 24 additions & 4 deletions .github/workflows/android_smoke_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@ env:

jobs:
instrumentation-tests:
runs-on: macos-latest
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- api-level: 23
target: google_apis
arch: x86_64

- api-level: 36
target: google_apis
arch: x86_64

steps:
- uses: actions/checkout@v6
Expand All @@ -19,15 +30,24 @@ jobs:
cache: 'gradle'
- uses: gradle/gradle-build-action@v3

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Instrumentation Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedDevDebugAndroidTest --stacktrace --no-daemon
api-level: ${{ matrix.api-level }}
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
disable-animations: true
script: ./gradlew connectedDevDebugAndroidTest --stacktrace --no-daemon --info -P android.testInstrumentationRunnerArguments.class=org.wikipedia.testsuites.TestSuite

- name: Upload results
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: instrumentation-test-results ${{ matrix.api-level }}
name: instrumentation-test-results${{ matrix.api-level }}
path: ./**/build/reports/androidTests/connected/**
6 changes: 3 additions & 3 deletions app/src/androidTest/java/org/wikipedia/base/BaseRobot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import org.wikipedia.base.actions.WebActions
import java.util.concurrent.TimeUnit

abstract class BaseRobot {
protected val composeTestRule: ComposeTestRule
get() = ComposeTestManager.getComposeTestRule()

protected val click = ClickActions()
protected val input = InputActions()
protected val list = ListActions()
Expand All @@ -24,9 +27,6 @@ abstract class BaseRobot {
protected val verify = VerificationActions()
protected val web = WebActions()

protected val composeTestRule: ComposeTestRule
get() = ComposeTestManager.getComposeTestRule()

protected fun delay(seconds: Long) {
onView(isRoot()).perform(waitOnId(TimeUnit.SECONDS.toMillis(seconds)))
}
Expand Down
25 changes: 17 additions & 8 deletions app/src/androidTest/java/org/wikipedia/base/BaseTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.wikipedia.base
import android.Manifest
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.espresso.IdlingPolicies
Expand Down Expand Up @@ -40,6 +41,8 @@ data class DataInjector(
val readingListShareTooltipShown: Boolean = true,
val otdEntryDialogShown: Boolean = true,
val enableYearInReview: Boolean = false,
val yearInReviewReadingListSurveyShown: Boolean = false,
val exploreFeedSurveyShown: Boolean = false
)

abstract class BaseTest<T : AppCompatActivity>(
Expand All @@ -56,9 +59,11 @@ abstract class BaseTest<T : AppCompatActivity>(
var composeTestRule = createComposeRule()

@get:Rule
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.POST_NOTIFICATIONS
)
val permissionRule: GrantPermissionRule = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
GrantPermissionRule.grant(Manifest.permission.POST_NOTIFICATIONS)
} else {
GrantPermissionRule.grant()
}

protected lateinit var activity: T
protected lateinit var device: UiDevice
Expand All @@ -67,11 +72,15 @@ abstract class BaseTest<T : AppCompatActivity>(
init {
val intent = Intent(context, activityClass)
activityScenarioRule = ActivityScenarioRule(intent)
Prefs.isInitialOnboardingEnabled = dataInjector.isInitialOnboardingEnabled
Prefs.showOneTimeCustomizeToolbarTooltip = dataInjector.showOneTimeCustomizeToolbarTooltip
Prefs.readingListShareTooltipShown = dataInjector.readingListShareTooltipShown
Prefs.otdEntryDialogShown = dataInjector.otdEntryDialogShown
Prefs.isYearInReviewEnabled = dataInjector.enableYearInReview
Prefs.apply {
isInitialOnboardingEnabled = dataInjector.isInitialOnboardingEnabled
showOneTimeCustomizeToolbarTooltip = dataInjector.showOneTimeCustomizeToolbarTooltip
readingListShareTooltipShown = dataInjector.readingListShareTooltipShown
otdEntryDialogShown = dataInjector.otdEntryDialogShown
isYearInReviewEnabled = dataInjector.enableYearInReview
yearInReviewReadingListSurveyShown = dataInjector.yearInReviewReadingListSurveyShown
exploreFeedSurveyShown = dataInjector.exploreFeedSurveyShown
}
dataInjector.overrideEditsContribution?.let {
Prefs.overrideSuggestedEditContribution = it
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.test.espresso.ViewAction
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.doubleClick
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
Expand Down Expand Up @@ -49,7 +50,7 @@ class ClickActions {
onView(allOf(withContentDescription(description), isDisplayed())).perform(click())
}

fun onDisplayedViewWithIdAnContentDescription(
fun onDisplayedViewWithIdAndContentDescription(
@IdRes viewId: Int,
description: String
) {
Expand All @@ -62,6 +63,20 @@ class ClickActions {
onView(withText(text)).perform(click())
}

fun onParentViewWithChildIdAndText(@IdRes childId: Int, text: String) {
onView(
allOf(
hasDescendant(
allOf(
withId(childId),
withText(text)
)
),
isDisplayed()
)
).perform(click())
}

fun doubleClickOnViewWithId(@IdRes viewId: Int) {
onView(
allOf(
Expand Down Expand Up @@ -134,9 +149,9 @@ class ClickActions {
.inRoot(isDialog())
.perform(click())
} catch (e: NoMatchingViewException) {
Log.e("BaseRobot", "$errorString")
Log.e("DialogRobot", errorString)
} catch (e: Exception) {
Log.e("BaseRobot", "Unexpected Error: ${e.message}")
Log.e("DialogRobot", "Unexpected Error: ${e.message}")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewAssertion
Expand All @@ -25,6 +26,8 @@ import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.google.android.material.tabs.TabLayout
import junit.framework.AssertionFailedError
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
Expand Down Expand Up @@ -223,6 +226,50 @@ class ListActions {
)
}

fun isItemPresent(
recyclerViewId: Int = R.id.feed_view,
title: String,
textViewId: Int = R.id.view_card_header_title
): Boolean {
return try {
onView(withId(recyclerViewId))
.check(matches(hasDescendant(allOf(
withId(textViewId),
withText(title)
))))
true
} catch (_: NoMatchingViewException) {
false
} catch (_: AssertionFailedError) {
false
}
}

fun selectTabWithText(@IdRes viewId: Int, text: String) {
onView(withId(viewId))
.perform(object : ViewAction {
override fun getConstraints(): Matcher<View?> = isDisplayed()

override fun getDescription(): String = "Select tab with text: $text"

override fun perform(
uiController: UiController,
view: View
) {
val tabLayout = view as TabLayout
for (i in 0 until tabLayout.tabCount) {
val tab = tabLayout.getTabAt(i)
val labelView = tab?.customView?.findViewById<TextView>(R.id.language_label)
if (labelView?.text == text) {
tab.select()
break
}
}
uiController.loopMainThreadUntilIdle()
}
})
}

private fun verifyItemAtPosition(
recyclerViewId: Int,
position: Int,
Expand Down
14 changes: 13 additions & 1 deletion app/src/androidTest/java/org/wikipedia/robots/DialogRobot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@ package org.wikipedia.robots

import BaseRobot
import android.content.Context
import android.util.Log
import androidx.test.espresso.NoMatchingViewException
import org.wikipedia.R

class DialogRobot : BaseRobot() {

fun dismissSurveyDialog() = apply {
click.ifDialogShown("No thanks", errorString = "No Survey Dialog dialog shown.")
}

fun dismissContributionDialog() = apply {
click.ifDialogShown("No thanks", errorString = "No Contribution dialog shown.")
}

fun dismissBigEnglishDialog() = apply {
click.ifDialogShown("Maybe later", errorString = "No Big English dialog shown.")
try {
click.onDisplayedViewWithIdAndContentDescription(R.id.closeButton, "Close")
} catch (e: NoMatchingViewException) {
Log.e("DialogRobot", "No Big English Dialog shown.")
} catch (e: Exception) {
Log.e("DialogRobot", "Unexpected Error: ${e.message}")
}
}

fun clickLogOutUser() = apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,24 @@ class ExploreFeedRobot : BaseRobot() {
delay(TestConfig.DELAY_SWIPE_TO_REFRESH)
}

fun scrollToItem(
fun scrollToAndPerform(
recyclerViewId: Int = R.id.feed_view,
title: String,
textViewId: Int = R.id.view_card_header_title,
verticalOffset: Int = 200
verticalOffset: Int = 200,
action: (ExploreFeedRobot.() -> Unit)? = null
) = apply {
list.scrollToRecyclerView(
recyclerViewId,
title,
textViewId,
verticalOffset
)
if (list.isItemPresent(title = title)) {
list.scrollToRecyclerView(
recyclerViewId,
title,
textViewId,
verticalOffset
)
action?.invoke(this@ExploreFeedRobot)
} else {
Log.i("ExploreFeed", "$title not present today - skipping test")
}
}

fun assertFeaturedArticleTitleColor(theme: Theme) = apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,11 @@ class PageRobot(private val context: Context) : BaseRobot() {
}

fun openLanguageSelector() = apply {
click.onDisplayedViewWithIdAnContentDescription(R.id.page_language, context.getString(R.string.article_menu_bar_language_button))
click.onDisplayedViewWithIdAndContentDescription(R.id.page_language, context.getString(R.string.article_menu_bar_language_button))
}

fun openFindInArticle() = apply {
click.onDisplayedViewWithIdAnContentDescription(R.id.page_find_in_article, context.getString(R.string.menu_page_find_in_page))
click.onDisplayedViewWithIdAndContentDescription(R.id.page_find_in_article, context.getString(R.string.menu_page_find_in_page))
}

fun verifyFindInArticleCount(count: String) = apply {
Expand All @@ -281,11 +281,11 @@ class PageRobot(private val context: Context) : BaseRobot() {
}

fun openThemeSelector() = apply {
click.onDisplayedViewWithIdAnContentDescription(R.id.page_theme, context.getString(R.string.article_menu_bar_theme_button))
click.onDisplayedViewWithIdAndContentDescription(R.id.page_theme, context.getString(R.string.article_menu_bar_theme_button))
}

fun openTableOfContents() = apply {
click.onDisplayedViewWithIdAnContentDescription(R.id.page_contents, context.getString(R.string.article_menu_bar_contents_button))
click.onDisplayedViewWithIdAndContentDescription(R.id.page_contents, context.getString(R.string.article_menu_bar_contents_button))
delay(TestConfig.DELAY_SHORT)
}

Expand Down
Loading
Loading