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     def get_visible_notifications(self):
    225         """Gets the visible notifications
    226 
    227         @return: Returns all visible notifications in list format. Ex:
    228                 [{title:'', message:'', prority:'', id:''}]
    229         """
    230         return self._chrome.get_visible_notifications()
    231 
    232 
    233     @retry_chrome_call
    234     def load_url(self, url):
    235         """Loads the given url in a new tab. The new tab will be active.
    236 
    237         @param url: The url to load as a string.
    238         @return a str, the tab descriptor of the opened tab.
    239 
    240         """
    241         tab = self._browser.tabs.New()
    242         tab.Navigate(url)
    243         tab.Activate()
    244         tab.WaitForDocumentReadyStateToBeComplete()
    245         tab_descriptor = self._generate_tab_descriptor(tab)
    246         self._tabs[tab_descriptor] = tab
    247         self.clean_unexpected_tabs()
    248         return tab_descriptor
    249 
    250 
    251     def set_http_server_directories(self, directories):
    252         """Starts an HTTP server.
    253 
    254         @param directories: Directories to start serving.
    255 
    256         @return True on success. False otherwise.
    257 
    258         """
    259         return self._chrome.browser.platform.SetHTTPServerDirectories(directories)
    260 
    261 
    262     def http_server_url_of(self, fullpath):
    263         """Converts a path to a URL.
    264 
    265         @param fullpath: String containing the full path to the content.
    266 
    267         @return the URL for the provided path.
    268 
    269         """
    270         return self._chrome.browser.platform.http_server.UrlOf(fullpath)
    271 
    272 
    273     def get_tabs(self):
    274         """Gets the tabs opened by browser.
    275 
    276         @returns: The tabs attribute in telemetry browser object.
    277 
    278         """
    279         return self._browser.tabs
    280 
    281 
    282     def get_tab_by_descriptor(self, tab_descriptor):
    283         """Gets the tab by the tab descriptor.
    284 
    285         @returns: The tab object indicated by the tab descriptor.
    286 
    287         """
    288         return self._tabs[tab_descriptor]
    289 
    290 
    291     @retry_chrome_call
    292     def close_tab(self, tab_descriptor):
    293         """Closes the tab.
    294 
    295         @param tab_descriptor: Indicate which tab to be closed.
    296 
    297         """
    298         if tab_descriptor not in self._tabs:
    299             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    300         tab = self._tabs[tab_descriptor]
    301         del self._tabs[tab_descriptor]
    302         tab.Close()
    303         self.clean_unexpected_tabs()
    304 
    305 
    306     def wait_for_javascript_expression(
    307             self, tab_descriptor, expression, timeout):
    308         """Waits for the given JavaScript expression to be True on the given tab
    309 
    310         @param tab_descriptor: Indicate on which tab to wait for the expression.
    311         @param expression: Indiate for what expression to wait.
    312         @param timeout: Indicate the timeout of the expression.
    313         """
    314         if tab_descriptor not in self._tabs:
    315             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    316         self._tabs[tab_descriptor].WaitForJavaScriptCondition(
    317                 expression, timeout=timeout)
    318 
    319 
    320     def execute_javascript(self, tab_descriptor, statement, timeout):
    321         """Executes a JavaScript statement on the given tab.
    322 
    323         @param tab_descriptor: Indicate on which tab to execute the statement.
    324         @param statement: Indiate what statement to execute.
    325         @param timeout: Indicate the timeout of the statement.
    326         """
    327         if tab_descriptor not in self._tabs:
    328             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    329         self._tabs[tab_descriptor].ExecuteJavaScript(
    330                 statement, timeout=timeout)
    331 
    332 
    333     def evaluate_javascript(self, tab_descriptor, expression, timeout):
    334         """Evaluates a JavaScript expression on the given tab.
    335 
    336         @param tab_descriptor: Indicate on which tab to evaluate the expression.
    337         @param expression: Indiate what expression to evaluate.
    338         @param timeout: Indicate the timeout of the expression.
    339         @return the JSONized result of the given expression
    340         """
    341         if tab_descriptor not in self._tabs:
    342             raise RuntimeError('There is no tab for %s' % tab_descriptor)
    343         return self._tabs[tab_descriptor].EvaluateJavaScript(
    344                 expression, timeout=timeout)
    345