Home | History | Annotate | Download | only in multimedia
      1 # Copyright 2015 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """A module providing common resources for different facades."""
      6 
      7 import exceptions
      8 import logging
      9 import time
     10 
     11 from autotest_lib.client.bin import utils
     12 from autotest_lib.client.common_lib.cros import chrome
     13 from autotest_lib.client.common_lib.cros import retry
     14 from autotest_lib.client.cros import constants
     15 
     16 _FLAKY_CALL_RETRY_TIMEOUT_SEC = 60
     17 _FLAKY_CHROME_CALL_RETRY_DELAY_SEC = 1
     18 
     19 retry_chrome_call = retry.retry(
     20         (chrome.Error, exceptions.IndexError, exceptions.Exception),
     21         timeout_min=_FLAKY_CALL_RETRY_TIMEOUT_SEC / 60.0,
     22         delay_sec=_FLAKY_CHROME_CALL_RETRY_DELAY_SEC)
     23 
     24 
     25 class FacadeResoureError(Exception):
     26     """Error in FacadeResource."""
     27     pass
     28 
     29 
     30 _FLAKY_CHROME_START_RETRY_TIMEOUT_SEC = 120
     31 _FLAKY_CHROME_START_RETRY_DELAY_SEC = 10
     32 
     33 
     34 # Telemetry sometimes fails to start Chrome.
     35 retry_start_chrome = retry.retry(
     36         (Exception,),
     37         timeout_min=_FLAKY_CHROME_START_RETRY_TIMEOUT_SEC / 60.0,
     38         delay_sec=_FLAKY_CHROME_START_RETRY_DELAY_SEC,
     39         exception_to_raise=FacadeResoureError,
     40         label='Start Chrome')
     41 
     42 
     43 class FacadeResource(object):
     44     """This class provides access to telemetry chrome wrapper."""
     45 
     46     ARC_DISABLED = 'disabled'
     47     ARC_ENABLED = 'enabled'
     48     ARC_VERSION = 'CHROMEOS_ARC_VERSION'
     49     EXTRA_BROWSER_ARGS = ['--enable-gpu-benchmarking', '--use-fake-ui-for-media-stream']
     50 
     51     def __init__(self, chrome_object=None, restart=False):
     52         """Initializes a FacadeResource.
     53 
     54         @param chrome_object: A chrome.Chrome object or None.
     55         @param restart: Preserve the previous browser state.
     56 
     57         """
     58         self._chrome = chrome_object
     59 
     60     @property
     61     def _browser(self):
     62         """Gets the browser object from Chrome."""
     63         return self._chrome.browser
     64 
     65 
     66     @retry_start_chrome
     67     def _start_chrome(self, kwargs):
     68         """Start a Chrome with given arguments.
     69 
     70         @param kwargs: A dict of keyword arguments passed to Chrome.
     71 
     72         @return: A chrome.Chrome object.
     73 
     74         """
     75         logging.debug('Try to start Chrome with kwargs: %s', kwargs)
     76         return chrome.Chrome(**kwargs)
     77 
     78 
     79     def start_custom_chrome(self, kwargs):
     80         """Start a custom Chrome with given arguments.
     81 
     82         @param kwargs: A dict of keyword arguments passed to Chrome.
     83 
     84         @return: True on success, False otherwise.
     85 
     86         """
     87         # Close the previous Chrome.
     88         if self._chrome:
     89             self._chrome.close()
     90 
     91         # Start the new Chrome.
     92         try:
     93             self._chrome = self._start_chrome(kwargs)
     94         except FacadeResoureError:
     95             logging.error('Failed to start Chrome after retries')
     96             return False
     97         else:
     98             logging.info('Chrome started successfully')
     99 
    100         # The opened tabs are stored by tab descriptors.
    101         # Key is the tab descriptor string.
    102         # We use string as the key because of RPC Call. Client can use the
    103         # string to locate the tab object.
    104         # Value is the tab object.
    105         self._tabs = dict()
    106 
    107         # Workaround for issue crbug.com/588579.
    108         # On daisy, Chrome freezes about 30 seconds after login because of
    109         # TPM error. Avoid test accessing Chrome during this time.
    110         # Check issue crbug.com/588579 and crbug.com/591646.
    111         if utils.get_board() == 'daisy':
    112             logging.warning('Delay 30s for issue 588579 on daisy')
    113             time.sleep(30)
    114 
    115         return True
    116 
    117 
    118     def start_default_chrome(self, restart=False, extra_browser_args=None):
    119         """Start the default Chrome.
    120 
    121         @param restart: True to start Chrome without clearing previous state.
    122         @param extra_browser_args: A list containing extra browser args passed
    123                                    to Chrome. This list will be appened to
    124                                    default EXTRA_BROWSER_ARGS.
    125 
    126         @return: True on success, False otherwise.
    127 
    128         """
    129         # TODO: (crbug.com/618111) Add test driven switch for
    130         # supporting arc_mode enabled or disabled. At this time
    131         # if ARC build is tested, arc_mode is always enabled.
    132         arc_mode = self.ARC_DISABLED
    133         if utils.get_board_property(self.ARC_VERSION):
    134             arc_mode = self.ARC_ENABLED
    135         kwargs = {
    136             'extension_paths': [constants.MULTIMEDIA_TEST_EXTENSION],
    137             'extra_browser_args': self.EXTRA_BROWSER_ARGS,
    138             'clear_enterprise_policy': not restart,
    139             'arc_mode': arc_mode,
    140             'autotest_ext': True
    141         }
    142         if extra_browser_args:
    143             kwargs['extra_browser_args'] += extra_browser_args
    144         return self.start_custom_chrome(kwargs)
    145 
    146 
    147     def __enter__(self):
    148         return self
    149 
    150 
    151     def __exit__(self, *args):
    152         if self._chrome:
    153             self._chrome.close()
    154             self._chrome = None
    155 
    156 
    157     @staticmethod
    158     def _generate_tab_descriptor(tab):
    159         """Generate tab descriptor by tab object.
    160 
    161         @param tab: the tab object.
    162         @return a str, the tab descriptor of the tab.
    163 
    164         """
    165         return hex(id(tab))
    166 
    167 
    168     def clean_unexpected_tabs(self):
    169         """Clean all tabs that are not opened by facade_resource
    170 
    171         It is used to make sure our chrome browser is clean.
    172 
    173         """
    174         # If they have the same length we can assume there is no unexpected
    175         # tabs.
    176         browser_tabs = self.get_tabs()
    177         if len(browser_tabs) == len(self._tabs):
    178             return
    179 
    180         for tab in browser_tabs:
    181             if self._generate_tab_descriptor(tab) not in self._tabs:
    182                 tab.Close()
    183 
    184 
    185     @retry_chrome_call
    186     def get_extension(self, extension_path=None):
    187         """Gets the extension from the indicated path.
    188 
    189         @param extension_path: the path of the target extension.
    190                                Set to None to get autotest extension.
    191                                Defaults to None.
    192         @return an extension object.
    193 
    194         @raise RuntimeError if the extension is not found.
    195         @raise chrome.Error if the found extension has not yet been
    196                retrieved succesfully.
    197 
    198         """
    199         try:
    200             if extension_path is None:
    201                 extension = self._chrome.autotest_ext
    202             else:
    203                 extension = self._chrome.get_extension(extension_path)
    204         except KeyError, errmsg:
    205             # Trigger retry_chrome_call to retry to retrieve the
    206             # found extension.
    207             raise chrome.Error(errmsg)
    208         if not extension:
    209             if extension_path is None:
    210                 raise RuntimeError('Autotest extension not found')
    211             else:
    212                 raise RuntimeError('Extension not found in %r'
    213                                     % extension_path)
    214         return extension
    215 
    216 
    217     @retry_chrome_call
    218     def load_url(self, url):
    219         """Loads the given url in a new tab. The new tab will be active.
    220 
    221         @param url: The url to load as a string.
    222         @return a str, the tab descriptor of the opened tab.
    223 
    224         """
    225         tab = self._browser.tabs.New()
    226         tab.Navigate(url)
    227         tab.Activate()
    228         tab.WaitForDocumentReadyStateToBeComplete()
    229         tab_descriptor = self._generate_tab_descriptor(tab)
    230         self._tabs[tab_descriptor] = tab
    231         self.clean_unexpected_tabs()
    232         return tab_descriptor
    233 
    234 
    235     def get_tabs(self):
    236         """Gets the tabs opened by browser.
    237 
    238         @returns: The tabs attribute in telemetry browser object.
    239 
    240         """
    241         return self._browser.tabs
    242 
    243 
    244     def get_tab_by_descriptor(self, tab_descriptor):
    245         """Gets the tab by the tab descriptor.
    246 
    247         @returns: The tab object indicated by the tab descriptor.
    248 
    249         """
    250         return self._tabs[tab_descriptor]
    251 
    252 
    253     @retry_chrome_call
    254     def close_tab(self, tab_descriptor):
    255         """Closes the tab.
    256 
    257         @param tab_descriptor: Indicate which tab to be closed.
    258 
    259         """
    260         if tab_descriptor not in self._tabs:
    261             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    262         tab = self._tabs[tab_descriptor]
    263         del self._tabs[tab_descriptor]
    264         tab.Close()
    265         self.clean_unexpected_tabs()
    266 
    267 
    268     def wait_for_javascript_expression(
    269             self, tab_descriptor, expression, timeout):
    270         """Waits for the given JavaScript expression to be True on the given tab
    271 
    272         @param tab_descriptor: Indicate on which tab to wait for the expression.
    273         @param expression: Indiate for what expression to wait.
    274         @param timeout: Indicate the timeout of the expression.
    275         """
    276         if tab_descriptor not in self._tabs:
    277             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    278         self._tabs[tab_descriptor].WaitForJavaScriptCondition(
    279                 expression, timeout=timeout)
    280 
    281 
    282     def execute_javascript(self, tab_descriptor, statement, timeout):
    283         """Executes a JavaScript statement on the given tab.
    284 
    285         @param tab_descriptor: Indicate on which tab to execute the statement.
    286         @param statement: Indiate what statement to execute.
    287         @param timeout: Indicate the timeout of the statement.
    288         """
    289         if tab_descriptor not in self._tabs:
    290             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    291         self._tabs[tab_descriptor].ExecuteJavaScript(
    292                 statement, timeout=timeout)
    293 
    294 
    295     def evaluate_javascript(self, tab_descriptor, expression, timeout):
    296         """Evaluates a JavaScript expression on the given tab.
    297 
    298         @param tab_descriptor: Indicate on which tab to evaluate the expression.
    299         @param expression: Indiate what expression to evaluate.
    300         @param timeout: Indicate the timeout of the expression.
    301         @return the JSONized result of the given expression
    302         """
    303         if tab_descriptor not in self._tabs:
    304             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    305         return self._tabs[tab_descriptor].EvaluateJavaScript(
    306                 expression, timeout=timeout)
    307