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.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