1# Copyright (c) 2013 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 5import logging 6 7from autotest_lib.client.bin import utils 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.common_lib.cros import chrome 10 11class ChromeNetworkingTestContext(object): 12 """ 13 ChromeNetworkingTestContext handles creating a Chrome browser session and 14 launching a set of Chrome extensions on it. It provides handles for 15 telemetry extension objects, which can be used to inject JavaScript from 16 autotest. 17 18 Apart from user provided extensions, ChromeNetworkingTestContext always 19 loads the default network testing extension 'network_test_ext' which 20 provides some boilerplate around chrome.networkingPrivate calls. 21 22 Example usage: 23 24 context = ChromeNetworkingTestContext() 25 context.setup() 26 extension = context.network_test_extension() 27 extension.EvaluateJavaScript('var foo = 1; return foo + 1;') 28 context.teardown() 29 30 ChromeNetworkingTestContext also supports the Python 'with' syntax for 31 syntactic sugar. 32 33 """ 34 35 NETWORK_TEST_EXTENSION_PATH = ('/usr/local/autotest/cros/networking/' 36 'chrome_testing/network_test_ext') 37 FIND_NETWORKS_TIMEOUT = 5 38 39 # Network type strings used by chrome.networkingPrivate 40 CHROME_NETWORK_TYPE_ETHERNET = 'Ethernet' 41 CHROME_NETWORK_TYPE_WIFI = 'WiFi' 42 CHROME_NETWORK_TYPE_BLUETOOTH = 'Bluetooth' 43 CHROME_NETWORK_TYPE_CELLULAR = 'Cellular' 44 CHROME_NETWORK_TYPE_VPN = 'VPN' 45 CHROME_NETWORK_TYPE_ALL = 'All' 46 47 def __init__(self, extensions=None, username=None, password=None, 48 gaia_login=False): 49 if extensions is None: 50 extensions = [] 51 extensions.append(self.NETWORK_TEST_EXTENSION_PATH) 52 self._extension_paths = extensions 53 self._username = username 54 self._password = password 55 self._gaia_login = gaia_login 56 self._chrome = None 57 58 def __enter__(self): 59 self.setup() 60 return self 61 62 def __exit__(self, *args): 63 self.teardown() 64 65 def _create_browser(self): 66 self._chrome = chrome.Chrome(logged_in=True, 67 gaia_login=self._gaia_login, 68 extension_paths=self._extension_paths, 69 username=self._username, 70 password=self._password) 71 72 # TODO(armansito): This call won't be necessary once crbug.com/251913 73 # gets fixed. 74 self._ensure_network_test_extension_is_ready() 75 76 def _ensure_network_test_extension_is_ready(self): 77 self.network_test_extension.WaitForJavaScriptCondition( 78 "typeof chromeTesting != 'undefined'", timeout=30) 79 80 def _get_extension(self, path): 81 if self._chrome is None: 82 raise error.TestFail('A browser session has not been setup.') 83 extension = self._chrome.get_extension(path) 84 if extension is None: 85 raise error.TestFail('Failed to find loaded extension "%s"' % path) 86 return extension 87 88 def setup(self): 89 """ 90 Initializes a ChromeOS browser session that loads the given extensions 91 with private API priviliges. 92 93 """ 94 logging.info('ChromeNetworkingTestContext: setup') 95 self._create_browser() 96 self.STATUS_PENDING = self.network_test_extension.EvaluateJavaScript( 97 'chromeTesting.STATUS_PENDING') 98 self.STATUS_SUCCESS = self.network_test_extension.EvaluateJavaScript( 99 'chromeTesting.STATUS_SUCCESS') 100 self.STATUS_FAILURE = self.network_test_extension.EvaluateJavaScript( 101 'chromeTesting.STATUS_FAILURE') 102 103 def teardown(self): 104 """ 105 Closes the browser session. 106 107 """ 108 logging.info('ChromeNetworkingTestContext: teardown') 109 if self._chrome: 110 self._chrome.browser.Close() 111 self._chrome = None 112 113 @property 114 def network_test_extension(self): 115 """ 116 @return Handle to the metworking test Chrome extension instance. 117 @raises error.TestFail if the browser has not been set up or if the 118 extension cannot get acquired. 119 120 """ 121 return self._get_extension(self.NETWORK_TEST_EXTENSION_PATH) 122 123 def call_test_function_async(self, function, *args): 124 """ 125 Asynchronously executes a JavaScript function that belongs to 126 "chromeTesting.networking" as defined in network_test_ext. The 127 return value (or call status) can be obtained at a later time via 128 "chromeTesting.networking.callStatus.<|function|>" 129 130 @param function: The name of the function to execute. 131 @param args: The list of arguments that are to be passed to |function|. 132 Note that strings in JavaScript are quoted using double quotes, 133 and this function won't convert string arguments to JavaScript 134 strings. To pass a string, the string itself must contain the 135 quotes, i.e. '"string"', otherwise the contents of the Python 136 string will be compiled as a JS token. 137 @raises exceptions.EvaluateException, in case of an error during JS 138 execution. 139 140 """ 141 arguments = ', '.join(str(i) for i in args) 142 extension = self.network_test_extension 143 extension.ExecuteJavaScript( 144 'chromeTesting.networking.' + function + '(' + arguments + ');') 145 146 def wait_for_condition_on_expression_result( 147 self, expression, condition, timeout): 148 """ 149 Blocks until |condition| returns True when applied to the result of the 150 JavaScript expression |expression|. 151 152 @param expression: JavaScript expression to evaluate. 153 @param condition: A function that accepts a single argument and returns 154 a boolean. 155 @param timeout: The timeout interval length, in seconds, after which 156 this method will raise an error. 157 @raises error.TestFail, if the conditions is not met within the given 158 timeout interval. 159 160 """ 161 extension = self.network_test_extension 162 def _evaluate_expr(): 163 return extension.EvaluateJavaScript(expression) 164 utils.poll_for_condition( 165 lambda: condition(_evaluate_expr()), 166 error.TestFail( 167 'Timed out waiting for condition on expression: ' + 168 expression), 169 timeout) 170 return _evaluate_expr() 171 172 def call_test_function(self, timeout, function, *args): 173 """ 174 Executes a JavaScript function that belongs to 175 "chromeTesting.networking" and blocks until the function has completed 176 its execution. A function is considered to have completed if the result 177 of "chromeTesting.networking.callStatus.<|function|>.status" equals 178 STATUS_SUCCESS or STATUS_FAILURE. 179 180 @param timeout: The timeout interval, in seconds, for which this 181 function will block. If the call status is still STATUS_PENDING 182 after the timeout expires, then an error will be raised. 183 @param function: The name of the function to execute. 184 @param args: The list of arguments that are to be passed to |function|. 185 See the docstring for "call_test_function_async" for a more 186 detailed description. 187 @raises exceptions.EvaluateException, in case of an error during JS 188 execution. 189 @raises error.TestFail, if the function doesn't finish executing within 190 |timeout|. 191 192 """ 193 self.call_test_function_async(function, *args) 194 return self.wait_for_condition_on_expression_result( 195 'chromeTesting.networking.callStatus.' + function, 196 lambda x: (x is not None and 197 x['status'] != self.STATUS_PENDING), 198 timeout) 199 200 def find_cellular_networks(self): 201 """ 202 Queries the current cellular networks. 203 204 @return A list containing the found cellular networks. 205 206 """ 207 return self.find_networks(self.CHROME_NETWORK_TYPE_CELLULAR) 208 209 def find_wifi_networks(self): 210 """ 211 Queries the current wifi networks. 212 213 @return A list containing the found wifi networks. 214 215 """ 216 return self.find_networks(self.CHROME_NETWORK_TYPE_WIFI) 217 218 def find_networks(self, network_type): 219 """ 220 Queries the current networks of the queried type. 221 222 @param network_type: One of CHROME_NETWORK_TYPE_* strings. 223 224 @return A list containing the found cellular networks. 225 226 """ 227 call_status = self.call_test_function( 228 self.FIND_NETWORKS_TIMEOUT, 229 'findNetworks', 230 '"' + network_type + '"') 231 if call_status['status'] == self.STATUS_FAILURE: 232 raise error.TestFail( 233 'Failed to get networks: ' + call_status['error']) 234 networks = call_status['result'] 235 if type(networks) != list: 236 raise error.TestFail( 237 'Expected a list, found "' + repr(networks) + '".') 238 return networks 239