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." + ) } } } 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/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) } } 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) } } 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/nextcloud/test/GrantTestPermissionRule.kt b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt new file mode 100644 index 000000000000..867152f37dab --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/GrantTestPermissionRule.kt @@ -0,0 +1,30 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +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/SetOnlineStatusBottomSheetIT.kt b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt index c9e07da94cfd..583697e196f3 100644 --- a/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt +++ b/app/src/androidTest/java/com/nextcloud/ui/SetOnlineStatusBottomSheetIT.kt @@ -27,10 +27,7 @@ import org.junit.Test class SetOnlineStatusBottomSheetIT : AbstractIT() { @get:Rule - val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.POST_NOTIFICATIONS - ) + 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 b9e9feec6e2e..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,6 +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.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.R import com.owncloud.android.lib.resources.users.ClearAt @@ -28,10 +28,7 @@ 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 = GrantTestPermissionRule.grantStorageAndNotification() @Test fun open() { 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) } } } 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/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 93caa0dba447..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,6 +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.GrantTestPermissionRule import com.nextcloud.test.RetryTestRule import com.owncloud.android.AbstractIT import com.owncloud.android.MainApp @@ -39,36 +39,36 @@ 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 = GrantTestPermissionRule.grantStorageAndNotification() @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/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) + } } } } 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 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..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,13 +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.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -25,10 +25,7 @@ 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 = GrantTestPermissionRule.grantStorageAndNotification() @Test @ScreenshotTest 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 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..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,13 +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.GrantTestPermissionRule import com.owncloud.android.AbstractIT import com.owncloud.android.datamodel.OCFile import com.owncloud.android.ui.activity.FileDisplayActivity @@ -26,10 +26,7 @@ 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 = GrantTestPermissionRule.grantStorageAndNotification() @Test @ScreenshotTest 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") } 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 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() 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 @@ + + + + + + + + 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"