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 7import exceptions 8import logging 9import time 10 11from autotest_lib.client.bin import utils 12from autotest_lib.client.common_lib.cros import chrome 13from autotest_lib.client.common_lib.cros import retry 14from autotest_lib.client.cros import constants 15 16import py_utils 17 18_FLAKY_CALL_RETRY_TIMEOUT_SEC = 60 19_FLAKY_CHROME_CALL_RETRY_DELAY_SEC = 1 20 21retry_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 27class 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. 37retry_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 45class 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.MULTIMEDIA_TEST_EXTENSION], 139 'extra_browser_args': self.EXTRA_BROWSER_ARGS, 140 'clear_enterprise_policy': not restart, 141 'arc_mode': arc_mode, 142 'autotest_ext': True 143 } 144 if extra_browser_args: 145 kwargs['extra_browser_args'] += extra_browser_args 146 return self.start_custom_chrome(kwargs) 147 148 149 def __enter__(self): 150 return self 151 152 153 def __exit__(self, *args): 154 if self._chrome: 155 self._chrome.close() 156 self._chrome = None 157 158 159 @staticmethod 160 def _generate_tab_descriptor(tab): 161 """Generate tab descriptor by tab object. 162 163 @param tab: the tab object. 164 @return a str, the tab descriptor of the tab. 165 166 """ 167 return hex(id(tab)) 168 169 170 def clean_unexpected_tabs(self): 171 """Clean all tabs that are not opened by facade_resource 172 173 It is used to make sure our chrome browser is clean. 174 175 """ 176 # If they have the same length we can assume there is no unexpected 177 # tabs. 178 browser_tabs = self.get_tabs() 179 if len(browser_tabs) == len(self._tabs): 180 return 181 182 for tab in browser_tabs: 183 if self._generate_tab_descriptor(tab) not in self._tabs: 184 # TODO(mojahsu): Reevaluate this code. crbug.com/719592 185 try: 186 tab.Close() 187 except py_utils.TimeoutException: 188 logging.warn('close tab timeout %r, %s', tab, tab.url) 189 190 191 @retry_chrome_call 192 def get_extension(self, extension_path=None): 193 """Gets the extension from the indicated path. 194 195 @param extension_path: the path of the target extension. 196 Set to None to get autotest extension. 197 Defaults to None. 198 @return an extension object. 199 200 @raise RuntimeError if the extension is not found. 201 @raise chrome.Error if the found extension has not yet been 202 retrieved succesfully. 203 204 """ 205 try: 206 if extension_path is None: 207 extension = self._chrome.autotest_ext 208 else: 209 extension = self._chrome.get_extension(extension_path) 210 except KeyError, errmsg: 211 # Trigger retry_chrome_call to retry to retrieve the 212 # found extension. 213 raise chrome.Error(errmsg) 214 if not extension: 215 if extension_path is None: 216 raise RuntimeError('Autotest extension not found') 217 else: 218 raise RuntimeError('Extension not found in %r' 219 % extension_path) 220 return extension 221 222 223 @retry_chrome_call 224 def load_url(self, url): 225 """Loads the given url in a new tab. The new tab will be active. 226 227 @param url: The url to load as a string. 228 @return a str, the tab descriptor of the opened tab. 229 230 """ 231 tab = self._browser.tabs.New() 232 tab.Navigate(url) 233 tab.Activate() 234 tab.WaitForDocumentReadyStateToBeComplete() 235 tab_descriptor = self._generate_tab_descriptor(tab) 236 self._tabs[tab_descriptor] = tab 237 self.clean_unexpected_tabs() 238 return tab_descriptor 239 240 241 def get_tabs(self): 242 """Gets the tabs opened by browser. 243 244 @returns: The tabs attribute in telemetry browser object. 245 246 """ 247 return self._browser.tabs 248 249 250 def get_tab_by_descriptor(self, tab_descriptor): 251 """Gets the tab by the tab descriptor. 252 253 @returns: The tab object indicated by the tab descriptor. 254 255 """ 256 return self._tabs[tab_descriptor] 257 258 259 @retry_chrome_call 260 def close_tab(self, tab_descriptor): 261 """Closes the tab. 262 263 @param tab_descriptor: Indicate which tab to be closed. 264 265 """ 266 if tab_descriptor not in self._tabs: 267 raise RuntimeError('There is no tab for %s' % tab_descriptor) 268 tab = self._tabs[tab_descriptor] 269 del self._tabs[tab_descriptor] 270 tab.Close() 271 self.clean_unexpected_tabs() 272 273 274 def wait_for_javascript_expression( 275 self, tab_descriptor, expression, timeout): 276 """Waits for the given JavaScript expression to be True on the given tab 277 278 @param tab_descriptor: Indicate on which tab to wait for the expression. 279 @param expression: Indiate for what expression to wait. 280 @param timeout: Indicate the timeout of the expression. 281 """ 282 if tab_descriptor not in self._tabs: 283 raise RuntimeError('There is no tab for %s' % tab_descriptor) 284 self._tabs[tab_descriptor].WaitForJavaScriptCondition( 285 expression, timeout=timeout) 286 287 288 def execute_javascript(self, tab_descriptor, statement, timeout): 289 """Executes a JavaScript statement on the given tab. 290 291 @param tab_descriptor: Indicate on which tab to execute the statement. 292 @param statement: Indiate what statement to execute. 293 @param timeout: Indicate the timeout of the statement. 294 """ 295 if tab_descriptor not in self._tabs: 296 raise RuntimeError('There is no tab for %s' % tab_descriptor) 297 self._tabs[tab_descriptor].ExecuteJavaScript( 298 statement, timeout=timeout) 299 300 301 def evaluate_javascript(self, tab_descriptor, expression, timeout): 302 """Evaluates a JavaScript expression on the given tab. 303 304 @param tab_descriptor: Indicate on which tab to evaluate the expression. 305 @param expression: Indiate what expression to evaluate. 306 @param timeout: Indicate the timeout of the expression. 307 @return the JSONized result of the given expression 308 """ 309 if tab_descriptor not in self._tabs: 310 raise RuntimeError('There is no tab for %s' % tab_descriptor) 311 return self._tabs[tab_descriptor].EvaluateJavaScript( 312 expression, timeout=timeout) 313