diff --git a/scripts/qa/android-ui/fetch_secrets_json.py b/scripts/qa/android-ui/fetch_secrets_json.py index cee236c9ca4..9c1ec19efc1 100755 --- a/scripts/qa/android-ui/fetch_secrets_json.py +++ b/scripts/qa/android-ui/fetch_secrets_json.py @@ -18,8 +18,13 @@ "KEYCLOAK_QA_AUTOMATION", "SOCKS_PROXY_PASSWORD", } +LOGIN_SECRET_SECTIONS = { + "LARGE_TEAM_MEMBER_LOGIN_DETAILS", + "LARGE_TEAM_OWNER_LOGIN_DETAILS", +} HOST_SECRET_SECTIONS = {"TESTINY_API_KEY_ANDROID"} PASSWORD_FIELD = {"PASSWORD"} +LOGIN_FIELDS = {"USERNAME", "PASSWORD"} BACKEND_FIELDS = { "ACMEDISCOVERYURL", "BACKENDURL", @@ -48,6 +53,8 @@ def allowed_fields_for_title(title: str) -> set[str] | None: section = sanitize(title) if section.startswith("BACKENDCONNECTION_"): return BACKEND_FIELDS + if section in LOGIN_SECRET_SECTIONS: + return LOGIN_FIELDS if section in DEVICE_SECRET_SECTIONS or section in HOST_SECRET_SECTIONS: return PASSWORD_FIELD return None diff --git a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LargeTeamTests.kt b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LargeTeamTests.kt new file mode 100644 index 00000000000..124aa6780d6 --- /dev/null +++ b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LargeTeamTests.kt @@ -0,0 +1,213 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.tests.core.e2eTests + +import CredentialsManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import backendUtils.client.getBackendClientIds +import backendUtils.client.removeBackendClient +import com.wire.android.tests.core.BaseUiTest +import com.wire.android.tests.support.UiAutomatorSetup +import com.wire.android.tests.support.tags.Category +import com.wire.android.tests.support.tags.TestCaseId +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import user.utils.ClientUser + +@RunWith(AndroidJUnit4::class) +class LargeTeamTests : BaseUiTest() { + + private lateinit var teamOwner: ClientUser + private lateinit var member1: ClientUser + + @Before + fun setUp() { + initCommonTestHelpers() + device = UiAutomatorSetup.start(UiAutomatorSetup.APP_ALPHA) + } + + @After + fun tearDown() { + runCatching { testServiceHelper.testServiceClient.cleanUp() } + } + + // TODO: Increase the Kalium Test Service timeout so Member1 can log in and send a message. + // TODO: Re-enable the commented steps after the timeout is increased. + @Suppress("CyclomaticComplexMethod", "LongMethod") + @TestCaseId("TC-4398", "TC-8630") + @Category("regression", "RC", "largeTeams") + @Test + fun givenIAmInLargeTeam_whenICreateGroupAndExchangeMessages_thenGroupIsCreatedAndMessagesAreReceived() { + step("Given There is a known user TeamOwner") { + teamOwner = ClientUser( + "Emery", + "Kuvalis", + requiredSecret("LARGE_TEAM_OWNER_LOGIN_DETAILS", "USERNAME"), + requiredSecret("LARGE_TEAM_OWNER_LOGIN_DETAILS", "PASSWORD") + ).apply { + hardcoded = true + backendName = backendClient.name + nameAliases.add("user1Name") + } + clientUserManager.appendCustomUser(teamOwner) + } + + step("And There is a known user Member1") { + member1 = ClientUser( + "Solomon", + "Conroy", + requiredSecret("LARGE_TEAM_MEMBER_LOGIN_DETAILS", "USERNAME"), + requiredSecret("LARGE_TEAM_MEMBER_LOGIN_DETAILS", "PASSWORD") + ).apply { + hardcoded = true + backendName = backendClient.name + nameAliases.add("user2Name") + } + clientUserManager.appendCustomUser(member1) + } + + step("And User TeamOwner is me and removes all their registered OTR clients") { + clientUserManager.setSelfUser(teamOwner) + backendClient.getBackendClientIds(teamOwner).forEach { clientId -> + backendClient.removeBackendClient(teamOwner, clientId) + } + } + + // step("And User Member1 adds a new device Device1 with label Device1") { + // testServiceHelper.addDevice("user2Name", null, "Device1") + // } + + step("And I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + } + } + + step("And I enter a valid email and password to sign in") { + pages.loginPage.apply { + enterTeamOwnerLoggingEmail(teamOwner.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamOwnerLoggingPassword(teamOwner.password ?: "") + clickLoginButton() + } + } + + step("And I wait until I am fully logged in and decline share data alert") { + pages.registrationPage.apply { + waitUntilLoginFlowIsCompleted() + clickAllowNotificationButton() + clickDeclineShareDataAlert() + } + } + + step("When I start a new conversation and tap New Group") { + pages.conversationListPage.tapStartNewConversationButton() + pages.searchPage.tapCreateNewGroupButton() + } + + step("And I add Member1 and Member2 as participants") { + pages.searchPage.apply { + tapSearchPeopleField() + typeUserNameInSearchField(clientUserManager, "Solomon Conroy") + assertUsernameInSearchResultIs("@881d169258") + tapUsernameInSearchResult("@881d169258") + clearSearchInputField() + typeUserNameInSearchField(clientUserManager, "Florencio Larkin") + assertUsernameInSearchResultIs("@9d966fc182") + tapUsernameInSearchResult("@9d966fc182") + clearSearchInputField() + tapContinueButtonOnAddParticipantsPage() + } + } + + step("And I enter MyTeam as group name and continue") { + pages.createNewGroupPage.apply { + assertCreateNewGroupDetailsPageVisible() + enterNewGroupName("MyTeam") + tapContinueButtonOnGroupDetailsPage() + } + } + + step("And I see group settings and create the group") { + pages.createNewGroupPage.apply { + assertCreateNewGroupSettingsPageVisible() + tapCreateGroupButtonOnGroupSettingsPage() + } + } + + step("Then I see group conversation MyTeam is in foreground") { + pages.conversationViewPage.assertGroupConversationInForeground("MyTeam") + } + + step("When I open MyTeam group details and delete the conversation") { + pages.conversationViewPage.clickOnGroupConversationDetails("MyTeam") + pages.groupConversationDetailsPage.apply { + tapShowMoreOptionsButton() + tapDeleteConversationButton() + tapDeleteGroupButton() + } + } + + step("Then I see conversation list and do not see MyTeam") { + pages.conversationListPage.apply { + assertConversationListVisible() + assertConversationNotVisible("MyTeam") + } + } + + step("When I open group conversation Full House") { + pages.conversationListPage.clickGroupConversation("Full House") + pages.conversationViewPage.assertGroupConversationInForeground("Full House") + } + + step("And I send message Hello and see it in current conversation") { + pages.conversationViewPage.apply { + typeMessageInInputField("Hello") + clickSendButton() + assertSentMessageIsVisibleInCurrentConversation("Hello") + } + } + + // step("And User Member1 sends message Hi to group conversation Full House") { + // testServiceHelper.userSendMessageToConversation( + // "user2Name", + // "Hi", + // "Device1", + // "Full House" + // ) + // } + + // step("Then I see the message Hi in current conversation") { + // pages.conversationViewPage.assertReceivedMessageIsVisibleInCurrentConversation("Hi") + // } + } + + private fun requiredSecret(parentKey: String, fieldKey: String): String = + CredentialsManager.getSecretFieldValue(parentKey, fieldKey) + ?.takeIf { it.isNotBlank() } + ?: error("Missing secret [$parentKey/$fieldKey] in generated test BuildConfig.") +} diff --git a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LinksTests.kt b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LinksTests.kt new file mode 100644 index 00000000000..9d4a927b391 --- /dev/null +++ b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LinksTests.kt @@ -0,0 +1,331 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.tests.core.e2eTests + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import backendUtils.team.TeamRoles +import com.wire.android.tests.core.BaseUiTest +import com.wire.android.tests.support.UiAutomatorSetup +import com.wire.android.tests.support.tags.Category +import com.wire.android.tests.support.tags.TestCaseId +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import uiautomatorutils.KeyboardUtils.closeKeyboardIfOpened +import user.utils.ClientUser + +@RunWith(AndroidJUnit4::class) +class LinksTests : BaseUiTest() { + + private lateinit var teamOwner: ClientUser + + @Before + fun setUp() { + initCommonTestHelpers() + device = UiAutomatorSetup.start(UiAutomatorSetup.APP_ALPHA) + } + + @Suppress("CyclomaticComplexMethod", "LongMethod") + @TestCaseId("TC-4433") + @Category("regression", "RC", "links") + @Test + fun givenGroupConversationWithLink_whenITapReceivedLink_thenWebpageOpensInBrowser() { + step("Given There is a team owner TeamOwner with team Linking") { + backendSetupHelper.createTeamOwnerByAlias( + "user1Name", + "Linking", + "en_US", + true, + backendClient, + context + ) + } + + step("And User TeamOwner adds Member1 to team Linking with role Member") { + backendSetupHelper.userXAddsUsersToTeam( + "user1Name", + "user2Name", + "Linking", + TeamRoles.Member, + backendClient, + context, + true + ) + } + + step("And User Member1 adds a new device Device1 with label Device1") { + testServiceHelper.addDevice("user2Name", null, "Device1") + } + + step("And User TeamOwner has group conversation WeLikeLinks with Member1 in team Linking") { + backendSetupHelper.userHasGroupConversationInTeam( + "user1Name", + "WeLikeLinks", + "user2Name", + "Linking" + ) + } + + step("And User TeamOwner is me") { + teamOwner = clientUserManager.findUserByNameOrNameAlias("user1Name") + clientUserManager.setSelfUser(teamOwner) + } + + step("And I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + } + } + + step("And I enter a valid email and password to sign in") { + pages.loginPage.apply { + enterTeamOwnerLoggingEmail(teamOwner.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamOwnerLoggingPassword(teamOwner.password ?: "") + clickLoginButton() + } + } + + step("And I wait until I am fully logged in and decline share data alert") { + pages.registrationPage.apply { + waitUntilLoginFlowIsCompleted() + clickAllowNotificationButton() + clickDeclineShareDataAlert() + } + } + + step("And I see and open group conversation WeLikeLinks") { + pages.conversationListPage.apply { + assertGroupConversationVisible("WeLikeLinks") + tapConversationNameInConversationList("WeLikeLinks") + } + } + + step("When User Member1 sends link www.github.com to group conversation WeLikeLinks") { + testServiceHelper.userSendsGenericMessageToConversation( + "user2Name", + "WeLikeLinks", + "Device1", + "www.github.com" + ) + } + + step("And I see and tap link www.github.com in current conversation") { + pages.conversationViewPage.apply { + assertReceivedMessageIsVisibleInCurrentConversation("www.github.com") + tapLinkInCurrentConversation("www.github.com") + } + } + + step("Then I see an alert informing me that I will be forwarded to www.github.com in my browser") { + pages.conversationViewPage.assertLinkOpeningAlertVisible("www.github.com") + } + + step("When I tap open button on the link alert") { + pages.conversationViewPage.tapOpenButtonOnLinkAlert() + } + + step("Then I see the Wire app is not in foreground") { + pages.commonAppPage.assertWireAppIsNotInForeground() + } + + step("And I dismiss Chrome prompts and see webpage with github in foreground") { + pages.chromePage.apply { + dismissFirstRunIfVisible() + dismissNotificationsPromptIfVisible() + } + pages.settingsPage.assertChromeUrlIsDisplayed("github") + } + } + + @Suppress("CyclomaticComplexMethod", "LongMethod") + @TestCaseId("TC-4434") + @Category("regression", "RC", "links", "WPB-3518") + @Test + fun givenConversationWithManyMessages_whenITapLinkAfterScrolling_thenWebpageOpensInBrowser() { + step("Given There is a team owner TeamOwner with team Linking") { + backendSetupHelper.createTeamOwnerByAlias( + "user1Name", + "Linking", + "en_US", + true, + backendClient, + context + ) + } + + step("And User TeamOwner adds Member1 to team Linking with role Member") { + backendSetupHelper.userXAddsUsersToTeam( + "user1Name", + "user2Name", + "Linking", + TeamRoles.Member, + backendClient, + context, + true + ) + } + + step("And User TeamOwner has group conversation WeLikeLinks with Member1 in team Linking") { + backendSetupHelper.userHasGroupConversationInTeam( + "user1Name", + "WeLikeLinks", + "user2Name", + "Linking" + ) + } + + step("And User Member1 adds a new device Device1 with label Device1") { + testServiceHelper.addDevice("user2Name", null, "Device1") + } + + step("And User TeamOwner is me") { + teamOwner = clientUserManager.findUserByNameOrNameAlias("user1Name") + clientUserManager.setSelfUser(teamOwner) + } + + step("And I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + } + } + + step("And I enter a valid email and password to sign in") { + pages.loginPage.apply { + enterTeamOwnerLoggingEmail(teamOwner.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamOwnerLoggingPassword(teamOwner.password ?: "") + clickLoginButton() + } + } + + step("And I wait until I am fully logged in and decline share data alert") { + pages.registrationPage.apply { + waitUntilLoginFlowIsCompleted() + clickAllowNotificationButton() + clickDeclineShareDataAlert() + } + } + + step("And I see and open group conversation WeLikeLinks") { + pages.conversationListPage.apply { + assertGroupConversationVisible("WeLikeLinks") + tapConversationNameInConversationList("WeLikeLinks") + } + } + + step("When User Member1 sends link www.github.com to group conversation WeLikeLinks") { + testServiceHelper.userSendsGenericMessageToConversation( + "user2Name", + "WeLikeLinks", + "Device1", + "www.github.com" + ) + } + + step("And I see and tap link www.github.com in current conversation") { + pages.conversationViewPage.apply { + assertReceivedMessageIsVisibleInCurrentConversation("www.github.com") + tapLinkInCurrentConversation("www.github.com") + assertLinkOpeningAlertVisible("www.github.com") + tapOpenButtonOnLinkAlert() + } + } + + step("And I dismiss Chrome prompts and see the Wire app is not in foreground") { + pages.chromePage.dismissFirstRunIfVisible() + pages.commonAppPage.assertWireAppIsNotInForeground() + } + + step("And I restart Wire and see the Wire app is in foreground") { + pages.commonAppPage.apply { + restartWireApp() + assertWireAppIsInForeground() + } + } + + step("And User Member1 sends 20 default messages to conversation WeLikeLinks") { + repeat(20) { + testServiceHelper.userSendMessageToConversation( + "user2Name", + "1 message", + "Device1", + "WeLikeLinks" + ) + } + } + + step("And I send message That is a lot of messages and see it in current conversation") { + pages.conversationViewPage.apply { + typeMessageInInputField("That is a lot of messages") + clickSendButton() + assertSentMessageIsVisibleInCurrentConversation("That is a lot of messages") + } + } + + step("And User Member1 sends message Yes! to conversation WeLikeLinks") { + testServiceHelper.userSendMessageToConversation( + "user2Name", + "Yes!", + "Device1", + "WeLikeLinks" + ) + } + + step("And I see message Yes! in current conversation") { + pages.conversationViewPage.assertReceivedMessageIsVisibleInCurrentConversation("Yes!") + } + + step("When I scroll to the top, bottom and top of conversation view") { + closeKeyboardIfOpened() + pages.conversationViewPage.apply { + scrollToTopOfConversationScreen() + scrollToBottomOfConversationScreen() + scrollToTopOfConversationScreen() + } + } + + step("And I tap link www.github.com and open it in my browser") { + pages.conversationViewPage.apply { + tapLinkInCurrentConversation("www.github.com") + assertLinkOpeningAlertVisible("www.github.com") + tapOpenButtonOnLinkAlert() + } + } + + step("Then I see the Wire app is not in foreground and webpage with github is open") { + pages.commonAppPage.assertWireAppIsNotInForeground() + pages.chromePage.dismissNotificationsPromptIfVisible() + pages.settingsPage.assertChromeUrlIsDisplayed("github") + } + } +} diff --git a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LogoutTests.kt b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LogoutTests.kt new file mode 100644 index 00000000000..3b9325e5c14 --- /dev/null +++ b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/e2eTests/LogoutTests.kt @@ -0,0 +1,357 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.tests.core.e2eTests + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import backendUtils.team.TeamRoles +import com.wire.android.tests.core.BaseUiTest +import com.wire.android.tests.support.UiAutomatorSetup +import com.wire.android.tests.support.tags.Category +import com.wire.android.tests.support.tags.TestCaseId +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import uiautomatorutils.KeyboardUtils.closeKeyboardIfOpened +import user.utils.ClientUser + +@RunWith(AndroidJUnit4::class) +class LogoutTests : BaseUiTest() { + + private lateinit var teamOwner: ClientUser + private lateinit var member1: ClientUser + + @Before + fun setUp() { + initCommonTestHelpers() + device = UiAutomatorSetup.start(UiAutomatorSetup.APP_ALPHA) + } + + @Suppress("LongMethod") + @TestCaseId("TC-4445") + @Category("regression", "RC", "logout") + @Test + fun givenTeamOwner_whenILogInAndLogOut_thenLogoutIsSuccessful() { + step("Given There is a team owner TeamOwner with team Logout") { + backendSetupHelper.createTeamOwnerByAlias( + "user1Name", + "Logout", + "en_US", + true, + backendClient, + context + ) + teamOwner = clientUserManager.findUserByNameOrNameAlias("user1Name") + } + + step("And User TeamOwner is me") { + clientUserManager.setSelfUser(teamOwner) + } + + step("And I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + } + } + + step("And I enter a valid email and password to sign in") { + pages.loginPage.apply { + enterTeamOwnerLoggingEmail(teamOwner.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamOwnerLoggingPassword(teamOwner.password ?: "") + clickLoginButton() + } + } + + step("And I wait until I am fully logged in and decline share data alert") { + pages.registrationPage.apply { + waitUntilLoginFlowIsCompleted() + clickAllowNotificationButton() + clickDeclineShareDataAlert() + } + } + + step("And I tap User Profile Button and see User Profile Page") { + pages.conversationListPage.clickUserProfileButton() + pages.selfUserProfilePage.iSeeUserProfilePage() + } + + step("When I tap log out button on User Profile Page") { + pages.selfUserProfilePage.tapLogoutButton() + } + + step("Then I see alert informing me that I am about to clear my data when I log out") { + pages.selfUserProfilePage.iSeeClearDataOnLogOutAlert() + } + + step("When I tap log out button on clear data alert") { + pages.selfUserProfilePage.tapLogoutButton() + } + + step("Then I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + } + + @Suppress("CyclomaticComplexMethod", "LongMethod") + @TestCaseId("TC-4447") + @Category("regression", "RC", "logout") + @Test + fun givenTeamMemberWithConversation_whenILogOutWithoutClearingDataAndLogInAgain_thenHistoryIsPreserved() { + prepareMemberConversationAndLogin() + + step("And I see and open conversation TeamOwner") { + pages.conversationListPage.apply { + assertConversationVisible(teamOwner.name ?: "") + tapConversationNameInConversationList(teamOwner.name ?: "") + } + } + + step("When I send message Hello! and see it in current conversation") { + pages.conversationViewPage.apply { + typeMessageInInputField("Hello!") + clickSendButton() + assertSentMessageIsVisibleInCurrentConversation("Hello!") + } + } + + step("And User TeamOwner sends message Hello to you, too! to Member1") { + testServiceHelper.userSendMessageToPersonalMlsConversation( + "user1Name", + "Hello to you, too!", + "Device1", + "user2Name" + ) + } + + step("And I see message Hello to you, too! in current conversation") { + pages.conversationViewPage.assertReceivedMessageIsVisibleInCurrentConversation("Hello to you, too!") + } + + step("And I return to conversation list and open User Profile Page") { + closeKeyboardIfOpened() + pages.conversationViewPage.tapBackButtonToCloseConversationViewPage() + pages.conversationListPage.clickUserProfileButton() + pages.selfUserProfilePage.iSeeUserProfilePage() + } + + step("When I log out without clearing my data") { + pages.selfUserProfilePage.apply { + tapLogoutButton() + iSeeClearDataOnLogOutAlert() + tapLogoutButton() + } + } + + step("Then I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link and sign in again") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + enterTeamMemberLoggingEmail(member1.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamMemberLoggingPassword(member1.password ?: "") + clickLoginButton() + } + pages.registrationPage.waitUntilLoginFlowIsCompleted() + } + + step("Then I see conversation list and open conversation TeamOwner") { + pages.conversationListPage.apply { + assertConversationListVisible() + assertConversationVisible(teamOwner.name ?: "") + tapConversationNameInConversationList(teamOwner.name ?: "") + } + } + + step("And I see message Hello to you, too! in current conversation") { + pages.conversationViewPage.assertReceivedMessageIsVisibleInCurrentConversation("Hello to you, too!") + } + } + + @Suppress("CyclomaticComplexMethod", "LongMethod") + @TestCaseId("TC-4446") + @Category("regression", "RC", "logout") + @Test + fun givenTeamMemberWithConversation_whenILogOutWithClearData_thenConversationHistoryIsRemoved() { + prepareMemberConversationAndLogin() + + step("And I see and open conversation TeamOwner") { + pages.conversationListPage.apply { + assertConversationVisible(teamOwner.name ?: "") + tapConversationNameInConversationList(teamOwner.name ?: "") + } + } + + step("When I send message Hello! and see it in current conversation") { + pages.conversationViewPage.apply { + typeMessageInInputField("Hello!") + clickSendButton() + assertSentMessageIsVisibleInCurrentConversation("Hello!") + } + } + + step("And User TeamOwner sends message Hello to you, too! to Member1") { + testServiceHelper.userSendMessageToPersonalMlsConversation( + "user1Name", + "Hello to you, too!", + "Device1", + "user2Name" + ) + } + + step("And I see message Hello to you, too! in current conversation") { + pages.conversationViewPage.assertReceivedMessageIsVisibleInCurrentConversation("Hello to you, too!") + } + + step("And I return to conversation list and open User Profile Page") { + closeKeyboardIfOpened() + pages.conversationViewPage.tapBackButtonToCloseConversationViewPage() + pages.conversationListPage.clickUserProfileButton() + pages.selfUserProfilePage.iSeeUserProfilePage() + } + + step("When I tap log out button and see the clear data alert") { + pages.selfUserProfilePage.apply { + tapLogoutButton() + iSeeClearDataOnLogOutAlert() + } + } + + step("And I select the option to clear my data and log out") { + pages.selfUserProfilePage.apply { + iSeeInfoTextCheckbox("Delete all your personal information and conversations on this device") + tapInfoTextCheckbox() + tapLogoutButton() + } + } + + step("Then I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link and sign in again") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + enterTeamMemberLoggingEmail(member1.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamMemberLoggingPassword(member1.password ?: "") + clickLoginButton() + } + } + + step("And I wait until I am fully logged in and decline share data alert") { + pages.registrationPage.apply { + waitUntilLoginFlowIsCompleted() + clickAllowNotificationButton() + clickDeclineShareDataAlert() + } + } + + step("Then I see and open conversation TeamOwner") { + pages.conversationListPage.apply { + assertConversationVisible(teamOwner.name ?: "") + tapConversationNameInConversationList(teamOwner.name ?: "") + } + } + + step("And I do not see message Hello to you, too! in current conversation") { + pages.conversationViewPage.assertMessageNotVisible("Hello to you, too!") + } + } + + // Shared setup for TC-4446 and TC-4447; each test keeps its distinct logout behavior explicit. + private fun prepareMemberConversationAndLogin() { + step("Given There is a team owner TeamOwner with team Logout") { + backendSetupHelper.createTeamOwnerByAlias( + "user1Name", + "Logout", + "en_US", + true, + backendClient, + context + ) + } + + step("And User TeamOwner adds Member1 and Member2 to team Logout with role Member") { + backendSetupHelper.userXAddsUsersToTeam( + "user1Name", + "user2Name,user3Name", + "Logout", + TeamRoles.Member, + backendClient, + context, + true + ) + } + + step("And User TeamOwner has 1:1 conversation with Member1 in team Logout") { + backendSetupHelper.userHas1on1ConversationInTeam("user1Name", "user2Name", "Logout") + } + + step("And User Member1 is me") { + teamOwner = clientUserManager.findUserByNameOrNameAlias("user1Name") + member1 = clientUserManager.findUserByNameOrNameAlias("user2Name") + clientUserManager.setSelfUser(member1) + } + + step("And I see email verification Welcome Page") { + pages.registrationPage.assertEmailWelcomePage() + } + + step("And I open staging backend deep link") { + pages.loginPage.apply { + clickStagingDeepLink() + clickProceedButtonOnDeeplinkOverlay() + clickContinueButtonOnBackendConfigSuccess() + } + } + + step("And I enter a valid email and password to sign in") { + pages.loginPage.apply { + enterTeamMemberLoggingEmail(member1.email ?: "") + clickLoginButton() + assertUserLoginScreenVisible() + enterTeamMemberLoggingPassword(member1.password ?: "") + clickLoginButton() + } + } + + step("And I wait until I am fully logged in and decline share data alert") { + pages.registrationPage.apply { + waitUntilLoginFlowIsCompleted() + clickAllowNotificationButton() + clickDeclineShareDataAlert() + } + } + } +} diff --git a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/CommonAppPage.kt b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/CommonAppPage.kt index e460b41143a..daa0d541bc2 100644 --- a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/CommonAppPage.kt +++ b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/CommonAppPage.kt @@ -82,6 +82,27 @@ data class CommonAppPage(private val device: UiDevice) { return this } + fun restartWireApp(): CommonAppPage { + device.executeShellCommand( + "am start -n ${UiAutomatorSetup.appPackage}/com.wire.android.ui.WireActivity" + ) + return this + } + + fun assertWireAppIsInForeground(): CommonAppPage { + val wireAppIsInForeground = UiWaitUtils.retryUntilTimeout( + timeout = UiWaitUtils.SHORT_WAIT, + pollingInterval = UiWaitUtils.POLLING_FAST + ) { + device.currentPackageName == UiAutomatorSetup.appPackage + } + assertTrue( + "Wire app is not in foreground: ${device.currentPackageName}", + wireAppIsInForeground + ) + return this + } + fun closeWebPage(): CommonAppPage { UiWaitUtils.waitElement( closeWebPageButton, diff --git a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/ConversationViewPage.kt b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/ConversationViewPage.kt index 46c87ff7323..308c3fd2ccf 100644 --- a/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/ConversationViewPage.kt +++ b/tests/testsCore/src/androidTest/kotlin/com/wire/android/tests/core/pages/ConversationViewPage.kt @@ -759,6 +759,26 @@ data class ConversationViewPage(private val device: UiDevice) { return this } + fun tapLinkInCurrentConversation(link: String): ConversationViewPage { + UiWaitUtils.waitElement(UiSelectorParams(text = link)).click() + return this + } + + fun assertLinkOpeningAlertVisible(link: String): ConversationViewPage { + val alertText = UiWaitUtils.waitElement( + UiSelectorParams(textContains = "This will take you to") + ).text.orEmpty() + if (!alertText.contains(link)) { + throw AssertionError("Link opening alert does not contain '$link'.") + } + return this + } + + fun tapOpenButtonOnLinkAlert(): ConversationViewPage { + UiWaitUtils.waitElement(openButton).click() + return this + } + fun assertSystemMessageVisible(message: String) = apply { waitElement(UiSelectorParams(textContains = message)) } fun assertVisibleMentionedNameIs(mentionedName: String): ConversationViewPage { diff --git a/tests/testsSupport/build.gradle.kts b/tests/testsSupport/build.gradle.kts index 6fdbde7fa7b..bffe9a861f3 100644 --- a/tests/testsSupport/build.gradle.kts +++ b/tests/testsSupport/build.gradle.kts @@ -19,6 +19,8 @@ val secretsJson = rootProject.file("secrets.json") val deviceSecretSections = setOf( "CALLINGSERVICE_BASIC_AUTH", "KEYCLOAK_QA_AUTOMATION", + "LARGE_TEAM_MEMBER_LOGIN_DETAILS", + "LARGE_TEAM_OWNER_LOGIN_DETAILS", "SOCKS_PROXY_PASSWORD" ) diff --git a/tests/testsSupport/src/main/service/TestService.kt b/tests/testsSupport/src/main/service/TestService.kt index 20545b2b761..929638d6ace 100644 --- a/tests/testsSupport/src/main/service/TestService.kt +++ b/tests/testsSupport/src/main/service/TestService.kt @@ -59,6 +59,7 @@ class TestService(private val baseUri: String, private val testName: String) { companion object { private val log = WireTestLogger.getLog(TestService::class.java.simpleName) + private const val REDACTED_VALUE = "[REDACTED]" private const val CONNECT_TIMEOUT = 120000 private const val STATUS_204 = 204 private const val ZINFRA = "staging.zinfra.io" @@ -90,7 +91,7 @@ class TestService(private val baseUri: String, private val testName: String) { try { log.info("${c.requestMethod}: ${c.url}") request?.let { - log.info(" >>> Request: ${truncateOnlyOnBig(it.toString())}") + log.info(" >>> Request: ${truncateOnlyOnBig(it.redactSensitiveFields())}") writeStream(it.toString(), c.outputStream) } status = c.responseCode @@ -155,6 +156,11 @@ class TestService(private val baseUri: String, private val testName: String) { } } + private fun JSONObject.redactSensitiveFields(): String = JSONObject(toString()).apply { + if (has("password")) put("password", REDACTED_VALUE) + if (has("verificationCode")) put("verificationCode", REDACTED_VALUE) + }.toString() + private fun assertResponseCode(responseCode: Int, acceptableResponseCodes: List) { if (!acceptableResponseCodes.contains(responseCode)) { throw HttpRequestException( @@ -180,7 +186,7 @@ class TestService(private val baseUri: String, private val testName: String) { verificationCode?.let { put("verificationCode", it) } put("deviceName", deviceName) put("name", testName) - if (owner.backendName == "staging") { + if (owner.backendName.equals("staging", ignoreCase = true)) { put("backend", "staging") } else { val ownerBackend = BackendClient.loadBackend(owner.backendName.orEmpty()) diff --git a/tests/testsSupport/src/main/user/usermanager/ClientUserManager.kt b/tests/testsSupport/src/main/user/usermanager/ClientUserManager.kt index 5025e27dbfe..6dc41982df9 100644 --- a/tests/testsSupport/src/main/user/usermanager/ClientUserManager.kt +++ b/tests/testsSupport/src/main/user/usermanager/ClientUserManager.kt @@ -561,7 +561,6 @@ class ClientUserManager( ) { verifyUsersCountSatisfiesConstraints(1) var owner = findUserByNameOrNameAlias(nameAlias) - WireTestLogger.getLog("Hello").info(owner.toString() + "innnnn") owner = runBlocking { backend.createTeamOwnerViaBackend(owner, teamName, locale, updateHandle, context) } owner.backendName = backend.name appendCustomUser(owner)