1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.fenix.ui.robots
7 import android.util.Log
8 import androidx.test.espresso.Espresso.onView
9 import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
10 import androidx.test.espresso.action.ViewActions
11 import androidx.test.espresso.assertion.ViewAssertions
12 import androidx.test.espresso.assertion.ViewAssertions.matches
13 import androidx.test.espresso.matcher.RootMatchers
14 import androidx.test.espresso.matcher.ViewMatchers
15 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
16 import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
17 import androidx.test.espresso.matcher.ViewMatchers.withHint
18 import androidx.test.espresso.matcher.ViewMatchers.withId
19 import androidx.test.espresso.matcher.ViewMatchers.withText
20 import androidx.test.uiautomator.By
21 import androidx.test.uiautomator.UiSelector
22 import androidx.test.uiautomator.Until
23 import org.hamcrest.CoreMatchers
24 import org.hamcrest.CoreMatchers.containsString
25 import org.mozilla.fenix.R
26 import org.mozilla.fenix.helpers.Constants.TAG
27 import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
28 import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
29 import org.mozilla.fenix.helpers.MatcherHelper.assertItemIsEnabledAndVisible
30 import org.mozilla.fenix.helpers.MatcherHelper.assertUIObjectExists
31 import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId
32 import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
33 import org.mozilla.fenix.helpers.MatcherHelper.itemWithClassNameAndIndex
34 import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
35 import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
36 import org.mozilla.fenix.helpers.TestHelper.mDevice
37 import org.mozilla.fenix.helpers.TestHelper.packageName
38 import org.mozilla.fenix.helpers.click
39 import org.mozilla.fenix.helpers.ext.waitNotNull
42 * Implementation of Robot Pattern for the Privacy Settings > saved logins sub menu
45 class SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot {
46 fun verifySecurityPromptForLogins() {
47 Log.i(TAG, "verifySecurityPromptForLogins: Trying to verify that the \"Secure your logins and passwords\" dialog is visible")
48 onView(withText("Secure your logins and passwords")).check(
50 withEffectiveVisibility(
51 ViewMatchers.Visibility.VISIBLE,
55 Log.i(TAG, "verifySecurityPromptForLogins: Verified that the \"Secure your logins and passwords\" dialog is visible")
58 fun verifyEmptySavedLoginsListView() {
59 Log.i(TAG, "verifyEmptySavedLoginsListView: Trying to verify that the saved logins section description is displayed")
60 onView(withText(getStringResource(R.string.preferences_passwords_saved_logins_description_empty_text)))
61 .check(matches(isDisplayed()))
62 Log.i(TAG, "verifyEmptySavedLoginsListView: Verified that the saved logins section description is displayed")
63 Log.i(TAG, "verifyEmptySavedLoginsListView: Trying to verify that the \"Learn more about Sync\" link is displayed")
64 onView(withText(R.string.preferences_passwords_saved_logins_description_empty_learn_more_link))
65 .check(matches(isDisplayed()))
66 Log.i(TAG, "verifyEmptySavedLoginsListView: Verified that the \"Learn more about Sync\" link is displayed")
67 Log.i(TAG, "verifyEmptySavedLoginsListView: Trying to verify that the \"Add login\" button is displayed")
68 onView(withText(R.string.preferences_logins_add_login))
69 .check(matches(isDisplayed()))
70 Log.i(TAG, "verifyEmptySavedLoginsListView: Verified that the \"Add login\" button is displayed")
73 fun verifySavedLoginsAfterSync() {
75 Until.findObjects(By.text("https://accounts.google.com")),
78 Log.i(TAG, "verifySavedLoginsAfterSync: Trying to verify that the \"https://accounts.google.comn\" login is displayed")
79 onView(withText("https://accounts.google.com")).check(matches(isDisplayed()))
80 Log.i(TAG, "verifySavedLoginsAfterSync: Verified that the \"https://accounts.google.comn\" login is displayed")
84 Log.i(TAG, "tapSetupLater: Trying to click the \"Later\" dialog button")
85 onView(withText("Later")).perform(ViewActions.click())
86 Log.i(TAG, "tapSetupLater: Clicked the \"Later\" dialog button")
89 fun clickAddLoginButton() {
90 Log.i(TAG, "clickAddLoginButton: Trying to click the \"Add login\" button")
91 itemContainingText(getStringResource(R.string.preferences_logins_add_login)).click()
92 Log.i(TAG, "clickAddLoginButton: Clicked the \"Add login\" button")
95 fun verifyAddNewLoginView() {
105 Log.i(TAG, "verifyAddNewLoginView: Trying to verify the \"https://www.example.com\" site text box hint")
106 siteTextInputHint().check(matches(withHint(R.string.add_login_hostname_hint_text)))
107 Log.i(TAG, "verifyAddNewLoginView: Verified the \"https://www.example.com\" site text box hint")
110 fun enterSiteCredential(website: String) {
111 Log.i(TAG, "enterSiteCredential: Trying to set the \"Site\" text box text to: $website")
112 siteTextInput().setText(website)
113 Log.i(TAG, "enterSiteCredential: The \"Site\" text box text was set to: $website")
116 fun verifyHostnameErrorMessage() =
117 assertUIObjectExists(itemContainingText(getStringResource(R.string.add_login_hostname_invalid_text_2)))
119 fun verifyPasswordErrorMessage() =
120 assertUIObjectExists(itemContainingText(getStringResource(R.string.saved_login_password_required)))
122 fun verifyPasswordClearButtonEnabled() =
123 assertItemIsEnabledAndVisible(itemWithResId("$packageName:id/clearPasswordTextButton"))
125 fun verifyHostnameClearButtonEnabled() =
126 assertItemIsEnabledAndVisible(itemWithResId("$packageName:id/clearHostnameTextButton"))
128 fun clickSearchLoginButton() {
129 Log.i(TAG, "clickSearchLoginButton: Trying to click the search logins button")
130 itemWithResId("$packageName:id/search").click()
131 Log.i(TAG, "clickSearchLoginButton: Clicked the search logins button")
134 fun clickSavedLoginsChevronIcon() {
135 Log.i(TAG, "clickSavedLoginsChevronIcon: Trying to click the \"Saved logins\" chevron button")
136 itemWithResId("$packageName:id/toolbar_chevron_icon").click()
137 Log.i(TAG, "clickSavedLoginsChevronIcon: Clicked the \"Saved logins\" chevron button")
140 fun verifyLoginsSortingOptions() {
141 assertUIObjectExists(itemContainingText(getStringResource(R.string.saved_logins_sort_strategy_alphabetically)))
142 assertUIObjectExists(itemContainingText(getStringResource(R.string.saved_logins_sort_strategy_last_used)))
145 fun clickLastUsedSortingOption() {
146 Log.i(TAG, "clickLastUsedSortingOption: Trying to click the \"Last used\" sorting option")
147 itemContainingText(getStringResource(R.string.saved_logins_sort_strategy_last_used)).click()
148 Log.i(TAG, "clickLastUsedSortingOption: Clicked the \"Last used\" sorting option")
151 fun verifySortedLogin(position: Int, loginTitle: String) =
152 assertUIObjectExists(
153 itemWithClassNameAndIndex(className = "android.view.ViewGroup", index = position)
156 .resourceId("$packageName:id/webAddressView")
157 .textContains(loginTitle),
161 fun searchLogin(searchTerm: String) {
162 Log.i(TAG, "searchLogin: Trying to set the search bar text to: $searchTerm")
163 itemWithResId("$packageName:id/search").setText(searchTerm)
164 Log.i(TAG, "searchLogin: Search bar text was set to: $searchTerm")
167 fun verifySavedLoginsSectionUsername(username: String) =
168 mDevice.waitNotNull(Until.findObjects(By.text(username)))
170 fun verifyLoginItemUsername(username: String) = assertUIObjectExists(itemContainingText(username))
172 fun verifyNotSavedLoginFromPrompt() {
173 Log.i(TAG, "verifyNotSavedLoginFromPrompt: Trying to verify that \"test@example.com\" does not exist in the saved logins list")
174 onView(withText("test@example.com"))
175 .check(ViewAssertions.doesNotExist())
176 Log.i(TAG, "verifyNotSavedLoginFromPrompt: Verified that \"test@example.com\" does not exist in the saved logins list")
179 fun verifyLocalhostExceptionAdded() {
180 Log.i(TAG, "verifyLocalhostExceptionAdded: Trying to verify that \"localhost\" is visible in the exceptions list")
181 onView(withText(containsString("localhost")))
182 .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
183 Log.i(TAG, "verifyLocalhostExceptionAdded: Verified that \"localhost\" is visible in the exceptions list")
186 fun viewSavedLoginDetails(loginUserName: String) {
187 Log.i(TAG, "viewSavedLoginDetails: Trying to click $loginUserName saved login")
188 onView(withText(loginUserName)).click()
189 Log.i(TAG, "viewSavedLoginDetails: Clicked $loginUserName saved login")
192 fun clickThreeDotButton(activityTestRule: HomeActivityIntentTestRule) {
193 Log.i(TAG, "clickThreeDotButton: Trying to click the three dot button")
194 openActionBarOverflowOrOptionsMenu(activityTestRule.activity)
195 Log.i(TAG, "clickThreeDotButton: Clicked the three dot button")
198 fun clickEditLoginButton() {
199 Log.i(TAG, "clickEditLoginButton: Trying to click the \"Edit\" button")
200 itemContainingText("Edit").click()
201 Log.i(TAG, "clickEditLoginButton: Clicked the \"Edit\" button")
204 fun clickDeleteLoginButton() {
205 Log.i(TAG, "clickDeleteLoginButton: Trying to click the \"Delete\" button")
206 itemContainingText("Delete").click()
207 Log.i(TAG, "clickDeleteLoginButton: Clicked the \"Delete\" button")
210 fun verifyLoginDeletionPrompt() =
211 assertUIObjectExists(itemContainingText(getStringResource(R.string.login_deletion_confirmation)))
213 fun clickConfirmDeleteLogin() {
214 Log.i(TAG, "clickConfirmDeleteLogin: Trying to click the \"Delete\" dialog button")
215 onView(withId(android.R.id.button1)).inRoot(RootMatchers.isDialog()).click()
216 Log.i(TAG, "clickConfirmDeleteLogin: Clicked the \"Delete\" dialog button")
219 fun clickCancelDeleteLogin() {
220 Log.i(TAG, "clickCancelDeleteLogin: Trying to click the \"Cancel\" dialog button")
221 onView(withId(android.R.id.button2)).inRoot(RootMatchers.isDialog()).click()
222 Log.i(TAG, "clickCancelDeleteLogin: Clicked the \"Cancel\" dialog button")
225 fun setNewUserName(userName: String) {
226 Log.i(TAG, "setNewUserName: Trying to set \"Username\" text box to: $userName")
227 usernameTextInput().setText(userName)
228 Log.i(TAG, "setNewUserName: \"Username\" text box was set to: $userName")
231 fun clickClearUserNameButton() {
232 Log.i(TAG, "clickClearUserNameButton: Trying to click the clear username button")
233 itemWithResId("$packageName:id/clearUsernameTextButton").click()
234 Log.i(TAG, "clickClearUserNameButton: Clicked the clear username button")
237 fun setNewPassword(password: String) {
238 Log.i(TAG, "setNewPassword: Trying to set \"Password\" text box to: $password")
239 passwordTextInput().setText(password)
240 Log.i(TAG, "setNewPassword: \"Password\" text box was set to: $password")
243 fun clickClearPasswordButton() {
244 Log.i(TAG, "clickClearPasswordButton: Trying to click the clear password button")
245 itemWithResId("$packageName:id/clearPasswordTextButton").click()
246 Log.i(TAG, "clickClearPasswordButton: Clicked the clear password button")
249 fun saveEditedLogin() {
250 Log.i(TAG, "saveEditedLogin: Trying to click the toolbar save button")
251 itemWithResId("$packageName:id/save_login_button").click()
252 Log.i(TAG, "saveEditedLogin: Clicked the toolbar save button")
255 fun verifySaveLoginButtonIsEnabled(isEnabled: Boolean) =
256 assertUIObjectExists(
257 checkedItemWithResId("$packageName:id/save_login_button", isChecked = true),
261 fun revealPassword() {
262 Log.i(TAG, "revealPassword: Trying to click the reveal password button")
263 onView(withId(R.id.revealPasswordButton)).click()
264 Log.i(TAG, "revealPassword: Clicked the reveal password button")
267 fun verifyPasswordSaved(password: String) {
268 Log.i(TAG, "verifyPasswordSaved: Trying to verify that the \"Password\" text box is set to $password")
269 onView(withId(R.id.passwordText)).check(matches(withText(password)))
270 Log.i(TAG, "verifyPasswordSaved: Verified that the \"Password\" text box is set to $password")
273 fun verifyUserNameRequiredErrorMessage() =
274 assertUIObjectExists(itemContainingText(getStringResource(R.string.saved_login_username_required)))
276 fun verifyPasswordRequiredErrorMessage() =
277 assertUIObjectExists(itemContainingText(getStringResource(R.string.saved_login_password_required)))
279 fun clickGoBackButton() = goBackButton().click()
281 fun clickCopyUserNameButton() =
282 itemWithResId("$packageName:id/copyUsername").also {
283 Log.i(TAG, "clickCopyUserNameButton: Waiting for $waitingTime ms for the copy username button to exist")
284 it.waitForExists(waitingTime)
285 Log.i(TAG, "clickCopyUserNameButton: Waited for $waitingTime ms for the copy username button to exist")
286 Log.i(TAG, "clickCopyUserNameButton:Trying to click the copy username button")
288 Log.i(TAG, "clickCopyUserNameButton:Clicked the copy username button")
291 fun clickCopyPasswordButton() =
292 itemWithResId("$packageName:id/copyPassword").also {
293 Log.i(TAG, "clickCopyPasswordButton: Waiting for $waitingTime ms for the copy password button to exist")
294 it.waitForExists(waitingTime)
295 Log.i(TAG, "clickCopyPasswordButton: Waited for $waitingTime ms for the copy password button to exist")
296 Log.i(TAG, "clickCopyPasswordButton:Trying to click the copy password button")
298 Log.i(TAG, "clickCopyPasswordButton:Clicked the copy password button")
302 fun goBack(interact: SettingsSubMenuLoginsAndPasswordRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordRobot.Transition {
303 Log.i(TAG, "goBack: Trying to click the navigate up button")
304 goBackButton().perform(ViewActions.click())
305 Log.i(TAG, "goBack: Clicked the navigate up button")
307 SettingsSubMenuLoginsAndPasswordRobot().interact()
308 return SettingsSubMenuLoginsAndPasswordRobot.Transition()
311 fun goBackToSavedLogins(interact: SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.() -> Unit): SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition {
312 Log.i(TAG, "goBackToSavedLogins: Trying to click the navigate up button")
313 goBackButton().perform(ViewActions.click())
314 Log.i(TAG, "goBackToSavedLogins: Clicked the navigate up button")
316 SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot().interact()
317 return SettingsSubMenuLoginsAndPasswordsSavedLoginsRobot.Transition()
320 fun goToSavedWebsite(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
321 Log.i(TAG, "goToSavedWebsite: Trying to click the open web site button")
322 openWebsiteButton().click()
323 Log.i(TAG, "goToSavedWebsite: Clicked the open web site button")
325 BrowserRobot().interact()
326 return BrowserRobot.Transition()
331 private fun goBackButton() =
332 onView(CoreMatchers.allOf(ViewMatchers.withContentDescription("Navigate up")))
334 private fun openWebsiteButton() = onView(withId(R.id.openWebAddress))
336 private fun siteHeader() = itemWithResId("$packageName:id/hostnameHeaderText")
337 private fun siteTextInput() = itemWithResId("$packageName:id/hostnameText")
338 private fun siteDescription() = itemContainingText(getStringResource(R.string.add_login_hostname_invalid_text_3))
339 private fun siteTextInputHint() = onView(withId(R.id.hostnameText))
340 private fun usernameHeader() = itemWithResId("$packageName:id/usernameHeader")
341 private fun usernameTextInput() = itemWithResId("$packageName:id/usernameText")
342 private fun passwordHeader() = itemWithResId("$packageName:id/passwordHeader")
343 private fun passwordTextInput() = itemWithResId("$packageName:id/passwordText")