Home | History | Annotate | Download | only in chrome_testing
      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 
      5 import logging
      6 
      7 from autotest_lib.client.bin import utils
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib.cros import chrome
     10 
     11 class 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