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