blob: 2116927ffb76122dcd4c50572948ff662a6913f8 [file] [log] [blame]
# Copyright (c) 2013 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.
import logging
import os
import re
from autotest_lib.client.common_lib.cros import arc_common
from autotest_lib.client.common_lib.cros import arc_util
from autotest_lib.client.common_lib.cros import assistant_util
from autotest_lib.client.cros import constants
from autotest_lib.client.bin import utils
from telemetry.core import cros_interface, exceptions
from telemetry.internal.browser import browser_finder, browser_options
from telemetry.internal.browser import extension_to_load
import py_utils
Error = exceptions.Error
def NormalizeEmail(username):
"""Remove dots from username. Add @gmail.com if necessary.
TODO(achuith): Get rid of this when crbug.com/358427 is fixed.
@param username: username/email to be scrubbed.
"""
parts = re.split('@', username)
parts[0] = re.sub('\.', '', parts[0])
if len(parts) == 1:
parts.append('gmail.com')
return '@'.join(parts)
class Chrome(object):
"""Wrapper for creating a telemetry browser instance with extensions.
The recommended way to use this class is to create the instance using the
with statement:
>>> with chrome.Chrome(...) as cr:
>>> # Do whatever you need with cr.
>>> pass
This will make sure all the clean-up functions are called. If you really
need to use this class without the with statement, make sure to call the
close() method once you're done with the Chrome instance.
"""
BROWSER_TYPE_LOGIN = 'system'
BROWSER_TYPE_GUEST = 'system-guest'
AUTOTEST_EXT_ID = 'behllobkkfkfnphdnhnkndlbkcpglgmj'
def __init__(self, logged_in=True, extension_paths=None, autotest_ext=False,
num_tries=3, extra_browser_args=None,
clear_enterprise_policy=True, expect_policy_fetch=False,
dont_override_profile=False, disable_gaia_services=True,
disable_default_apps=True, auto_login=True, gaia_login=False,
username=None, password=None, gaia_id=None,
arc_mode=None, arc_timeout=None,
enable_web_app_auto_install=False,
disable_arc_opt_in=True,
disable_arc_opt_in_verification=True,
disable_arc_cpu_restriction=True,
disable_app_sync=False,
disable_play_auto_install=False,
disable_locale_sync=True,
disable_play_store_auto_update=True,
enable_assistant=False,
enterprise_arc_test=False,
init_network_controller=False,
mute_audio=False,
proxy_server=None,
login_delay=0):
"""
Constructor of telemetry wrapper.
@param logged_in: Regular user (True) or guest user (False).
@param extension_paths: path of unpacked extension to install.
@param autotest_ext: Load a component extension with privileges to
invoke chrome.autotestPrivate.
@param num_tries: Number of attempts to log in.
@param extra_browser_args: Additional argument(s) to pass to the
browser. It can be a string or a list.
@param clear_enterprise_policy: Clear enterprise policy before
logging in.
@param expect_policy_fetch: Expect that chrome can reach the device
management server and download policy.
@param dont_override_profile: Don't delete cryptohome before login.
Telemetry will output a warning with this
option.
@param disable_gaia_services: For enterprise autotests, this option may
be used to enable policy fetch.
@param disable_default_apps: For tests that exercise default apps.
@param auto_login: Does not login automatically if this is False.
Useful if you need to examine oobe.
@param gaia_login: Logs in to real gaia.
@param username: Log in using this username instead of the default.
@param password: Log in using this password instead of the default.
@param gaia_id: Log in using this gaia_id instead of the default.
@param arc_mode: How ARC instance should be started. Default is to not
start.
@param arc_timeout: Timeout to wait for ARC to boot.
@param enable_web_app_auto_install: For tests that require to auto
download and install default web applications. By default it is disabled.
@param disable_arc_opt_in: For opt in flow autotest. This option is used
to disable the arc opt in flow.
@param disable_arc_opt_in_verification:
Adds --disable-arc-opt-in-verification to browser args. This should
generally be enabled when disable_arc_opt_in is enabled. However,
for data migration tests where user's home data is already set up
with opted-in state before login, this option needs to be set to
False with disable_arc_opt_in=True to make ARC container work.
@param disable_arc_cpu_restriction:
Adds --disable-arc-cpu-restriction to browser args. This is enabled
by default and will make tests run faster and is generally
desirable unless a test is actually trying to test performance
where ARC is running in the background for some porition of the
test.
@param disable_app_sync:
Adds --arc-disable-app-sync to browser args and this disables ARC
app sync flow. By default it is enabled.
@param disable_play_auto_install:
Adds --arc-disable-play-auto-install to browser args and this
disables ARC Play Auto Install flow. By default it is enabled.
@param enable_assistant: For tests that require to enable Google
Assistant service. Default is False.
@param enterprise_arc_test: Skips opt_in causing enterprise tests to fail
@param disable_locale_sync:
Adds --arc-disable-locale-sync to browser args and this
disables locale sync between Chrome and Android container. In case
of disabling sync, Android container is started with language and
preference language list as it was set on the moment of starting
full instance. Used to prevent random app restarts caused by racy
locale change, coming from profile sync. By default locale sync is
disabled.
@param disable_play_store_auto_update:
Adds --arc-play-store-auto-update=off to browser args and this
disables Play Store, GMS Core and third-party apps auto-update.
By default auto-update is off to have stable autotest environment.
@param mute_audio: Mute audio.
@param proxy_server: To launch the chrome with --proxy-server
Adds '--proxy-server="http://$HTTP_PROXY:PORT"' to browser args. By
default proxy-server is disabled
@param login_delay: Time for idle in login screen to simulate the time
required for password typing.
"""
self._autotest_ext_path = None
# Force autotest extension if we need enable Play Store.
if (utils.is_arc_available() and (arc_util.should_start_arc(arc_mode)
or not disable_arc_opt_in)):
autotest_ext = True
if extension_paths is None:
extension_paths = []
finder_options = browser_options.BrowserFinderOptions()
if proxy_server:
finder_options.browser_options.AppendExtraBrowserArgs(
['--proxy-server="%s"' % proxy_server])
if utils.is_arc_available() and arc_util.should_start_arc(arc_mode):
if disable_arc_opt_in and disable_arc_opt_in_verification:
finder_options.browser_options.AppendExtraBrowserArgs(
['--disable-arc-opt-in-verification'])
if disable_arc_cpu_restriction:
finder_options.browser_options.AppendExtraBrowserArgs(
['--disable-arc-cpu-restriction'])
if disable_app_sync:
finder_options.browser_options.AppendExtraBrowserArgs(
['--arc-disable-app-sync'])
if disable_play_auto_install:
finder_options.browser_options.AppendExtraBrowserArgs(
['--arc-disable-play-auto-install'])
if disable_locale_sync:
finder_options.browser_options.AppendExtraBrowserArgs(
['--arc-disable-locale-sync'])
if disable_play_store_auto_update:
finder_options.browser_options.AppendExtraBrowserArgs(
['--arc-play-store-auto-update=off'])
logged_in = True
if autotest_ext:
self._autotest_ext_path = os.path.join(os.path.dirname(__file__),
'autotest_private_ext')
extension_paths.append(self._autotest_ext_path)
finder_options.browser_options.AppendExtraBrowserArgs(
['--whitelisted-extension-id=%s' % self.AUTOTEST_EXT_ID])
self._browser_type = (self.BROWSER_TYPE_LOGIN
if logged_in else self.BROWSER_TYPE_GUEST)
finder_options.browser_type = self.browser_type
if not enable_web_app_auto_install:
finder_options.browser_options.AppendExtraBrowserArgs(
['--disable-features=DefaultWebAppInstallation'])
if extra_browser_args:
finder_options.browser_options.AppendExtraBrowserArgs(
extra_browser_args)
# finder options must be set before parse_args(), browser options must
# be set before Create().
# TODO(crbug.com/360890) Below MUST be '2' so that it doesn't inhibit
# autotest debug logs
finder_options.verbosity = 2
finder_options.CreateParser().parse_args(args=[])
b_options = finder_options.browser_options
b_options.disable_component_extensions_with_background_pages = False
b_options.create_browser_with_oobe = True
b_options.clear_enterprise_policy = clear_enterprise_policy
b_options.dont_override_profile = dont_override_profile
b_options.disable_gaia_services = disable_gaia_services
b_options.disable_default_apps = disable_default_apps
b_options.disable_component_extensions_with_background_pages = disable_default_apps
b_options.disable_background_networking = False
b_options.expect_policy_fetch = expect_policy_fetch
b_options.auto_login = auto_login
b_options.gaia_login = gaia_login
b_options.mute_audio = mute_audio
b_options.login_delay = login_delay
if utils.is_arc_available() and not disable_arc_opt_in:
arc_util.set_browser_options_for_opt_in(b_options)
self.username = b_options.username if username is None else username
self.password = b_options.password if password is None else password
self.username = NormalizeEmail(self.username)
b_options.username = self.username
b_options.password = self.password
self.gaia_id = b_options.gaia_id if gaia_id is None else gaia_id
b_options.gaia_id = self.gaia_id
self.arc_mode = arc_mode
if logged_in:
extensions_to_load = b_options.extensions_to_load
for path in extension_paths:
extension = extension_to_load.ExtensionToLoad(
path, self.browser_type)
extensions_to_load.append(extension)
self._extensions_to_load = extensions_to_load
# Turn on collection of Chrome coredumps via creation of a magic file.
# (Without this, Chrome coredumps are trashed.)
open(constants.CHROME_CORE_MAGIC_FILE, 'w').close()
self._browser_to_create = browser_finder.FindBrowser(
finder_options)
self._browser_to_create.SetUpEnvironment(b_options)
for i in range(num_tries):
try:
self._browser = self._browser_to_create.Create()
self._browser_pid = \
cros_interface.CrOSInterface().GetChromePid()
if utils.is_arc_available():
if disable_arc_opt_in:
if arc_util.should_start_arc(arc_mode):
arc_util.enable_play_store(self.autotest_ext, True)
else:
if not enterprise_arc_test:
wait_for_provisioning = \
arc_mode != arc_common.ARC_MODE_ENABLED_ASYNC
arc_util.opt_in(
browser=self.browser,
autotest_ext=self.autotest_ext,
wait_for_provisioning=wait_for_provisioning)
arc_util.post_processing_after_browser(self, arc_timeout)
if enable_assistant:
assistant_util.enable_assistant(self.autotest_ext)
break
except exceptions.LoginException as e:
logging.error('Timed out logging in, tries=%d, error=%s',
i, repr(e))
if i == num_tries-1:
raise
if init_network_controller:
self._browser.platform.network_controller.Open()
def __enter__(self):
return self
def __exit__(self, *args):
# Turn off collection of Chrome coredumps turned on in init.
if os.path.exists(constants.CHROME_CORE_MAGIC_FILE):
os.remove(constants.CHROME_CORE_MAGIC_FILE)
self.close()
@property
def browser(self):
"""Returns a telemetry browser instance."""
return self._browser
def get_extension(self, extension_path, retry=5):
"""Fetches a telemetry extension instance given the extension path."""
def _has_ext(ext):
"""
Return True if the extension is fully loaded.
Sometimes an extension will be in the _extensions_to_load, but not
be fully loaded, and will error when trying to fetch from
self.browser.extensions. Happens most common when ARC is enabled.
This will add a wait/retry.
@param ext: the extension to look for
@returns True if found, False if not.
"""
try:
return bool(self.browser.extensions[ext])
except KeyError:
return False
for ext in self._extensions_to_load:
if extension_path == ext.path:
utils.poll_for_condition(lambda: _has_ext(ext),
timeout=retry)
return self.browser.extensions[ext]
return None
@property
def autotest_ext(self):
"""Returns the autotest extension."""
return self.get_extension(self._autotest_ext_path)
@property
def login_status(self):
"""Returns login status."""
ext = self.autotest_ext
if not ext:
return None
ext.ExecuteJavaScript('''
window.__login_status = null;
chrome.autotestPrivate.loginStatus(function(s) {
window.__login_status = s;
});
''')
return utils.poll_for_condition(
lambda: ext.EvaluateJavaScript('window.__login_status'),
timeout=10)
def disable_dim_display(self):
"""Avoid dim display.
@returns True if success otherwise False.
"""
ext = self.autotest_ext
if not ext:
return False
try:
ext.ExecuteJavaScript(
'''chrome.power.requestKeepAwake("display")''')
except:
logging.error("failed to disable dim display")
return False
return True
def get_visible_notifications(self):
"""Returns an array of visible notifications of Chrome.
For specific type of each notification, please refer to Chromium's
chrome/common/extensions/api/autotest_private.idl.
"""
ext = self.autotest_ext
if not ext:
return None
ext.ExecuteJavaScript('''
window.__items = null;
chrome.autotestPrivate.getVisibleNotifications(function(items) {
window.__items = items;
});
''')
if ext.EvaluateJavaScript('window.__items') is None:
return None
return ext.EvaluateJavaScript('window.__items')
@property
def browser_type(self):
"""Returns the browser_type."""
return self._browser_type
@staticmethod
def did_browser_crash(func):
"""Runs func, returns True if the browser crashed, False otherwise.
@param func: function to run.
"""
try:
func()
except Error:
return True
return False
@staticmethod
def wait_for_browser_restart(func, browser):
"""Runs func, and waits for a browser restart.
@param func: function to run.
"""
_cri = cros_interface.CrOSInterface()
pid = _cri.GetChromePid()
Chrome.did_browser_crash(func)
utils.poll_for_condition(
lambda: pid != _cri.GetChromePid(), timeout=60)
browser.WaitForBrowserToComeUp()
def wait_for_browser_to_come_up(self):
"""Waits for the browser to come up. This should only be called after a
browser crash.
"""
def _BrowserReady(cr):
tabs = [] # Wrapper for pass by reference.
if self.did_browser_crash(
lambda: tabs.append(cr.browser.tabs.New())):
return False
try:
tabs[0].Close()
except:
# crbug.com/350941
logging.error('Timed out closing tab')
return True
py_utils.WaitFor(lambda: _BrowserReady(self), timeout=10)
def close(self):
"""Closes the browser.
"""
try:
if utils.is_arc_available():
arc_util.pre_processing_before_close(self)
finally:
# Calling platform.StopAllLocalServers() to tear down the telemetry
# server processes such as the one started by
# platform.SetHTTPServerDirectories(). Not calling this function
# will leak the process and may affect test results.
# (crbug.com/663387)
self._browser.platform.StopAllLocalServers()
self._browser.Close()
self._browser_to_create.CleanUpEnvironment()
self._browser.platform.network_controller.Close()