blob: 68a02c8e7e0e161bbe6d61d95a075b674eed46f3 [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2015 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.
"""A module providing common resources for different facades."""
import exceptions
import logging
import time
from autotest_lib.client.bin import utils
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome
from autotest_lib.client.common_lib.cros import retry
from autotest_lib.client.cros import constants
from telemetry.internal.backends.chrome_inspector import devtools_http
import py_utils
_FLAKY_CALL_RETRY_TIMEOUT_SEC = 60
_FLAKY_CHROME_CALL_RETRY_DELAY_SEC = 1
retry_chrome_call = retry.retry(
(chrome.Error, exceptions.IndexError, exceptions.Exception),
timeout_min=_FLAKY_CALL_RETRY_TIMEOUT_SEC / 60.0,
delay_sec=_FLAKY_CHROME_CALL_RETRY_DELAY_SEC)
class FacadeResoureError(Exception):
"""Error in FacadeResource."""
pass
_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC = 120
_FLAKY_CHROME_START_RETRY_DELAY_SEC = 10
# Telemetry sometimes fails to start Chrome.
retry_start_chrome = retry.retry(
(Exception,),
timeout_min=_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC / 60.0,
delay_sec=_FLAKY_CHROME_START_RETRY_DELAY_SEC,
exception_to_raise=FacadeResoureError,
label='Start Chrome')
class FacadeResource(object):
"""This class provides access to telemetry chrome wrapper."""
ARC_DISABLED = 'disabled'
ARC_ENABLED = 'enabled'
ARC_VERSION = 'CHROMEOS_ARC_VERSION'
EXTRA_BROWSER_ARGS = ['--enable-gpu-benchmarking', '--use-fake-ui-for-media-stream']
def __init__(self, chrome_object=None, restart=False):
"""Initializes a FacadeResource.
@param chrome_object: A chrome.Chrome object or None.
@param restart: Preserve the previous browser state.
"""
self._chrome = chrome_object
@property
def _browser(self):
"""Gets the browser object from Chrome."""
return self._chrome.browser
@retry_start_chrome
def _start_chrome(self, kwargs):
"""Start a Chrome with given arguments.
@param kwargs: A dict of keyword arguments passed to Chrome.
@return: A chrome.Chrome object.
"""
logging.debug('Try to start Chrome with kwargs: %s', kwargs)
return chrome.Chrome(**kwargs)
def start_custom_chrome(self, kwargs):
"""Start a custom Chrome with given arguments.
@param kwargs: A dict of keyword arguments passed to Chrome.
@return: True on success, False otherwise.
"""
# Close the previous Chrome.
if self._chrome:
self._chrome.close()
# Start the new Chrome.
try:
self._chrome = self._start_chrome(kwargs)
except FacadeResoureError:
logging.error('Failed to start Chrome after retries')
return False
else:
logging.info('Chrome started successfully')
# The opened tabs are stored by tab descriptors.
# Key is the tab descriptor string.
# We use string as the key because of RPC Call. Client can use the
# string to locate the tab object.
# Value is the tab object.
self._tabs = dict()
# Workaround for issue crbug.com/588579.
# On daisy, Chrome freezes about 30 seconds after login because of
# TPM error. Avoid test accessing Chrome during this time.
# Check issue crbug.com/588579 and crbug.com/591646.
if utils.get_board() == 'daisy':
logging.warning('Delay 30s for issue 588579 on daisy')
time.sleep(30)
return True
def start_default_chrome(self, restart=False, extra_browser_args=None,
disable_arc=False):
"""Start the default Chrome.
@param restart: True to start Chrome without clearing previous state.
@param extra_browser_args: A list containing extra browser args passed
to Chrome. This list will be appened to
default EXTRA_BROWSER_ARGS.
@param disable_arc: True to disable ARC++.
@return: True on success, False otherwise.
"""
# TODO: (crbug.com/618111) Add test driven switch for
# supporting arc_mode enabled or disabled. At this time
# if ARC build is tested, arc_mode is always enabled.
if not disable_arc and utils.get_board_property(self.ARC_VERSION):
arc_mode = self.ARC_ENABLED
else:
arc_mode = self.ARC_DISABLED
kwargs = {
'extension_paths': [constants.AUDIO_TEST_EXTENSION,
constants.DISPLAY_TEST_EXTENSION],
'extra_browser_args': self.EXTRA_BROWSER_ARGS,
'clear_enterprise_policy': not restart,
'arc_mode': arc_mode,
'autotest_ext': True
}
if extra_browser_args:
kwargs['extra_browser_args'] += extra_browser_args
return self.start_custom_chrome(kwargs)
def __enter__(self):
return self
def __exit__(self, *args):
if self._chrome:
self._chrome.close()
self._chrome = None
@staticmethod
def _generate_tab_descriptor(tab):
"""Generate tab descriptor by tab object.
@param tab: the tab object.
@return a str, the tab descriptor of the tab.
"""
return hex(id(tab))
def clean_unexpected_tabs(self):
"""Clean all tabs that are not opened by facade_resource
It is used to make sure our chrome browser is clean.
"""
# If they have the same length we can assume there is no unexpected
# tabs.
browser_tabs = self.get_tabs()
if len(browser_tabs) == len(self._tabs):
return
for tab in browser_tabs:
if self._generate_tab_descriptor(tab) not in self._tabs:
# TODO(mojahsu): Reevaluate this code. crbug.com/719592
try:
tab.Close()
except py_utils.TimeoutException:
logging.warn('close tab timeout %r, %s', tab, tab.url)
@retry_chrome_call
def get_extension(self, extension_path=None):
"""Gets the extension from the indicated path.
@param extension_path: the path of the target extension.
Set to None to get autotest extension.
Defaults to None.
@return an extension object.
@raise RuntimeError if the extension is not found.
@raise chrome.Error if the found extension has not yet been
retrieved succesfully.
"""
try:
if extension_path is None:
extension = self._chrome.autotest_ext
else:
extension = self._chrome.get_extension(extension_path)
except KeyError as errmsg:
# Trigger retry_chrome_call to retry to retrieve the
# found extension.
raise chrome.Error(errmsg)
if not extension:
if extension_path is None:
raise RuntimeError('Autotest extension not found')
else:
raise RuntimeError('Extension not found in %r'
% extension_path)
return extension
def get_visible_notifications(self):
"""Gets the visible notifications
@return: Returns all visible notifications in list format. Ex:
[{title:'', message:'', prority:'', id:''}]
"""
return self._chrome.get_visible_notifications()
@retry_chrome_call
def load_url(self, url):
"""Loads the given url in a new tab. The new tab will be active.
@param url: The url to load as a string.
@return a str, the tab descriptor of the opened tab.
"""
tab = self._browser.tabs.New()
tab.Navigate(url)
tab.Activate()
tab.WaitForDocumentReadyStateToBeComplete()
tab_descriptor = self._generate_tab_descriptor(tab)
self._tabs[tab_descriptor] = tab
self.clean_unexpected_tabs()
return tab_descriptor
def set_http_server_directories(self, directories):
"""Starts an HTTP server.
@param directories: Directories to start serving.
@return True on success. False otherwise.
"""
return self._chrome.browser.platform.SetHTTPServerDirectories(directories)
def http_server_url_of(self, fullpath):
"""Converts a path to a URL.
@param fullpath: String containing the full path to the content.
@return the URL for the provided path.
"""
return self._chrome.browser.platform.http_server.UrlOf(fullpath)
def get_tabs(self):
"""Gets the tabs opened by browser.
@returns: The tabs attribute in telemetry browser object.
"""
return self._browser.tabs
def get_tab_by_descriptor(self, tab_descriptor):
"""Gets the tab by the tab descriptor.
@returns: The tab object indicated by the tab descriptor.
"""
return self._tabs[tab_descriptor]
@retry_chrome_call
def close_tab(self, tab_descriptor):
"""Closes the tab.
@param tab_descriptor: Indicate which tab to be closed.
"""
if tab_descriptor not in self._tabs:
raise RuntimeError('There is no tab for %s' % tab_descriptor)
tab = self._tabs[tab_descriptor]
del self._tabs[tab_descriptor]
tab.Close()
self.clean_unexpected_tabs()
def wait_for_javascript_expression(
self, tab_descriptor, expression, timeout):
"""Waits for the given JavaScript expression to be True on the given tab
@param tab_descriptor: Indicate on which tab to wait for the expression.
@param expression: Indiate for what expression to wait.
@param timeout: Indicate the timeout of the expression.
"""
if tab_descriptor not in self._tabs:
raise RuntimeError('There is no tab for %s' % tab_descriptor)
self._tabs[tab_descriptor].WaitForJavaScriptCondition(
expression, timeout=timeout)
def execute_javascript(self, tab_descriptor, statement, timeout):
"""Executes a JavaScript statement on the given tab.
@param tab_descriptor: Indicate on which tab to execute the statement.
@param statement: Indiate what statement to execute.
@param timeout: Indicate the timeout of the statement.
"""
if tab_descriptor not in self._tabs:
raise RuntimeError('There is no tab for %s' % tab_descriptor)
self._tabs[tab_descriptor].ExecuteJavaScript(
statement, timeout=timeout)
def evaluate_javascript(self, tab_descriptor, expression, timeout):
"""Evaluates a JavaScript expression on the given tab.
@param tab_descriptor: Indicate on which tab to evaluate the expression.
@param expression: Indiate what expression to evaluate.
@param timeout: Indicate the timeout of the expression.
@return the JSONized result of the given expression
"""
if tab_descriptor not in self._tabs:
raise RuntimeError('There is no tab for %s' % tab_descriptor)
return self._tabs[tab_descriptor].EvaluateJavaScript(
expression, timeout=timeout)
class Application(FacadeResource):
""" This class provides access to WebStore Applications"""
APP_NAME_IDS = {
'camera' : 'njfbnohfdkmbmnjapinfcopialeghnmh',
'files' : 'hhaomjibdihmijegdhdafkllkbggdgoj'
}
# Time in seconds to load the app
LOAD_TIME = 5
def __init__(self, chrome_object=None):
super(Application, self).__init__(chrome_object)
@retry_chrome_call
def evaluate_javascript(self, code):
"""Executes javascript and returns some result.
Occasionally calls to EvaluateJavascript on the autotest_ext will fail
to find the extension. Instead of wrapping every call in a try/except,
calls will go through this function instead.
@param code: The javascript string to execute
"""
try:
result = self._chrome.autotest_ext.EvaluateJavaScript(code)
return result
except KeyError:
logging.exception('Could not find autotest_ext')
except (devtools_http.DevToolsClientUrlError,
devtools_http.DevToolsClientConnectionError):
logging.exception('Could not connect to DevTools')
raise error.TestError("Could not execute %s" % code)
def click_on(self, ui, name, isRegex=False, role=None):
"""
Click on given role and name matches
@ui: ui_utils object
@param name: item node name.
@param isRegex: If name is in regex format then isRegex should be
True otherwise False.
@param role: role of the element. Example: button or window etc.
@raise error.TestError if the test is failed to find given node
"""
if not ui.item_present(name, isRegex=isRegex, role=role):
raise error.TestError("name=%s, role=%s did not appeared with in "
"time" % (name, role))
ui.doDefault_on_obj(name, isRegex=isRegex, role=role)
def is_app_opened(self, name):
"""
Verify if the Webstore app is opened or not
@param name: Name of the app to verify.
"""
self.evaluate_javascript("var isShown = null;")
is_app_shown_js = """
chrome.autotestPrivate.isAppShown('%s',
function(appShown){isShown = appShown});
""" % self.APP_NAME_IDS[name.lower()]
self.evaluate_javascript(is_app_shown_js)
return self.evaluate_javascript('isShown')
def launch_app(self, name):
"""
Launch the app/extension by its ID and verify that it opens.
@param name: Name of the app to launch.
"""
logging.info("Launching %s app" % name)
if name == "camera":
webapps_js = "chrome.autotestPrivate.waitForSystemWebAppsInstall(" \
"function(){})"
self.evaluate_javascript(webapps_js)
launch_js = "chrome.autotestPrivate.launchSystemWebApp('%s', '%s', " \
"function(){})" % ("Camera",
"chrome://camera-app/views/main.html")
else:
launch_js = "chrome.autotestPrivate.launchApp('%s', function(){})" \
% self.APP_NAME_IDS[name.lower()]
self.evaluate_javascript(launch_js)
def is_app_opened():
return self.is_app_opened(name)
utils.poll_for_condition(condition=is_app_opened,
desc="%s app is not launched" % name,
timeout=self.LOAD_TIME)
logging.info('%s app is launched', name)
def close_app(self, name):
"""
Close the app/extension by its ID and verify that it closes.
@param name: Name of the app to close.
"""
close_js = "chrome.autotestPrivate.closeApp('%s', function(){})" \
% self.APP_NAME_IDS[name.lower()]
self.evaluate_javascript(close_js)
def is_app_closed():
return not self.is_app_opened(name)
utils.poll_for_condition(condition=is_app_closed,
desc="%s app is not closed" % name,
timeout=self.LOAD_TIME)
logging.info('%s app is closed', name)