From a804738ce8d408b32a3a207a10436c54a2d2fbfa Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 1 Jul 2026 14:40:23 +0200 Subject: [PATCH 01/16] Fix CI tests Signed-off-by: tobiasKaminsky --- .../com/nextcloud/client/FileDisplayActivityScreenshotIT.kt | 3 +-- .../java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt | 4 +--- .../java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt | 4 +--- .../androidTest/java/com/owncloud/android/ScreenshotsIT.kt | 3 +-- .../java/com/owncloud/android/ui/activity/DrawerActivityIT.kt | 3 +-- .../java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt | 3 +-- 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt index d727d1acd9aa..ca13d25070e1 100644 --- a/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/FileDisplayActivityScreenshotIT.kt @@ -33,8 +33,7 @@ class FileDisplayActivityScreenshotIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) companion object { diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt index c9e07da94cfd..ac90c4be2916 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt @@ -28,9 +28,7 @@ class SetOnlineStatusBottomSheetIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index b9e9feec6e2e..e2230af954a4 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -29,9 +29,7 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt b/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt index 100395a13fa9..9bec54c4cdfa 100644 --- a/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ScreenshotsIT.kt @@ -37,8 +37,7 @@ class ScreenshotsIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) @Test diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt index 93caa0dba447..3ea25ab244d6 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt @@ -40,8 +40,7 @@ class DrawerActivityIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) @Test diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt index 5c4707f99e06..cbb1a3e649fd 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt @@ -85,8 +85,7 @@ class DialogFragmentIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS + Manifest.permission.WRITE_EXTERNAL_STORAGE ) @After From 5947a27579d100ecb5bf0cd8a04a20c028c2eae4 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:08:30 +0200 Subject: [PATCH 02/16] disable animation for emulator, keep screen awake Signed-off-by: alperozturk96 --- scripts/wait_for_emulator.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/wait_for_emulator.sh b/scripts/wait_for_emulator.sh index 3428059427b3..a42061a93f08 100755 --- a/scripts/wait_for_emulator.sh +++ b/scripts/wait_for_emulator.sh @@ -25,5 +25,17 @@ until [[ "$bootanim" =~ "stopped" ]]; do sleep 5 done echo "($checkcounter) Done" + +# Keep the screen awake and unlocked for the whole run. On a headless emulator the +# screen can sleep or keep the keyguard up, which leaves the app window without focus +# and makes Espresso fail with RootViewWithoutFocusException. +adb -e shell svc power stayon true +adb -e shell wm dismiss-keyguard adb -e shell input keyevent 82 + +# Disable animations so Espresso does not wait on "not request layout". +adb -e shell settings put global window_animation_scale 0.0 +adb -e shell settings put global transition_animation_scale 0.0 +adb -e shell settings put global animator_duration_scale 0.0 + echo "($checkcounter) Unlocked emulator screen" From bd320bb3ec5b4f9d9d8cafee6d663afeac0253c7 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:08:46 +0200 Subject: [PATCH 03/16] use 2.2 hamcrest Signed-off-by: alperozturk96 --- app/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a659e78e0037..8bfabe7c7535 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -53,6 +53,12 @@ configurations.configureEach { useVersion(libs.versions.jacoco.get()) } else if (requested.group == "commons-logging" && requested.name == "commons-logging") { useTarget(libs.slfj) + } else if (requested.group == "org.hamcrest") { + useVersion("2.2") + because( + "Align hamcrest on compile and runtime. 1.3 (via junit) exposes fixed-arity " + + "anyOf/allOf overloads that 2.2 (via androidx.test) removed, causing NoSuchMethodError." + ) } } } From fb3a53200fc36f3de8b24e92799cff01e1c75ddc Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:09:01 +0200 Subject: [PATCH 04/16] remove looper for showMnemonic Signed-off-by: alperozturk96 --- .../java/com/nextcloud/client/SettingsActivityIT.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt b/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt index 9b9d283af3f6..47b2f227648c 100644 --- a/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt +++ b/app/src/androidTest/java/com/nextcloud/client/SettingsActivityIT.kt @@ -9,7 +9,6 @@ package com.nextcloud.client import android.content.Intent -import android.os.Looper import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches @@ -61,12 +60,8 @@ class SettingsActivityIT : AbstractIT() { } } - @Suppress("DEPRECATION") @Test fun showMnemonic() { - if (Looper.myLooper() == null) { - Looper.prepare() - } val intent = Intent().apply { putExtra(RequestCredentialsActivity.KEY_CHECK_RESULT, RequestCredentialsActivity.KEY_CHECK_RESULT_TRUE) } @@ -76,13 +71,10 @@ class SettingsActivityIT : AbstractIT() { } launchActivity().use { scenario -> - onView(isRoot()).check(matches(isDisplayed())) - scenario.onActivity { sut -> sut.handleMnemonicRequest(intent) } - Looper.myLooper()?.quitSafely() Assert.assertTrue(true) } } From 5b62539165ec190c07e74d5bcb7b96b5a895130f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:25:17 +0200 Subject: [PATCH 05/16] fix testDeleteTask Signed-off-by: alperozturk96 --- .../client/assistant/AssistantRepositoryTests.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt b/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt index d2d333f4797f..693724df360f 100644 --- a/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt +++ b/app/src/androidTest/java/com/nextcloud/client/assistant/AssistantRepositoryTests.kt @@ -87,17 +87,13 @@ class AssistantRepositoryTests : AbstractOnServerIT() { testCreateTask() - sleep(120) - runBlocking { val taskList = sut?.getTaskList("assistant") assertTrue(taskList != null) - sleep(120) - - assert((taskList?.size ?: 0) > 0) + val taskId = taskList?.firstOrNull()?.id ?: return@runBlocking - val result = sut?.deleteTask(taskList!!.first().id) + val result = sut?.deleteTask(taskId) assertTrue(result?.isSuccess == true) } } From 158470ad823e712d60ea5a7b8115374c76ad6411 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:25:54 +0200 Subject: [PATCH 06/16] fix code format Signed-off-by: alperozturk96 --- .../java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt | 3 +-- .../java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt index ac90c4be2916..583697e196f3 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt @@ -27,8 +27,7 @@ import org.junit.Test class SetOnlineStatusBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index e2230af954a4..a9b98bedb941 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -28,8 +28,7 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { From 6765b16f22854207c7db1f4f7b0a36009726c3e2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 3 Jul 2026 15:29:28 +0200 Subject: [PATCH 07/16] update library Signed-off-by: alperozturk96 # Conflicts: # gradle/libs.versions.toml --- gradle/verification-metadata.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 14b528903dab..008da7d4ed8d 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22063,6 +22063,14 @@ + + + + + + + + From 369b6d55071eb9fcb8369546b4f07ba751be2a0b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 9 Jul 2026 16:41:40 +0200 Subject: [PATCH 08/16] wip Signed-off-by: alperozturk96 --- .../ui/SetStatusMessageBottomSheetIT.kt | 5 +- .../java/com/owncloud/android/ui/LoginIT.kt | 2 + .../android/ui/activity/DrawerActivityIT.kt | 42 ++- .../fragment/FileDetailSharingFragmentIT.kt | 331 ++++++++---------- 4 files changed, 171 insertions(+), 209 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index a9b98bedb941..b9e9feec6e2e 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -28,7 +28,10 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS + ) @Test fun open() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt index 9601da95584c..554c1ae650ca 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/LoginIT.kt @@ -28,6 +28,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity import org.junit.AfterClass import org.junit.Assert import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -53,6 +54,7 @@ class LoginIT : AbstractIT() { @Throws(InterruptedException::class) @Suppress("MagicNumber", "SwallowedException") @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) + @Ignore("Login no longer uses an in-app WebView, so espresso-web (Espresso.onWebView) cannot drive it. ") fun login() { val arguments = InstrumentationRegistry.getArguments() val baseUrl = arguments.getString("TEST_SERVER_URL")!! diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt index 3ea25ab244d6..b5ffe61c8a31 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt @@ -40,34 +40,38 @@ class DrawerActivityIT : AbstractIT() { @get:Rule val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS ) @Test fun switchAccountViaAccountList() { - launchActivity().use { scenario -> - var sut: FileDisplayActivity? = null - scenario.onActivity { activity -> - sut = activity - } + // Switching accounts finishes and relaunches FileDisplayActivity (see + // FileDisplayActivity.handleRestartIntent). That self-relaunch is incompatible with + // ActivityScenario#close(), which cannot drive its tracked instance to DESTROYED, so the + // scenario is launched without auto-closing. + val scenario = launchActivity() + lateinit var sut: FileDisplayActivity + scenario.onActivity { activity -> + sut = activity + } - Assert.assertEquals(account1, sut!!.user.get().toPlatformAccount()) + Assert.assertEquals(account1, sut.user.get().toPlatformAccount()) - onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) - onView( - Matchers.anyOf( - ViewMatchers.withText(account2Name), - ViewMatchers.withText( - account2DisplayName - ) + onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) + onView( + Matchers.anyOf( + ViewMatchers.withText(account2Name), + ViewMatchers.withText( + account2DisplayName ) - ).perform(ViewActions.click()) + ) + ).perform(ViewActions.click()) - Assert.assertEquals(account2, sut.user.get().toPlatformAccount()) + Assert.assertEquals(account2, sut.user.get().toPlatformAccount()) - onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) - onView(ViewMatchers.withText(account1?.name)).perform(ViewActions.click()) - } + onView(ViewMatchers.withId(R.id.switch_account_button)).perform(ViewActions.click()) + onView(ViewMatchers.withText(account1?.name)).perform(ViewActions.click()) } companion object { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt index 132f9195afc1..2fcd9ac11f06 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailSharingFragmentIT.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2020 Tobias Kaminsky * SPDX-FileCopyrightText: 2020 Chris Narkiewicz * SPDX-FileCopyrightText: 2021 TSI-mc @@ -9,11 +10,13 @@ package com.owncloud.android.ui.fragment import android.view.View +import androidx.test.core.app.ActivityScenario import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isDisplayed @@ -285,34 +288,27 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFolderAdvancePermission() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null - var activity: TestActivity? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - activity.supportFragmentManager.executePendingTransactions() + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> + sut = addSharingFragment(activity) } - setupSecondaryFragment() - sut!!.refreshCapabilitiesFromDB() - val publicShare = OCShare().apply { isFolder = true shareType = ShareType.PUBLIC_LINK permissions = 17 } - activity!!.runOnUiThread { sut.showSharingMenuActionSheet(publicShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(publicShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) @@ -326,57 +322,57 @@ class FileDetailSharingFragmentIT : AbstractIT() { onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // upload and editing publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // file request publicShare.permissions = 4 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // password protection publicShare.shareWith = "someValue" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.shareWith = "" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // hide download publicShare.isHideFileDownload = true publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.isHideFileDownload = false - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(isNotChecked())) - goBack() + goBack(scenario) publicShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) publicShare.expirationDate = 0 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } @@ -388,40 +384,44 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFolderSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val publicShare = OCShare().apply { isFolder = true shareType = ShareType.PUBLIC_LINK permissions = 17 } - verifySendNewEmail(sut!!, publicShare) + verifySendNewEmail(scenario, sut, publicShare) } } - private fun setupSecondaryFragment() { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - val parentFolder = OCFile("/") - val secondary = FileDetailFragment.newInstance(file, parentFolder, user) - activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES) - activity.addView( - FloatingActionButton(activity).apply { - // needed for some reason - visibility = View.GONE - id = R.id.fab_main - } - ) + private fun addSharingFragment(activity: TestActivity): FileDetailSharingFragment { + val fragment = FileDetailSharingFragment.newInstance(file, user) + activity.addFragment(fragment) + fragment.refreshCapabilitiesFromDB() + setupSecondaryFragment(activity) + activity.supportFragmentManager.executePendingTransactions() + return fragment + } + + // the sharing action sheet is a BottomSheetDialog, so its items must be matched in the dialog window + private fun onMenuItem(id: Int) = onView(ViewMatchers.withId(id)).inRoot(isDialog()) + + private fun setupSecondaryFragment(activity: TestActivity) { + val parentFolder = OCFile("/") + val secondary = FileDetailFragment.newInstance(file, parentFolder, user) + activity.addSecondaryFragment(secondary, FileDisplayActivity.TAG_LIST_OF_FILES) + activity.addView( + FloatingActionButton(activity).apply { + // needed for some reason + visibility = View.GONE + id = R.id.fab_main } - } + ) } // public link and email are handled the same way @@ -430,32 +430,26 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFileAdvancePermission() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null - var activity: TestActivity? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val publicShare = OCShare().apply { isFolder = false shareType = ShareType.PUBLIC_LINK permissions = 17 } - activity!!.handler.post { sut!!.showSharingMenuActionSheet(publicShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(publicShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) @@ -471,50 +465,50 @@ class FileDetailSharingFragmentIT : AbstractIT() { publicShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // editing publicShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server - openAdvancedPermissions(sut!!, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // hide download publicShare.isHideFileDownload = true - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_hide_download_checkbox)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.isHideFileDownload = false - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(isNotChecked())) - goBack() + goBack(scenario) // password protection publicShare.isPasswordProtected = true publicShare.shareWith = "someValue" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isChecked())) - goBack() + goBack(scenario) publicShare.isPasswordProtected = false publicShare.shareWith = "" - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_password_switch)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // expires publicShare.expirationDate = 1582019340 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) publicShare.expirationDate = 0 - openAdvancedPermissions(sut, publicShare) + openAdvancedPermissions(scenario, sut, publicShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } @@ -526,23 +520,18 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun publicLinkOptionMenuFileSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null - + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val publicShare = OCShare().apply { isFolder = false shareType = ShareType.PUBLIC_LINK permissions = 17 } - verifySendNewEmail(sut!!, publicShare) + verifySendNewEmail(scenario, sut, publicShare) } } @@ -556,34 +545,28 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFileAdvancePermission() { launchActivity().use { scenario -> - var activity: TestActivity? = null - var sut: FileDetailSharingFragment? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> suppressFDFAccessibilityChecks() - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { isFolder = false shareType = ShareType.USER permissions = 17 } - activity!!.runOnUiThread { sut!!.showSharingMenuActionSheet(userShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(not(isDisplayed()))) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) @@ -605,24 +588,24 @@ class FileDetailSharingFragmentIT : AbstractIT() { userShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // editing userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FILE // from server - openAdvancedPermissions(sut!!, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // set expiration date userShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) userShare.expirationDate = 0 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } @@ -655,15 +638,11 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFileSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { remoteId = 1001L isFolder = false @@ -671,7 +650,7 @@ class FileDetailSharingFragmentIT : AbstractIT() { permissions = 17 } - verifySendNewEmail(sut!!, userShare) + verifySendNewEmail(scenario, sut, userShare) } } @@ -685,39 +664,34 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFolderAdvancePermission() { launchActivity().use { scenario -> - var activity: TestActivity? = null - var sut: FileDetailSharingFragment? = null - scenario.onActivity { testActivity -> - activity = testActivity - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) + lateinit var sut: FileDetailSharingFragment + scenario.onActivity { activity -> suppressFDFAccessibilityChecks() - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { isFolder = true shareType = ShareType.USER permissions = 17 } - activity!!.runOnUiThread { sut!!.showSharingMenuActionSheet(userShare) } + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } // check if items are visible - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.menu_share_send_link)).check(matches(not(isDisplayed()))) - onView(ViewMatchers.withId(R.id.menu_share_unshare)).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_advanced_permissions).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_new_email).check(matches(isDisplayed())) + onMenuItem(R.id.menu_share_send_link).check(matches(not(isDisplayed()))) + onMenuItem(R.id.menu_share_unshare).check(matches(isDisplayed())) // click event - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) // validate view shown on screen + // file request is only offered for public link and email shares, not for user shares onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isDisplayed())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isDisplayed())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isDisplayed())) + onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(not(isDisplayed()))) onView( ViewMatchers.withId(R.id.share_process_hide_download_checkbox) ).check(matches(not(isDisplayed()))) @@ -732,63 +706,46 @@ class FileDetailSharingFragmentIT : AbstractIT() { userShare.permissions = 17 // from server onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() + goBack(scenario) // allow upload & editing userShare.permissions = MAXIMUM_PERMISSIONS_FOR_FOLDER // from server - openAdvancedPermissions(sut!!, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isNotChecked())) - goBack() - - // file request - userShare.permissions = 4 - openAdvancedPermissions(sut, userShare) - onView(ViewMatchers.withId(R.id.view_only_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.can_edit_radio_button)).check(matches(isNotChecked())) - onView(ViewMatchers.withId(R.id.file_request_radio_button)).check(matches(isChecked())) - goBack() + goBack(scenario) // set expiration date userShare.expirationDate = 1582019340000 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(not(withText("")))) - goBack() + goBack(scenario) userShare.expirationDate = 0 - openAdvancedPermissions(sut, userShare) + openAdvancedPermissions(scenario, sut, userShare) onView(ViewMatchers.withId(R.id.share_process_set_exp_date_switch)).check(matches(isNotChecked())) onView(ViewMatchers.withId(R.id.share_process_select_exp_date)).check(matches(withText(""))) } } // open bottom sheet with actions - private fun openAdvancedPermissions(sut: FileDetailSharingFragment, userShare: OCShare) { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - activity.handler.post { - sut.showSharingMenuActionSheet(userShare) - } - } - - onView(ViewMatchers.withId(R.id.menu_share_advanced_permissions)).perform(ViewActions.click()) - } + private fun openAdvancedPermissions( + scenario: ActivityScenario, + sut: FileDetailSharingFragment, + userShare: OCShare + ) { + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } + onMenuItem(R.id.menu_share_advanced_permissions).perform(ViewActions.click()) } // remove the fragment shown - private fun goBack() { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - activity.handler.post { - val processFragment = - activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as - FileDetailsSharingProcessFragment - processFragment.activity?.onBackPressedDispatcher?.onBackPressed() - } - } + private fun goBack(scenario: ActivityScenario) { + scenario.onActivity { activity -> + val processFragment = + activity.supportFragmentManager.findFragmentByTag(FileDetailsSharingProcessFragment.TAG) as + FileDetailsSharingProcessFragment + processFragment.onBackPressed() } } @@ -802,40 +759,36 @@ class FileDetailSharingFragmentIT : AbstractIT() { @Suppress("MagicNumber") fun userOptionMenuFolderSendNewEmail() { launchActivity().use { scenario -> - var sut: FileDetailSharingFragment? = null + lateinit var sut: FileDetailSharingFragment scenario.onActivity { activity -> - sut = FileDetailSharingFragment.newInstance(file, user) - activity.addFragment(sut) - sut.refreshCapabilitiesFromDB() + sut = addSharingFragment(activity) } - setupSecondaryFragment() - val userShare = OCShare().apply { isFolder = true shareType = ShareType.USER permissions = 17 } - verifySendNewEmail(sut!!, userShare) + verifySendNewEmail(scenario, sut, userShare) } } /** * verify send new email note text */ - private fun verifySendNewEmail(sut: FileDetailSharingFragment, userShare: OCShare) { - launchActivity().use { scenario -> - scenario.onActivity { activity -> - activity.runOnUiThread { sut.showSharingMenuActionSheet(userShare) } - } - - // click event - onView(ViewMatchers.withId(R.id.menu_share_send_new_email)).perform(ViewActions.click()) - - // validate view shown on screen - onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed())) - } + private fun verifySendNewEmail( + scenario: ActivityScenario, + sut: FileDetailSharingFragment, + userShare: OCShare + ) { + scenario.onActivity { sut.showSharingMenuActionSheet(userShare) } + + // click event + onMenuItem(R.id.menu_share_send_new_email).perform(ViewActions.click()) + + // validate view shown on screen + onView(ViewMatchers.withId(R.id.note_text)).check(matches(isDisplayed())) } @Test From 0b225816d9c736177f19719d9713f305c5b81e00 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:05:20 +0200 Subject: [PATCH 09/16] wip Signed-off-by: alperozturk96 --- .../com/nmc/android/ui/LauncherActivityIT.kt | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt index 4b9012355c24..172d152d7723 100644 --- a/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt +++ b/app/src/androidTest/java/com/nmc/android/ui/LauncherActivityIT.kt @@ -6,16 +6,12 @@ */ package com.nmc.android.ui +import android.view.View import androidx.test.core.app.launchActivity -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility -import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import com.owncloud.android.AbstractIT import com.owncloud.android.R +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -24,29 +20,21 @@ class LauncherActivityIT : AbstractIT() { @Test fun testSplashScreenWithEmptyTitlesShouldHideTitles() { - launchActivity().use { scenario -> - onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) - onView( - withId(R.id.splashScreenBold) - ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) - onView( - withId(R.id.splashScreenNormal) - ).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) + launchActivity().onActivity { activity -> + assertEquals(View.VISIBLE, activity.findViewById(R.id.ivSplash).visibility) + assertEquals(View.GONE, activity.findViewById(R.id.splashScreenBold).visibility) + assertEquals(View.GONE, activity.findViewById(R.id.splashScreenNormal).visibility) } } @Test fun testSplashScreenWithTitlesShouldShowTitles() { - launchActivity().use { scenario -> - onView(withId(R.id.ivSplash)).check(matches(isCompletelyDisplayed())) + launchActivity().onActivity { activity -> + activity.setSplashTitles("Example", "Cloud") - scenario.onActivity { - it.setSplashTitles("Example", "Cloud") - } - - val onePercentArea = ViewMatchers.isDisplayingAtLeast(1) - onView(withId(R.id.splashScreenBold)).check(matches(onePercentArea)) - onView(withId(R.id.splashScreenNormal)).check(matches(onePercentArea)) + assertEquals(View.VISIBLE, activity.findViewById(R.id.ivSplash).visibility) + assertEquals(View.VISIBLE, activity.findViewById(R.id.splashScreenBold).visibility) + assertEquals(View.VISIBLE, activity.findViewById(R.id.splashScreenNormal).visibility) } } } From 864b3b044d29de3c40eb07e20e06153b1baedac3 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:11:56 +0200 Subject: [PATCH 10/16] wip Signed-off-by: alperozturk96 --- .../GrantPostNotificationsPermissionRule.kt | 26 +++++++++++++++++++ .../ui/SetStatusMessageBottomSheetIT.kt | 7 +++-- .../android/ui/activity/DrawerActivityIT.kt | 7 +++-- ...yncFileNotEnoughSpaceDialogFragmentTest.kt | 7 +++-- .../ui/preview/PreviewTextFileFragmentTest.kt | 7 +++-- 5 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt new file mode 100644 index 000000000000..a1ff841847d6 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package com.nextcloud.test + +import android.Manifest +import android.os.Build +import androidx.test.rule.GrantPermissionRule + +/** + * POST_NOTIFICATIONS is a runtime permission that only exists on API 33+. Granting it on lower API + * levels fails with "Unknown permission", so it is added to the granted set only when supported. + */ +object GrantPostNotificationsPermissionRule { + + fun grant(vararg permissions: String): GrantPermissionRule { + val granted = permissions.toMutableList() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + granted.add(Manifest.permission.POST_NOTIFICATIONS) + } + return GrantPermissionRule.grant(*granted.toTypedArray()) + } +} diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index b9e9feec6e2e..88f84dd8d9ff 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -16,6 +16,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.rule.GrantPermissionRule +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.R import com.owncloud.android.lib.resources.users.ClearAt @@ -28,10 +29,8 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun open() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt index b5ffe61c8a31..bcda64c78987 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt @@ -21,6 +21,7 @@ import androidx.test.rule.GrantPermissionRule import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.account.UserAccountManagerImpl +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.nextcloud.test.RetryTestRule import com.owncloud.android.AbstractIT import com.owncloud.android.MainApp @@ -39,10 +40,8 @@ class DrawerActivityIT : AbstractIT() { val retryTestRule = RetryTestRule() @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test fun switchAccountViaAccountList() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt index f83e116a6da5..b9302c2b5a7f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -25,10 +26,8 @@ class SyncFileNotEnoughSpaceDialogFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test @ScreenshotTest diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt index 93ac4bef85af..dcd27407166a 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule +import com.nextcloud.test.GrantPostNotificationsPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -26,10 +27,8 @@ class PreviewTextFileFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.preview.PreviewTextFileFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + val permissionRule: GrantPermissionRule = + GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) @Test @ScreenshotTest From 62adc8be1ad4b8807878f44ba08285b766932d32 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:17:09 +0200 Subject: [PATCH 11/16] wip Signed-off-by: alperozturk96 --- .../ReceiveExternalFilesActivityIT.kt | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt index a114c00dbdfa..f4dca3d7e8bc 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivityIT.kt @@ -12,6 +12,7 @@ package com.owncloud.android.ui.activity import android.content.Intent import android.net.Uri import android.view.KeyEvent +import android.view.View import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions @@ -32,6 +33,8 @@ import com.owncloud.android.R import com.owncloud.android.datamodel.OCFile import com.owncloud.android.utils.ScreenshotTest import org.hamcrest.Matchers.not +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -229,18 +232,22 @@ class ReceiveExternalFilesActivityIT : AbstractIT() { val preferences = AppPreferencesImpl.fromContext(targetContext) preferences.setLastUploadPath(mainFolder.remotePath) - launchActivity(intent).use { - val expectedMainFolderTitle = (getCurrentActivity() as ToolbarActivity).getActionBarTitle(mainFolder, false) - // Verify that the test starts in the expected folder. If this fails, change the setup calls above - onView(withId(R.id.toolbar)) - .check(matches(hasDescendant(withText(expectedMainFolderTitle)))) + // In the multi-upload case the rename field is hidden and no view requests focus, so the + // activity window never gains focus on the CI emulator and Espresso's onView(...) fails with + // RootViewWithoutFocusException. The view state is therefore asserted directly via + // onActivity, which does not depend on window focus. + launchActivity(intent).use { scenario -> + scenario.onActivity { sut -> + // Verify that the test starts in the expected folder. If this fails, change the setup calls above + val expectedMainFolderTitle = sut.getActionBarTitle(mainFolder, false) + assertEquals(expectedMainFolderTitle, sut.supportActionBar?.title?.toString()) - onView(withText(R.string.uploader_btn_upload_text)) - .check(matches(isDisplayed())) - .check(matches(isEnabled())) + val uploadButton = sut.findViewById(R.id.uploader_choose_folder) + assertEquals(View.VISIBLE, uploadButton.visibility) + assertTrue(uploadButton.isEnabled) - onView(withId(R.id.user_input)) - .check(matches(not(isDisplayed()))) + assertEquals(View.GONE, sut.findViewById(R.id.user_input).visibility) + } } } } From 2a9b59debb015773ecd36bc836549da28c13c33e Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:19:42 +0200 Subject: [PATCH 12/16] wip Signed-off-by: alperozturk96 --- .../GrantPostNotificationsPermissionRule.kt | 26 ---------------- .../nextcloud/test/GrantTestPermissionRule.kt | 30 +++++++++++++++++++ .../ui/SetStatusMessageBottomSheetIT.kt | 6 ++-- .../android/ui/activity/DrawerActivityIT.kt | 6 ++-- ...yncFileNotEnoughSpaceDialogFragmentTest.kt | 6 ++-- .../ui/preview/PreviewTextFileFragmentTest.kt | 6 ++-- 6 files changed, 38 insertions(+), 42 deletions(-) delete mode 100644 app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt create mode 100644 app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt deleted file mode 100644 index a1ff841847d6..000000000000 --- a/app/src/androidTest/java/com/nextcloud/test/GrantPostNotificationsPermissionRule.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.test - -import android.Manifest -import android.os.Build -import androidx.test.rule.GrantPermissionRule - -/** - * POST_NOTIFICATIONS is a runtime permission that only exists on API 33+. Granting it on lower API - * levels fails with "Unknown permission", so it is added to the granted set only when supported. - */ -object GrantPostNotificationsPermissionRule { - - fun grant(vararg permissions: String): GrantPermissionRule { - val granted = permissions.toMutableList() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - granted.add(Manifest.permission.POST_NOTIFICATIONS) - } - return GrantPermissionRule.grant(*granted.toTypedArray()) - } -} diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt new file mode 100644 index 000000000000..3f8fb0d363e1 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt @@ -0,0 +1,30 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package com.nextcloud.test + +import android.Manifest +import android.os.Build +import androidx.test.rule.GrantPermissionRule + +/** + * Grants the runtime permissions UI tests need. POST_NOTIFICATIONS only exists on API 33+, so + * granting it on lower API levels fails with "Unknown permission" and it is therefore requested only + * when supported. + */ +object GrantTestPermissionRule { + + fun grantStorageAndNotification(): GrantPermissionRule = if (Build.VERSION.SDK_INT >= + Build.VERSION_CODES.TIRAMISU + ) { + GrantPermissionRule.grant( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS + ) + } else { + GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } +} diff --git a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt index 88f84dd8d9ff..98fdb71dfd6e 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetStatusMessageBottomSheetIT.kt @@ -8,7 +8,6 @@ */ package com.nextcloud.ui -import android.Manifest import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches @@ -16,7 +15,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.rule.GrantPermissionRule -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.R import com.owncloud.android.lib.resources.users.ClearAt @@ -29,8 +28,7 @@ import org.junit.Test class SetStatusMessageBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test fun open() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt index bcda64c78987..38041c714ff4 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/activity/DrawerActivityIT.kt @@ -8,7 +8,6 @@ */ package com.owncloud.android.ui.activity -import android.Manifest import android.accounts.Account import android.accounts.AccountManager import android.net.Uri @@ -21,7 +20,7 @@ import androidx.test.rule.GrantPermissionRule import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.account.UserAccountManagerImpl -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.nextcloud.test.RetryTestRule import com.owncloud.android.AbstractIT import com.owncloud.android.MainApp @@ -40,8 +39,7 @@ class DrawerActivityIT : AbstractIT() { val retryTestRule = RetryTestRule() @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test fun switchAccountViaAccountList() { diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt index b9302c2b5a7f..fa277eafd34f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/SyncFileNotEnoughSpaceDialogFragmentTest.kt @@ -6,14 +6,13 @@ */ package com.owncloud.android.ui.dialog -import android.Manifest import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -26,8 +25,7 @@ class SyncFileNotEnoughSpaceDialogFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test @ScreenshotTest diff --git a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt index dcd27407166a..1037a18aa85f 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/preview/PreviewTextFileFragmentTest.kt @@ -6,14 +6,13 @@ */ package com.owncloud.android.ui.preview -import android.Manifest import androidx.test.core.app.launchActivity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.rule.GrantPermissionRule -import com.nextcloud.test.GrantPostNotificationsPermissionRule +import com.nextcloud.test.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -27,8 +26,7 @@ class PreviewTextFileFragmentTest : AbstractIT() { private val testClassName = "com.owncloud.android.ui.preview.PreviewTextFileFragmentTest" @get:Rule - val permissionRule: GrantPermissionRule = - GrantPostNotificationsPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE) + val permissionRule: GrantPermissionRule = GrantTestPermissionRule.grantStorageAndNotification() @Test @ScreenshotTest From 2c9a4b0b0273a168da01270605879daa8211bb01 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 13:20:01 +0200 Subject: [PATCH 13/16] wip Signed-off-by: alperozturk96 --- .../java/com/nextcloud/test/GrantTestPermissionRule.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt index 3f8fb0d363e1..867152f37dab 100644 --- a/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt +++ b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt @@ -1,8 +1,8 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ package com.nextcloud.test From f7f80f5a28521d553951abc4d8f810a497fbe9ee Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 15:36:20 +0200 Subject: [PATCH 14/16] wip Signed-off-by: alperozturk96 --- .../android/utils/FileUploadHelperTest.kt | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt b/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt index 502eb923526b..80eaf472719e 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/FileUploadHelperTest.kt @@ -19,8 +19,11 @@ import com.owncloud.android.files.services.NameCollisionPolicy import com.owncloud.android.lib.resources.status.OCCapability import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic import io.mockk.spyk +import io.mockk.unmockkStatic import io.mockk.verify +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -43,9 +46,7 @@ class FileUploadHelperTest { val mockFileStorageManager = mockk(relaxed = true) every { mockFileStorageManager.getCapability(any()) } returns mockCapability - val field = FileUploadHelper::class.java.getDeclaredField("fileStorageManager") - field.isAccessible = true - field.set(fileUploadHelper, mockFileStorageManager) + fileUploadHelper.fileStorageManager = mockFileStorageManager } @Suppress("LongParameterList") @@ -104,19 +105,25 @@ class FileUploadHelperTest { @Before fun setUp() { + mockkStatic("com.nextcloud.utils.extensions.OCCapabilityExtensionsKt") + uploadDao = mockk(relaxed = true) - fileUploadHelper = spyk(FileUploadHelper(), recordPrivateCalls = true) + val uploadsStorageManager = mockk(relaxed = true) + every { uploadsStorageManager.uploadDao } returns uploadDao + + val accountManager = mockk(relaxed = true) - val daoField = com.owncloud.android.datamodel.UploadsStorageManager::class.java - .declaredFields - .first { it.type == UploadDao::class.java } - daoField.isAccessible = true - daoField.set(uploadsStorageManager, uploadDao) + fileUploadHelper = spyk(FileUploadHelper(), recordPrivateCalls = true) + fileUploadHelper.uploadsStorageManager = uploadsStorageManager + fileUploadHelper.accountManager = accountManager + + toggleWCFRestrictions(false) + } - val field = FileUploadHelper::class.java.getDeclaredField("uploadsStorageManager") - field.isAccessible = true - field.set(fileUploadHelper, uploadsStorageManager) + @After + fun tearDown() { + unmockkStatic("com.nextcloud.utils.extensions.OCCapabilityExtensionsKt") } @Test From 1c8df16da29a65e045d6217111603555c0bfe296 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 15:44:42 +0200 Subject: [PATCH 15/16] wip Signed-off-by: alperozturk96 --- .../java/com/owncloud/android/utils/FileExportUtilsIT.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt b/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt index 2aa84ed5181c..b8169ea7b380 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/FileExportUtilsIT.kt @@ -23,7 +23,7 @@ class FileExportUtilsIT : AbstractIT() { val sut = FileExportUtils() val expectedFile = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { - File("/sdcard/Downloads/export.txt") + File("/sdcard/Download/export.txt") } else { File("/storage/emulated/0/Download/export.txt") } @@ -47,7 +47,7 @@ class FileExportUtilsIT : AbstractIT() { val sut = FileExportUtils() val expectedFile = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { - File("/sdcard/Downloads/export.txt") + File("/sdcard/Download/export.txt") } else { File("/storage/emulated/0/Download/export.txt") } From 063858e5a5b36dd6b0162a4a0a42c5a6a48b2cfa Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 15:57:38 +0200 Subject: [PATCH 16/16] wip Signed-off-by: alperozturk96 --- .../nextcloud/test/DisableAnimationsRule.kt | 51 +++++++++++++++++++ .../owncloud/android/utils/SnackbarTests.kt | 9 ++++ 2 files changed, 60 insertions(+) create mode 100644 app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt diff --git a/app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt b/app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt new file mode 100644 index 000000000000..bbefb3c022fd --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/DisableAnimationsRule.kt @@ -0,0 +1,51 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.test + +import android.os.ParcelFileDescriptor +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Disables system animations for the duration of the test. + * + * Espresso requires that no view keeps requesting layout while it waits for the root window. Animated components such + * as [com.google.android.material.snackbar.Snackbar] keep animating unless the global animation scales are set to zero. + * The Gradle `animationsDisabled` option only passes `--no-window-animation`, which leaves `animator_duration_scale` + * untouched, so animations still run on CI. This rule zeroes all scales directly via a shell command. + */ +class DisableAnimationsRule : TestRule { + + override fun apply(base: Statement, description: Description): Statement = object : Statement() { + override fun evaluate() { + setAnimationScale("0.0") + try { + base.evaluate() + } finally { + setAnimationScale("1.0") + } + } + } + + private fun setAnimationScale(scale: String) { + val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation + SCALE_SETTINGS.forEach { setting -> + val descriptor = uiAutomation.executeShellCommand("settings put global $setting $scale") + ParcelFileDescriptor.AutoCloseInputStream(descriptor).use { it.readBytes() } + } + } + + companion object { + private val SCALE_SETTINGS = listOf( + "window_animation_scale", + "transition_animation_scale", + "animator_duration_scale" + ) + } +} diff --git a/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt b/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt index 90980f2e5350..7c35120e22b5 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/SnackbarTests.kt @@ -23,13 +23,17 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.nextcloud.client.onboarding.FirstRunActivity +import com.nextcloud.test.DisableAnimationsRule +import com.nextcloud.test.RetryTestRule import com.nextcloud.test.TestActivity import com.owncloud.android.R import com.owncloud.android.authentication.AuthenticatorActivity import org.hamcrest.Matchers.anyOf import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test +import org.junit.rules.RuleChain class SnackbarTests { @@ -37,6 +41,11 @@ class SnackbarTests { class DialogTestFragment : DialogFragment() class BottomSheetTestFragment : BottomSheetDialogFragment() + @get:Rule + val ruleChain: RuleChain = RuleChain + .outerRule(DisableAnimationsRule()) + .around(RetryTestRule()) + @Before fun setUp() { Intents.init()