# Lint as: python2, python3 # Copyright 2016 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # # arc_util.py is supposed to be called from chrome.py for ARC specific logic. # It should not import arc.py since it will create a import loop. import collections import logging import os import tempfile from autotest_lib.client.bin import utils from autotest_lib.client.common_lib import error from autotest_lib.client.common_lib import file_utils from autotest_lib.client.common_lib.cros import arc_common from telemetry.core import exceptions from telemetry.internal.browser import extension_page _ARC_SUPPORT_HOST_URL = 'chrome-extension://cnbgggchhmkkdmeppjobngjoejnihlei/' _ARC_SUPPORT_HOST_PAGENAME = '_generated_background_page.html' _DUMPSTATE_PATH = '/var/log/arc-dumpstate.log' _USERNAME = 'crosarcplusplustest@gmail.com' _ARCP_URL = 'https://sites.google.com/a/chromium.org/dev/chromium-os' \ '/testing/arcplusplus-testing/arcp' _OPT_IN_BEGIN = 'Initializing ARC opt-in flow.' _OPT_IN_SKIP = 'Skip waiting provisioning completed.' _OPT_IN_FINISH = 'ARC opt-in flow complete.' _SIGN_IN_TIMEOUT = 120 _SIGN_IN_CHECK_INTERVAL = 1 # Describes ARC session state. # - provisioned is set to True once ARC is provisioned. # - tos_needed is set to True in case ARC Terms of Service need to be shown. # This depends on ARC Terms of Service acceptance and policy for # ARC preferences, such as backup and restore and use of location # services. ArcSessionState = collections.namedtuple( 'ArcSessionState', ['provisioned', 'tos_needed']) def get_arc_session_state(autotest_ext): """Returns the runtime state of ARC session. @param autotest_ext private autotest API. @raises error.TestFail in case autotest API failure or if ARC is not allowed for the device. @return ArcSessionState that describes the state of current ARC session. """ get_arc_state = ''' new Promise((resolve, reject) => { chrome.autotestPrivate.getArcState((state) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; } resolve(state); }); }) ''' try: state = autotest_ext.EvaluateJavaScript(get_arc_state, promise=True) return ArcSessionState(state['provisioned'], state['tosNeeded']) except exceptions.EvaluateException as e: raise error.TestFail('Could not get ARC session state "%s".' % e) def _wait_arc_provisioned(autotest_ext): """Waits until ARC provisioning is completed. @param autotest_ext private autotest API. @raises: error.TimeoutException if provisioning is not complete. """ utils.poll_for_condition( condition=lambda: get_arc_session_state(autotest_ext).provisioned, desc='Wait for ARC is provisioned', timeout=_SIGN_IN_TIMEOUT, sleep_interval=_SIGN_IN_CHECK_INTERVAL) def should_start_arc(arc_mode): """ Determines whether ARC should be started. @param arc_mode: mode as defined in arc_common. @returns: True or False. """ logging.debug('ARC is enabled in mode %s', arc_mode) assert arc_mode is None or arc_mode in arc_common.ARC_MODES return arc_mode in [arc_common.ARC_MODE_ENABLED, arc_common.ARC_MODE_ENABLED_ASYNC] def post_processing_after_browser(chrome, timeout): """ Called when a new browser instance has been initialized. Note that this hook function is called regardless of arc_mode. @param chrome: Chrome object. """ # Remove any stale dumpstate files. if os.path.isfile(_DUMPSTATE_PATH): os.unlink(_DUMPSTATE_PATH) # Wait for Android container ready if ARC is enabled. if chrome.arc_mode == arc_common.ARC_MODE_ENABLED: try: arc_common.wait_for_android_boot(timeout) except Exception: # Save dumpstate so that we can figure out why boot does not # complete. _save_android_dumpstate() raise def pre_processing_before_close(chrome): """ Called when the browser instance is being closed. Note that this hook function is called regardless of arc_mode. @param chrome: Chrome object. """ if not should_start_arc(chrome.arc_mode): return # TODO(b/29341443): Implement stopping of adb logcat when we start adb # logcat for all tests # Save dumpstate just before logout. _save_android_dumpstate() def _save_android_dumpstate(): """ Triggers a dumpstate and saves its contents to to /var/log/arc-dumpstate.log with logging. Exception thrown while doing dumpstate will be ignored. """ try: logging.info('Saving Android dumpstate.') with open(_DUMPSTATE_PATH, 'w') as out: log = utils.system_output('android-sh -c arc-bugreport') out.write(log) logging.info('Android dumpstate successfully saved.') except Exception: # Dumpstate is nice-to-have stuff. Do not make it as a fatal error. logging.exception('Failed to save Android dumpstate.') def get_test_account_info(): """Retrieve test account information.""" with tempfile.NamedTemporaryFile() as pltp: file_utils.download_file(_ARCP_URL, pltp.name) password = pltp.read().rstrip() return (_USERNAME, password) def set_browser_options_for_opt_in(b_options): """ Setup Chrome for gaia login and opt_in. @param b_options: browser options object used by chrome.Chrome. """ b_options.username, b_options.password = get_test_account_info() b_options.disable_default_apps = False b_options.disable_component_extensions_with_background_pages = False b_options.gaia_login = True def enable_play_store(autotest_ext, enabled, enable_managed_policy=True): """ Enable ARC++ Play Store Do nothing if the account is managed and enable_managed_policy is set to True. @param autotest_ext: autotest extension object. @param enabled: if True then perform opt-in, otherwise opt-out. @param enable_managed_policy: If False then policy check is ignored for managed user case and ARC++ is forced enabled or disabled. @returns: True if the opt-in should continue; else False. """ if autotest_ext is None: raise error.TestFail( 'Could not change the Play Store enabled state because ' 'autotest API does not exist') # Skip enabling for managed users, since value is policy enforced. # Return early if a managed user has ArcEnabled set to false. get_play_store_state = ''' new Promise((resolve, reject) => { chrome.autotestPrivate.getPlayStoreState((state) => { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError.message)); return; } resolve(state); }); }) ''' try: state = autotest_ext.EvaluateJavaScript(get_play_store_state, promise=True) if state['managed']: # Handle managed case. logging.info('Determined that ARC is managed by user policy.') if enable_managed_policy: if enabled == state['enabled']: return True logging.info('Returning early since ARC is policy-enforced.') return False logging.info('Forcing ARC %s, ignore managed state.', ('enabled' if enabled else 'disabled')) autotest_ext.ExecuteJavaScript(''' chrome.autotestPrivate.setPlayStoreEnabled( %s, function(enabled) {}); ''' % ('true' if enabled else 'false')) except exceptions.EvaluateException as e: raise error.TestFail('Could not change the Play Store enabled state ' ' via autotest API. "%s".' % e) return True def find_opt_in_extension_page(browser): """ Find and verify the opt-in extension extension page. @param browser: chrome.Chrome broswer object. @returns: the extension page. @raises: error.TestFail if extension is not found or is mal-formed. """ opt_in_extension_id = extension_page.UrlToExtensionId(_ARC_SUPPORT_HOST_URL) try: extension_pages = browser.extensions.GetByExtensionId( opt_in_extension_id) except Exception as e: raise error.TestFail('Could not locate extension for arc opt-in. ' 'Make sure disable_default_apps is False. ' '"%s".' % e) extension_main_page = None for page in extension_pages: url = page.EvaluateJavaScript('location.href;') if url.endswith(_ARC_SUPPORT_HOST_PAGENAME): extension_main_page = page break if not extension_main_page: raise error.TestError('Found opt-in extension but not correct page!') extension_main_page.WaitForDocumentReadyStateToBeComplete() js_code_did_start_conditions = ['termsPage != null', '(termsPage.isManaged_ || termsPage.state_ == LoadState.LOADED)'] try: for condition in js_code_did_start_conditions: extension_main_page.WaitForJavaScriptCondition(condition, timeout=60) except Exception as e: raise error.TestError('Error waiting for "%s": "%s".' % (condition, e)) return extension_main_page def opt_in_and_wait_for_completion(extension_main_page): """ Step through the user input of the opt-in extension and wait for completion. @param extension_main_page: opt-in extension object. @raises error.TestFail if opt-in doesn't complete after timeout. """ extension_main_page.ExecuteJavaScript('termsPage.onAgree()') try: extension_main_page.WaitForJavaScriptCondition('!appWindow', timeout=_SIGN_IN_TIMEOUT) except Exception as e: js_read_error_message = """ err = appWindow.contentWindow.document.getElementById( "error-message"); if (err) { err.innerText; } """ err_msg = extension_main_page.EvaluateJavaScript(js_read_error_message) err_msg = err_msg.strip() logging.error('Error: %r', err_msg) if err_msg: raise error.TestFail('Opt-in app error: %r' % err_msg) else: raise error.TestFail('Opt-in app did not finish running after %s ' 'seconds!' % _SIGN_IN_TIMEOUT) # Reset termsPage to be able to reuse OptIn page and wait condition for ToS # are loaded. extension_main_page.ExecuteJavaScript('termsPage = null') def opt_in(browser, autotest_ext, enable_managed_policy=True, wait_for_provisioning=True): """ Step through opt in and wait for it to complete. Return early if the arc_setting cannot be set True. @param browser: chrome.Chrome browser object. @param autotest_ext: autotest extension object. @param enable_managed_policy: If False then policy check is ignored for managed user case and ARC++ is forced enabled. @param wait_for_provisioning: True in case we have to wait for provisioning to complete. @raises: error.TestFail if opt in fails. """ logging.info(_OPT_IN_BEGIN) # TODO(b/124391451): Enterprise tests have race, when ARC policy appears # after profile prefs sync completed. That means they may appear as # non-managed first, treated like this but finally turns as managed. This # leads to test crash. Solution is to wait until prefs are synced or, if # possible tune test accounts to wait sync is completed before starting the # Chrome session. # Enabling Play Store may affect this flag, capture it before. tos_needed = get_arc_session_state(autotest_ext).tos_needed if not enable_play_store(autotest_ext, True, enable_managed_policy): return if not wait_for_provisioning: logging.info(_OPT_IN_SKIP) return if tos_needed: extension_main_page = find_opt_in_extension_page(browser) opt_in_and_wait_for_completion(extension_main_page) else: _wait_arc_provisioned(autotest_ext) logging.info(_OPT_IN_FINISH)