blob: 7bdc054b1a7af4a444129b1b955b4593c1d65450 [file] [log] [blame]
# 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)