• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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