| # Copyright 2017 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """Provides all settings related actions. |
| |
| """ |
| |
| from __future__ import absolute_import |
| import abc |
| import time |
| |
| import action_error |
| from appium_util import path_constants |
| from appium_util import timeout_util |
| import element |
| import input as input_module |
| import scroll |
| import step as step_module |
| import tap |
| import xml.etree.ElementTree as ET |
| import six |
| |
| # Settings paths. |
| _ANDROID_CLEAR_BUTTON_PATH = 'clear_button' |
| _ANDROID_CLEAR_BROWSING_DATA_PATH = 'clear_browsing_data_button' |
| _ANDROID_FIRST_ACCOUNT_XPATH = ( |
| '//android.widget.LinearLayout[1]/android.widget.FrameLayout[1]/' |
| 'android.view.View[1]/android.widget.FrameLayout[2]/' |
| 'android.widget.LinearLayout[1]/android.widget.ListView[1]/' |
| 'android.widget.LinearLayout[1]') |
| _ANDROID_MORE_BUTTON_PATH = 'more_button' |
| _ANDROID_POSITIVE_BUTTON_PATH = 'positive_button' |
| _ANDROID_SIGN_IN_ICON_PATH = 'icon' |
| |
| _IOS_CLEAR_BROWSING_DATA_PATH = 'Clear Browsing Data' |
| _IOS_CLEAR_BROWSING_DATA_BUTTON_ID = 'kClearBrowsingDataButtonIdentifier' |
| |
| _ACCOUNT_SIGNOUT_CELL_PATH = 'kSettingsAccountsTableViewId' |
| _ACCOUNT_CELL_PATH = 'kSettingsAccountCellId' |
| _SIGNIN_CELL_PATH = 'kSettingsSignInCellId' |
| _SIGNIN_CELL_PROMO_PATH = 'kSigninPromoViewId' |
| _CLOSE_BUTTON_PATH = 'ic_close' |
| _COLLECTIONVIEW_PATH = 'kSettingsTableViewId' |
| _HISTORY_PATH = 'History' |
| _IMPORT_DATA_CELL_PATH = 'kImportDataKeepSeparateCellId' |
| _PASSWORD_PATH = 'Enter your password' |
| _PRIVACY_CELL_PATH = 'kSettingsPrivacyCellId' |
| _REMOVE_BUTTON_PATH = 'Remove' |
| _SETTINGS_CELL_PATH = 'Settings' |
| _USERNAME_PATH = 'Email or phone' |
| |
| _FIRST_COLLECTION_CELL_PATH = ( |
| '//XCUIElementTypeTable["kSettingsTableViewId"]/' |
| 'XCUIElementTypeCell[1]') |
| _IOS_GOT_IT_BUTTON_PATH = 'Ok, Got it' |
| _IOS_YES_IMIN_BUTTTON_PATH = "kConfirmationAccessibilityIdentifier" |
| _IOS_CONTINUE_BUTTON_PATH = 'Continue' |
| _IOS_NEXT_BUTTON_PATH = '(//XCUIElementTypeButton[@name="Next"])[1]' |
| _IOS_FIRST_SETTINGS_ACCOUNT_CELL_PATH = ( |
| '//XCUIElementTypeTable["kSettingsAccountsTableViewId"]//' |
| 'XCUIElementTypeCell[contains(@name, "@")]') |
| |
| _TEXT_XPATH = path_constants.CONTAINS_TEXT_XPATH_PATTERN |
| |
| # Maximum time in seconds to wait for an element that is expected to be absent. |
| _TIMEOUT = timeout_util.WAIT_FOR_ABSENCE_TIMEOUT |
| |
| |
| class Settings(six.with_metaclass(abc.ABCMeta, object)): |
| """Abstract base class for settings related actions. |
| |
| """ |
| |
| @abc.abstractmethod |
| def AddAccount(self, driver_provider, step): |
| """Adds an account from Chrome Add Account screen. |
| |
| Should be called from Chrome Sign in screen. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| step: A Step tuple. |
| """ |
| pass |
| |
| @abc.abstractmethod |
| def RemoveAllAccounts(self, driver_provider, unused_step): |
| """Remove all accounts from the app. |
| |
| Should be called from the Settings screen, giving more flexibility to remove |
| all accounts regardless of the current state of the app. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| pass |
| |
| @abc.abstractmethod |
| def TapOnFirstAccountInSettingsAccounts(self, driver_provider, unused_step): |
| """Taps on the first account in the settings accounts view. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| pass |
| |
| @abc.abstractmethod |
| def ClearBrowsingData(self, driver_provider, unused_step): |
| """Clears all browsing data. |
| |
| Should be called from the Settings screen, giving more flexibility to clear |
| all browsing data regardless of the current state of the app. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| pass |
| |
| |
| class IOSSettings(Settings): |
| """Provides iOS implementation for settings related actions. |
| |
| """ |
| |
| def AddAccount(self, driver_provider, step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| step: A Step tuple. |
| """ |
| # Enter Username. |
| username, password = step.value.split(',') |
| input_module.IOSInput().TypeUsingLocator( |
| driver_provider, step_module.ID, _USERNAME_PATH, username, |
| timeout_util.WAIT_FOR_PRESENCE_TIMEOUT) |
| # Tap on Next button. |
| tap.IOSTap().TapOnElementByXPATH(driver_provider, _IOS_NEXT_BUTTON_PATH) |
| time.sleep(1) |
| # Enter Password. |
| input_module.IOSInput().TypeUsingLocator(driver_provider, step_module.ID, |
| _PASSWORD_PATH, password, _TIMEOUT) |
| # Tap on Next button. |
| tap.IOSTap().TapOnElementByXPATH(driver_provider, _IOS_NEXT_BUTTON_PATH) |
| tap.IOSTap().TapOnElementByID(driver_provider, _IOS_YES_IMIN_BUTTTON_PATH, |
| timeout_util.WAIT_FOR_PRESENCE_TIMEOUT) |
| # Handle when import data flow UI is presented. |
| if element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _IMPORT_DATA_CELL_PATH, |
| timeout_util.WAIT_FOR_PRESENCE_TIMEOUT): |
| tap.IOSTap().TapOnElementByID(driver_provider, _IMPORT_DATA_CELL_PATH) |
| tap.IOSTap().TapOnElementByID(driver_provider, _IOS_CONTINUE_BUTTON_PATH) |
| |
| def RemoveAllAccounts(self, driver_provider, unused_step): |
| loop_count = 0 |
| while loop_count < 20: |
| loop_count += 1 |
| time.sleep(1) |
| pagesource = driver_provider.driver.page_source |
| tree = ET.fromstring(pagesource.encode('utf-8')) |
| #Check if screen contains SignInPromo: |
| xpath = './/XCUIElementTypeOther[@name="'+ _SIGNIN_CELL_PROMO_PATH +'"]' |
| ele = tree.findall(xpath) |
| #Check if the element is present in the XMLTree. |
| #On IPads SignIn Promo is always Present but not Visible if the screen |
| # is in background. |
| if ele and ele[0].attrib['visible'] == 'true': |
| promo_cell_element = element.IOSElement().GetElementsByID( |
| driver_provider, _SIGNIN_CELL_PROMO_PATH)[0] |
| x = promo_cell_element.size.get('width') * 0.5 |
| y = promo_cell_element.size.get('height') * 0.8 |
| tap.IOSTap().TapWithOptions(driver_provider, |
| promo_cell_element, x, y) |
| continue |
| #Check If Unified Consent Screen is present |
| xpath = './/*[@name="kUnifiedConsentScrollViewIdentifier"]' |
| ele = tree.findall(xpath) |
| if ele: |
| # If "ADD ACCOUNT button is present that means there are no accounts |
| # present on the device. |
| # Tap Cancel to Dismiss the screen and exit while loop. |
| add_account_xpath = './/*[@name="kAddAccountAccessibilityIdentifier"]' |
| add_account_element = tree.findall(add_account_xpath) |
| if add_account_element: |
| tap.IOSTap().TapOnElementByID(driver_provider, 'kSkipSigninAccessibilityIdentifier') |
| return |
| #If accounts are present on the device, the Choose an account UI |
| # is displayed. |
| choose_account_xpath = './/*[@name="Choose an Account"]' |
| choose_account_element = tree.findall(choose_account_xpath) |
| if choose_account_element: |
| tap.IOSTap().TapOnElementByXPATH(driver_provider, '//XCUIElementTypeCell[1]') |
| tap.IOSTap().TapOnElementByID(driver_provider, "kConfirmationAccessibilityIdentifier") |
| continue |
| #If the user is already singed in. |
| xpath = './/*[@name="' + _ACCOUNT_CELL_PATH + '"]' |
| ele = tree.findall(xpath) |
| if ele and ele[0].attrib['visible'] == 'true': |
| tap.IOSTap().TapOnElementByID(driver_provider, _ACCOUNT_CELL_PATH) |
| continue |
| # Tap on Sign into Chrome cell if the user is not signed in. |
| # Handles a case with New Sign In Promo is not displayed. |
| xpath = './/*[@name="' +_SIGNIN_CELL_PATH +'"]' |
| ele = tree.findall(xpath) |
| if ele and ele[0].attrib['visible'] == 'true': |
| tap.IOSTap().TapOnElementByID(driver_provider, _SIGNIN_CELL_PATH) |
| continue |
| |
| # All accounts are removed if Chrome Sign in screen is found. |
| # This is old flow (Without Unified Consent) |
| xpath = './/*[@name="' +_USERNAME_PATH +'"]' |
| ele = tree.findall(xpath) |
| if ele and ele[0].attrib['visible'] == 'true': |
| tap.IOSTap().TapOnElementByIDAtIndex(driver_provider, |
| _CLOSE_BUTTON_PATH, index=-1) |
| return |
| # User could be redirected to Chrome account info screen after taping |
| # on first cell on Settings screen if user was previously signed into |
| # Chrome. |
| xpath = './/*[@name="' + _ACCOUNT_SIGNOUT_CELL_PATH + '"]' |
| ele = tree.findall(xpath) |
| if ele and ele[0].attrib['visible'] == 'true': |
| self._RemoveFirstAccount(driver_provider) |
| continue |
| |
| # Tap Continue button if displayed. OLD UI Case: |
| xpath = './/*[@name="'+_IOS_CONTINUE_BUTTON_PATH+'"]' |
| ele = tree.findall(xpath) |
| if ele: |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_CONTINUE_BUTTON_PATH) |
| continue |
| |
| # Tap OK, Got It if displayed. OLD UI Case: |
| xpath = './/*[@name="'+_IOS_GOT_IT_BUTTON_PATH+'"]' |
| ele = tree.findall(xpath) |
| if ele: |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_GOT_IT_BUTTON_PATH) |
| tap.IOSTap().TapOnElementByXPATH(driver_provider, |
| _FIRST_COLLECTION_CELL_PATH) |
| self._RemoveFirstAccount(driver_provider) |
| continue |
| |
| # Handle import data flow. |
| xpath = './/*[@name="' + _IMPORT_DATA_CELL_PATH + '"]' |
| ele = tree.findall(xpath) |
| if ele and ele[0].attrib['visible'] == 'true': |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IMPORT_DATA_CELL_PATH) |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_CONTINUE_BUTTON_PATH) |
| #http://crbug/958823 Unified Consent with screen doesn't |
| # disable background elements. So wait few seconds before continue. |
| time.sleep(3) |
| continue |
| |
| raise action_error.TooManyElementsFoundError( |
| 'Exceeded loop count when trying to remove all accounts.') |
| |
| # TODO(crbug/959903): Remove this method later. |
| def RemoveAllAccounts_OLD(self, driver_provider, unused_step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| |
| Raises: |
| TooManyElementsFoundError: Excessive looping trying to remove accounts. |
| """ |
| # Remove accounts from Chrome one by one until all are gone. |
| loop_count = 0 |
| while loop_count < 25: |
| loop_count += 1 |
| if element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _COLLECTIONVIEW_PATH, _TIMEOUT): |
| # Tap on the account cell if the user is signed in. |
| if element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _ACCOUNT_CELL_PATH, _TIMEOUT): |
| tap.IOSTap().TapOnElementByID(driver_provider, _ACCOUNT_CELL_PATH) |
| # If the new sign in Promo is displayed. |
| elif element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _SIGNIN_CELL_PROMO_PATH, _TIMEOUT): |
| promo_cell_element = element.IOSElement().GetElementsByID( |
| driver_provider, _SIGNIN_CELL_PROMO_PATH)[0] |
| x = promo_cell_element.size.get('width') * 0.5 |
| y = promo_cell_element.size.get('height') * 0.8 |
| tap.IOSTap().TapWithOptions(driver_provider, promo_cell_element, x, y) |
| # Tap on Sign in to Chrome cell if the user is not signed in. |
| else: |
| tap.IOSTap().TapOnElementByID(driver_provider, _SIGNIN_CELL_PATH) |
| # All accounts are removed if Chrome Sign in screen is found. |
| if element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _USERNAME_PATH, |
| timeout_util.WAIT_FOR_PRESENCE_TIMEOUT): |
| tap.IOSTap().TapOnElementByIDAtIndex( |
| driver_provider, _CLOSE_BUTTON_PATH, index=-1) |
| return |
| # User could be redirected to Chrome account info screen after taping on |
| # first cell on Settings screen if user was previously signed into Chrome. |
| if element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _ACCOUNT_SIGNOUT_CELL_PATH, |
| _TIMEOUT): |
| self._RemoveFirstAccount(driver_provider) |
| # User could be redirected to Sign in screen after taping on the first |
| # cell on Settings screen if user was previously signed out of Chrome. |
| else: |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_CONTINUE_BUTTON_PATH) |
| # Handle import data flow. |
| if element.IOSElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _IMPORT_DATA_CELL_PATH, _TIMEOUT): |
| tap.IOSTap().TapOnElementByID(driver_provider, _IMPORT_DATA_CELL_PATH) |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_CONTINUE_BUTTON_PATH) |
| # Carry on with Sign in flow. |
| tap.IOSTap().TapOnElementByID(driver_provider, _IOS_GOT_IT_BUTTON_PATH) |
| tap.IOSTap().TapOnElementByXPATH(driver_provider, |
| _FIRST_COLLECTION_CELL_PATH) |
| self._RemoveFirstAccount(driver_provider) |
| raise action_error.TooManyElementsFoundError( |
| 'Exceeded loop count when trying to remove all accounts.') |
| |
| def TapOnFirstAccountInSettingsAccounts(self, driver_provider, unused_step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| tap.IOSTap().TapOnElementByXPATH(driver_provider, |
| _IOS_FIRST_SETTINGS_ACCOUNT_CELL_PATH, |
| timeout_util.WAIT_FOR_PRESENCE_TIMEOUT) |
| |
| def ClearBrowsingData(self, driver_provider, unused_step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| scroll.IOSScroll().ScrollTo(driver_provider, 'down') |
| tap.IOSTap().TapOnElementByID( |
| driver_provider, _PRIVACY_CELL_PATH) |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_CLEAR_BROWSING_DATA_PATH) |
| time.sleep(1) |
| tap.IOSTap().TapOnElementByID(driver_provider, |
| _IOS_CLEAR_BROWSING_DATA_BUTTON_ID) |
| tap.IOSTap().TapOnElementByID( |
| driver_provider, 'Clear Browsing DataAlertAction') |
| time.sleep(3) |
| |
| def _RemoveFirstAccount(self, driver_provider): |
| """Removes the first account from Chrome account info screen. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| """ |
| self.TapOnFirstAccountInSettingsAccounts(driver_provider, unused_step=None) |
| time.sleep(1) |
| tap.IOSTap().TapOnElementByID(driver_provider, 'Remove Account from this Device') |
| tap.IOSTap().TapOnElementByID(driver_provider, _REMOVE_BUTTON_PATH) |
| time.sleep(1) |
| |
| |
| class AndroidSettings(Settings): |
| """Provides Android implementation for settings related actions. |
| |
| """ |
| |
| def AddAccount(self, driver_provider, step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| step: A Step tuple. |
| """ |
| # TODO(crbug.com/651234): Enter method implementation here. |
| return |
| |
| def RemoveAllAccounts(self, driver_provider, unused_step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| # TODO(crbug.com/651234): Enter method implementation here. |
| return |
| |
| def SignInWithAccountAlreadyOnDevice(self, driver_provider, step): |
| """Signs into Chrome when an Account is already on the device. |
| |
| Should be called from the Settings Screen. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| step: A Step tuple. |
| """ |
| timeout = timeout_util.WAIT_FOR_PRESENCE_TIMEOUT |
| tap.AndroidTap().TapOnElementByID(driver_provider, |
| _ANDROID_SIGN_IN_ICON_PATH) |
| # Choose the desired account if one is provided. |
| if step.value: |
| tap.AndroidTap().TapOnElementByXPATH(driver_provider, |
| _TEXT_XPATH % step.value) |
| |
| tap.AndroidTap().TapOnElementByID(driver_provider, |
| _ANDROID_POSITIVE_BUTTON_PATH) |
| # This may or may not appear depending on screen size. |
| if element.AndroidElement().IsElementBySeleniumLocatorPresent( |
| driver_provider, step_module.ID, _ANDROID_MORE_BUTTON_PATH, timeout): |
| tap.AndroidTap().TapOnElementByID(driver_provider, |
| _ANDROID_MORE_BUTTON_PATH) |
| |
| tap.AndroidTap().TapOnElementByID(driver_provider, |
| _ANDROID_POSITIVE_BUTTON_PATH, timeout) |
| |
| def TapOnFirstAccountInSettingsAccounts(self, driver_provider, unused_step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| tap.AndroidTap().TapOnElementByXPATH(driver_provider, |
| _ANDROID_FIRST_ACCOUNT_XPATH, |
| timeout_util.WAIT_FOR_PRESENCE_TIMEOUT) |
| |
| def ClearBrowsingData(self, driver_provider, unused_step): |
| """See description in base class. |
| |
| Args: |
| driver_provider: An instance of DriverProvider class. |
| unused_step: A Step tuple. |
| """ |
| tap.AndroidTap().TapOnElementByXPATH(driver_provider, |
| _TEXT_XPATH % _HISTORY_PATH) |
| tap.AndroidTap().TapOnElementByID(driver_provider, |
| _ANDROID_CLEAR_BROWSING_DATA_PATH) |
| tap.AndroidTap().TapOnElementByID(driver_provider, |
| _ANDROID_CLEAR_BUTTON_PATH) |