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