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