Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import argparse | 5 import argparse |
| 6 import json | 6 import json |
| 7 import logging | |
| 7 import os | 8 import os |
| 8 import re | 9 import re |
| 9 import socket | 10 import socket |
| 10 import shlex | 11 import shlex |
| 11 import sys | 12 import sys |
| 12 import time | 13 import time |
| 13 import traceback | 14 import traceback |
| 14 import unittest | 15 import unittest |
| 15 | 16 |
| 16 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, | 17 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, |
| 17 os.pardir, 'third_party', 'webdriver', 'pylib')) | 18 os.pardir, 'third_party', 'webdriver', 'pylib')) |
| 18 from selenium import webdriver | 19 from selenium import webdriver |
| 19 from selenium.webdriver.chrome.options import Options | 20 from selenium.webdriver.chrome.options import Options |
| 20 | 21 |
| 21 # TODO(robertogden): Add logging. | |
| 22 | 22 |
|
RyanSturm
2016/12/14 23:04:28
remove extra vertical whitespace.
Robert Ogden
2016/12/15 17:15:09
Done.
| |
| 23 | 23 |
| 24 def ParseFlags(): | 24 def ParseFlags(): |
| 25 """Parses the given command line arguments. | 25 """Parses the given command line arguments. |
| 26 | 26 |
| 27 Returns: | 27 Returns: |
| 28 A new Namespace object with class properties for each argument added below. | 28 A new Namespace object with class properties for each argument added below. |
| 29 See pydoc for argparse. | 29 See pydoc for argparse. |
| 30 """ | 30 """ |
| 31 parser = argparse.ArgumentParser() | 31 parser = argparse.ArgumentParser() |
| 32 parser.add_argument('--browser_args', type=str, help='Override browser flags ' | 32 parser.add_argument('--browser_args', type=str, help='Override browser flags ' |
| 33 'in code with these flags') | 33 'in code with these flags') |
| 34 parser.add_argument('--via_header_value', | 34 parser.add_argument('--via_header_value', |
| 35 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' | 35 default='1.1 Chrome-Compression-Proxy', help='What the via should match to ' |
| 36 'be considered valid') | 36 'be considered valid') |
| 37 parser.add_argument('--android', help='If given, attempts to run the test on ' | 37 parser.add_argument('--android', help='If given, attempts to run the test on ' |
| 38 'Android via adb. Ignores usage of --chrome_exec', action='store_true') | 38 'Android via adb. Ignores usage of --chrome_exec', action='store_true') |
| 39 parser.add_argument('--android_package', | 39 parser.add_argument('--android_package', |
| 40 default='com.android.chrome', help='Set the android package for Chrome') | 40 default='com.android.chrome', help='Set the android package for Chrome') |
| 41 parser.add_argument('--chrome_exec', type=str, help='The path to ' | 41 parser.add_argument('--chrome_exec', type=str, help='The path to ' |
| 42 'the Chrome or Chromium executable') | 42 'the Chrome or Chromium executable') |
| 43 parser.add_argument('chrome_driver', type=str, help='The path to ' | 43 parser.add_argument('chrome_driver', type=str, help='The path to ' |
| 44 'the ChromeDriver executable. If not given, the default system chrome ' | 44 'the ChromeDriver executable. If not given, the default system chrome ' |
| 45 'will be used.') | 45 'will be used.') |
| 46 parser.add_argument('--disable_buffer', help='Causes stdout and stderr from ' | 46 parser.add_argument('--disable_buffer', help='Causes stdout and stderr from ' |
| 47 'tests to output normally. Otherwise, the standard output and standard ' | 47 'tests to output normally. Otherwise, the standard output and standard ' |
| 48 'error streams are buffered during the test run, and output during a ' | 48 'error streams are buffered during the test run, and output from a ' |
| 49 'passing test is discarded. Output will always be echoed normally on test ' | 49 'passing test is discarded. Output will always be echoed normally on test ' |
| 50 'fail or error and is added to the failure messages.', action='store_true') | 50 'fail or error and is added to the failure messages.', action='store_true') |
| 51 parser.add_argument('-c', '--catch', help='Control-C during the test run ' | 51 parser.add_argument('-c', '--catch', help='Control-C during the test run ' |
| 52 'waits for the current test to end and then reports all the results so ' | 52 'waits for the current test to end and then reports all the results so ' |
| 53 'far. A second Control-C raises the normal KeyboardInterrupt exception.', | 53 'far. A second Control-C raises the normal KeyboardInterrupt exception.', |
| 54 action='store_true') | 54 action='store_true') |
| 55 parser.add_argument('-f', '--failfast', help='Stop the test run on the first ' | 55 parser.add_argument('-f', '--failfast', help='Stop the test run on the first ' |
| 56 'error or failure.', action='store_true') | 56 'error or failure.', action='store_true') |
| 57 # TODO(robertogden): Log sys.argv here. | 57 parser.add_argument('--logging_level', choices=['DEBUG', 'INFO', 'WARN', |
| 58 'ERROR', 'CRIT'], default='ERROR', help='The logging level for log ' | |
|
RyanSturm
2016/12/14 23:04:28
s/level/threshold/ or "verbosity"
Robert Ogden
2016/12/15 17:15:09
Done.
| |
| 59 'message, printed to stderr. To see stderr logging output during a ' | |
| 60 'successful test run, also pass --disable_buffer. Default=ERROR') | |
| 61 parser.add_argument('--log_file', help='If given, write logging statements ' | |
| 62 'to the given file instead of stderr.') | |
| 58 return parser.parse_args(sys.argv[1:]) | 63 return parser.parse_args(sys.argv[1:]) |
| 59 | 64 |
| 65 def GetLogger(name='common'): | |
| 66 """Creates a Logger instance with the given name and returns it. | |
| 67 | |
| 68 If a logger has already been created with the same name, that instance is | |
| 69 returned instead. | |
| 70 | |
| 71 Args: | |
| 72 name: The name of the logger to return. | |
| 73 Returns: | |
| 74 A logger with the given name. | |
| 75 """ | |
| 76 logger = logging.getLogger(name) | |
| 77 if hasattr(logger, "initialized"): | |
| 78 return logger | |
| 79 logging_level = ParseFlags().logging_level | |
| 80 if logging_level == 'DEBUG': | |
| 81 logger.setLevel(logging.DEBUG) | |
| 82 elif logging_level == 'INFO': | |
| 83 logger.setLevel(logging.INFO) | |
| 84 elif logging_level == 'WARN': | |
| 85 logger.setLevel(logging.WARNING) | |
| 86 elif logging_level == 'ERROR': | |
| 87 logger.setLevel(logging.ERROR) | |
| 88 elif logging_level == 'CRIT': | |
| 89 logger.setLevel(logging.CRITICAL) | |
| 90 else: | |
| 91 logger.setLevel(logging.NOTSET) | |
| 92 formatter = logging.Formatter('%(asctime)s %(funcName)s() %(levelname)s: ' | |
| 93 '%(message)s') | |
| 94 if ParseFlags().log_file: | |
| 95 fh = logging.FileHandler(ParseFlags().log_file) | |
| 96 fh.setFormatter(formatter) | |
| 97 logger.addHandler(fh) | |
| 98 else: | |
| 99 ch = logging.StreamHandler(sys.stderr) | |
| 100 ch.setFormatter(formatter) | |
| 101 logger.addHandler(ch) | |
| 102 logger.initialized = True | |
| 103 return logger | |
| 60 | 104 |
| 61 class TestDriver: | 105 class TestDriver: |
| 62 """The main driver for an integration test. | 106 """The main driver for an integration test. |
| 63 | 107 |
| 64 This class is the tool that is used by every integration test to interact with | 108 This class is the tool that is used by every integration test to interact with |
| 65 the Chromium browser and validate proper functionality. This class sits on top | 109 the Chromium browser and validate proper functionality. This class sits on top |
| 66 of the Selenium Chrome Webdriver with added utility and helper functions for | 110 of the Selenium Chrome Webdriver with added utility and helper functions for |
| 67 Chrome-Proxy. This class should be used with Python's 'with' operator. | 111 Chrome-Proxy. This class should be used with Python's 'with' operator. |
| 68 | 112 |
| 69 Attributes: | 113 Attributes: |
| 70 _flags: A Namespace object from the call to ParseFlags() | 114 _flags: A Namespace object from the call to ParseFlags() |
| 71 _driver: A reference to the driver object from the Chrome Driver library. | 115 _driver: A reference to the driver object from the Chrome Driver library. |
| 72 _chrome_args: A set of string arguments to start Chrome with. | 116 _chrome_args: A set of string arguments to start Chrome with. |
| 73 _url: The string URL that Chrome will navigate to for this test. | 117 _url: The string URL that Chrome will navigate to for this test. |
| 74 """ | 118 """ |
| 75 | 119 |
| 76 def __init__(self): | 120 def __init__(self): |
| 77 self._flags = ParseFlags() | 121 self._flags = ParseFlags() |
| 78 self._driver = None | 122 self._driver = None |
| 79 self._chrome_args = set() | 123 self._chrome_args = set() |
| 80 self._url = '' | 124 self._url = '' |
| 125 self._logger = GetLogger(name='TestDriver') | |
| 81 | 126 |
| 82 def __enter__(self): | 127 def __enter__(self): |
| 83 return self | 128 return self |
| 84 | 129 |
| 85 def __exit__(self, exc_type, exc_value, tb): | 130 def __exit__(self, exc_type, exc_value, tb): |
| 86 if self._driver: | 131 if self._driver: |
| 87 self._StopDriver() | 132 self._StopDriver() |
| 88 | 133 |
| 89 def _OverrideChromeArgs(self): | 134 def _OverrideChromeArgs(self): |
| 90 """Overrides any given arguments in the code with those given on the command | 135 """Overrides any given arguments in the code with those given on the command |
| 91 line. | 136 line. |
| 92 | 137 |
| 93 Arguments that need to be overridden may contain different values for | 138 Arguments that need to be overridden may contain different values for |
| 94 a flag given in the code. In that case, check by the flag whether to | 139 a flag given in the code. In that case, check by the flag whether to |
| 95 override the argument. | 140 override the argument. |
| 96 """ | 141 """ |
| 97 def GetDictKey(argument): | 142 def GetDictKey(argument): |
| 98 return argument.split('=', 1)[0] | 143 return argument.split('=', 1)[0] |
| 99 if self._flags.browser_args and len(self._flags.browser_args) > 0: | 144 if self._flags.browser_args and len(self._flags.browser_args) > 0: |
| 100 # Build a dict of flags mapped to the whole argument. | 145 # Build a dict of flags mapped to the whole argument. |
| 101 original_args = {} | 146 original_args = {} |
| 102 for arg in self._chrome_args: | 147 for arg in self._chrome_args: |
| 103 original_args[GetDictKey(arg)] = arg | 148 original_args[GetDictKey(arg)] = arg |
| 104 # Override flags given in code with any command line arguments. | 149 # Override flags given in code with any command line arguments. |
| 105 for override_arg in shlex.split(self._flags.browser_args): | 150 for override_arg in shlex.split(self._flags.browser_args): |
| 106 arg_key = GetDictKey(override_arg) | 151 arg_key = GetDictKey(override_arg) |
| 107 if arg_key in original_args: | 152 if arg_key in original_args: |
| 108 self._chrome_args.remove(original_args[arg_key]) | 153 self._chrome_args.remove(original_args[arg_key]) |
| 154 self._logger.info('Removed Chrome flag. %s', original_args[arg_key]) | |
| 109 self._chrome_args.add(override_arg) | 155 self._chrome_args.add(override_arg) |
| 156 self._logger.info('Added Chrome flag. %s', override_arg) | |
| 110 # Always add the flag that allows histograms to be queried in javascript. | 157 # Always add the flag that allows histograms to be queried in javascript. |
| 111 self._chrome_args.add('--enable-stats-collection-bindings') | 158 self._chrome_args.add('--enable-stats-collection-bindings') |
| 112 | 159 |
| 113 def _StartDriver(self): | 160 def _StartDriver(self): |
| 114 """Parses the flags to pass to Chromium, then starts the ChromeDriver. | 161 """Parses the flags to pass to Chromium, then starts the ChromeDriver. |
| 115 | 162 |
| 116 If running Android, the Android package name is passed to ChromeDriver here. | 163 If running Android, the Android package name is passed to ChromeDriver here. |
| 117 """ | 164 """ |
| 118 self._OverrideChromeArgs() | 165 self._OverrideChromeArgs() |
| 119 capabilities = { | 166 capabilities = { |
| 120 'loggingPrefs': {'performance': 'INFO'}, | 167 'loggingPrefs': {'performance': 'INFO'}, |
| 121 'chromeOptions': { | 168 'chromeOptions': { |
| 122 'args': list(self._chrome_args) | 169 'args': list(self._chrome_args) |
| 123 } | 170 } |
| 124 } | 171 } |
| 125 if self._flags.android: | 172 if self._flags.android: |
| 126 capabilities['chromeOptions'].update({ | 173 capabilities['chromeOptions'].update({ |
| 127 'androidPackage': self._flags.android_package, | 174 'androidPackage': self._flags.android_package, |
| 128 }) | 175 }) |
| 176 self._logger.debug('Will use Chrome on Android') | |
| 129 elif self._flags.chrome_exec: | 177 elif self._flags.chrome_exec: |
| 130 capabilities['chrome.binary'] = self._flags.chrome_exec | 178 capabilities['chrome.binary'] = self._flags.chrome_exec |
| 179 self._logger.info('Using the Chrome binary at this path: %s', | |
| 180 self._flags.chrome_exec) | |
| 181 self._logger.info('Starting Chrome with these flags: %s', | |
| 182 str(self._chrome_args)) | |
| 183 self._logger.debug('Starting ChromeDriver with these capabilities: %s', | |
| 184 json.dumps(capabilities)) | |
| 131 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver, | 185 driver = webdriver.Chrome(executable_path=self._flags.chrome_driver, |
| 132 desired_capabilities=capabilities) | 186 desired_capabilities=capabilities) |
| 133 driver.command_executor._commands.update({ | 187 driver.command_executor._commands.update({ |
| 134 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), | 188 'getAvailableLogTypes': ('GET', '/session/$sessionId/log/types'), |
| 135 'getLog': ('POST', '/session/$sessionId/log')}) | 189 'getLog': ('POST', '/session/$sessionId/log')}) |
| 136 self._driver = driver | 190 self._driver = driver |
| 137 | 191 |
| 138 def _StopDriver(self): | 192 def _StopDriver(self): |
| 139 """Nicely stops the ChromeDriver. | 193 """Nicely stops the ChromeDriver. |
| 140 """ | 194 """ |
| 195 self._logger.debug('Stopping ChromeDriver') | |
| 141 self._driver.quit() | 196 self._driver.quit() |
| 142 self._driver = None | 197 self._driver = None |
| 143 | 198 |
| 144 def AddChromeArgs(self, args): | 199 def AddChromeArgs(self, args): |
| 145 """Adds multiple arguments that will be passed to Chromium at start. | 200 """Adds multiple arguments that will be passed to Chromium at start. |
| 146 | 201 |
| 147 Args: | 202 Args: |
| 148 args: An iterable of strings, each an argument to pass to Chrome at start. | 203 args: An iterable of strings, each an argument to pass to Chrome at start. |
| 149 """ | 204 """ |
| 150 for arg in args: | 205 for arg in args: |
| 151 self._chrome_args.add(arg) | 206 self._chrome_args.add(arg) |
| 152 | 207 |
| 153 def AddChromeArg(self, arg): | 208 def AddChromeArg(self, arg): |
| 154 """Adds a single argument that will be passed to Chromium at start. | 209 """Adds a single argument that will be passed to Chromium at start. |
| 155 | 210 |
| 156 Args: | 211 Args: |
| 157 arg: a string argument to pass to Chrome at start | 212 arg: a string argument to pass to Chrome at start |
| 158 """ | 213 """ |
| 159 self._chrome_args.add(arg) | 214 self._chrome_args.add(arg) |
| 215 self._logger.debug('Adding Chrome arg: %s', arg) | |
| 160 | 216 |
| 161 def RemoveChromeArgs(self, args): | 217 def RemoveChromeArgs(self, args): |
| 162 """Removes multiple arguments that will no longer be passed to Chromium at | 218 """Removes multiple arguments that will no longer be passed to Chromium at |
| 163 start. | 219 start. |
| 164 | 220 |
| 165 Args: | 221 Args: |
| 166 args: An iterable of strings to no longer use the next time Chrome | 222 args: An iterable of strings to no longer use the next time Chrome |
| 167 starts. | 223 starts. |
| 168 """ | 224 """ |
| 169 for arg in args: | 225 for arg in args: |
| 170 self._chrome_args.discard(arg) | 226 self._chrome_args.discard(arg) |
| 171 | 227 |
| 172 def RemoveChromeArg(self, arg): | 228 def RemoveChromeArg(self, arg): |
| 173 """Removes a single argument that will no longer be passed to Chromium at | 229 """Removes a single argument that will no longer be passed to Chromium at |
| 174 start. | 230 start. |
| 175 | 231 |
| 176 Args: | 232 Args: |
| 177 arg: A string flag to no longer use the next time Chrome starts. | 233 arg: A string flag to no longer use the next time Chrome starts. |
| 178 """ | 234 """ |
| 179 self._chrome_args.discard(arg) | 235 self._chrome_args.discard(arg) |
| 236 self._logger.debug('Removing Chrome arg: %s', arg) | |
| 180 | 237 |
| 181 def ClearChromeArgs(self): | 238 def ClearChromeArgs(self): |
| 182 """Removes all arguments from Chromium at start. | 239 """Removes all arguments from Chromium at start. |
| 183 """ | 240 """ |
| 184 self._chrome_args.clear() | 241 self._chrome_args.clear() |
| 242 self._logger.debug('Clearing all Chrome args') | |
| 185 | 243 |
| 186 def ClearCache(self): | 244 def ClearCache(self): |
| 187 """Clears the browser cache. | 245 """Clears the browser cache. |
| 188 | 246 |
| 189 Important note: ChromeDriver automatically starts | 247 Important note: ChromeDriver automatically starts |
| 190 a clean copy of Chrome on every instantiation. | 248 a clean copy of Chrome on every instantiation. |
| 191 """ | 249 """ |
| 192 self.ExecuteJavascript('if(window.chrome && chrome.benchmarking && ' | 250 res = self.ExecuteJavascript('if(window.chrome && chrome.benchmarking && ' |
| 193 'chrome.benchmarking.clearCache){chrome.benchmarking.clearCache(); ' | 251 'chrome.benchmarking.clearCache){chrome.benchmarking.clearCache(); ' |
| 194 'chrome.benchmarking.clearPredictorCache();chrome.benchmarking.' | 252 'chrome.benchmarking.clearPredictorCache();chrome.benchmarking.' |
| 195 'clearHostResolverCache();}') | 253 'clearHostResolverCache();}') |
| 254 self._logger.info('Cleared browser cache. Returned=%s', str(res)) | |
| 196 | 255 |
| 197 def LoadURL(self, url, timeout=30): | 256 def LoadURL(self, url, timeout=30): |
| 198 """Starts Chromium with any arguments previously given and navigates to the | 257 """Starts Chromium with any arguments previously given and navigates to the |
| 199 given URL. | 258 given URL. |
| 200 | 259 |
| 201 Args: | 260 Args: |
| 202 url: The URL to navigate to. | 261 url: The URL to navigate to. |
| 203 timeout: The time in seconds to load the page before timing out. | 262 timeout: The time in seconds to load the page before timing out. |
| 204 """ | 263 """ |
| 205 self._url = url | 264 self._url = url |
| 206 if not self._driver: | 265 if not self._driver: |
| 207 self._StartDriver() | 266 self._StartDriver() |
| 208 self._driver.set_page_load_timeout(timeout) | 267 self._driver.set_page_load_timeout(timeout) |
| 268 self._logger.debug('Set page load timeout to %f seconds', timeout) | |
| 209 self._driver.get(self._url) | 269 self._driver.get(self._url) |
| 270 self._logger.debug('Loaded page %s', url) | |
| 210 | 271 |
| 211 def ExecuteJavascript(self, script, timeout=30): | 272 def ExecuteJavascript(self, script, timeout=30): |
| 212 """Executes the given javascript in the browser's current page in an | 273 """Executes the given javascript in the browser's current page in an |
| 213 anonymous function. | 274 anonymous function. |
| 214 | 275 |
| 215 If you expect a result and don't get one, try adding a return statement or | 276 If you expect a result and don't get one, try adding a return statement or |
| 216 using ExecuteJavascriptStatement() below. | 277 using ExecuteJavascriptStatement() below. |
| 217 | 278 |
| 218 Args: | 279 Args: |
| 219 script: A string of Javascript code. | 280 script: A string of Javascript code. |
| 220 timeout: Timeout for the Javascript code to return in seconds. | 281 timeout: Timeout for the Javascript code to return in seconds. |
| 221 Returns: | 282 Returns: |
| 222 A string of the verbatim output from the Javascript execution. | 283 A string of the verbatim output from the Javascript execution. |
| 223 """ | 284 """ |
| 224 if not self._driver: | 285 if not self._driver: |
| 225 self._StartDriver() | 286 self._StartDriver() |
| 226 # TODO(robertogden): Use 'driver.set_script_timeout(timeout)' instead after | 287 # TODO(robertogden): Use 'driver.set_script_timeout(timeout)' instead after |
| 227 # crbug/672114 is fixed. | 288 # crbug/672114 is fixed. |
| 228 default_timeout = socket.getdefaulttimeout() | 289 default_timeout = socket.getdefaulttimeout() |
| 229 socket.setdefaulttimeout(timeout) | 290 socket.setdefaulttimeout(timeout) |
| 291 self._logger.debug('Set socket timeout to %f seconds', timeout) | |
| 230 script_result = self._driver.execute_script(script) | 292 script_result = self._driver.execute_script(script) |
| 293 self._logger.debug('Executed Javascript in browser: %s', script) | |
| 231 socket.setdefaulttimeout(default_timeout) | 294 socket.setdefaulttimeout(default_timeout) |
| 295 self._logger.debug('Set socket timeout to %s', str(default_timeout)) | |
| 232 return script_result | 296 return script_result |
| 233 | 297 |
| 234 def ExecuteJavascriptStatement(self, script, timeout=30): | 298 def ExecuteJavascriptStatement(self, script, timeout=30): |
| 235 """Wraps ExecuteJavascript() for use with a single statement. | 299 """Wraps ExecuteJavascript() for use with a single statement. |
| 236 | 300 |
| 237 Behavior is analogous to 'function(){ return <script> }();' | 301 Behavior is analogous to 'function(){ return <script> }();' |
| 238 | 302 |
| 239 Args: | 303 Args: |
| 240 script: A string of Javascript code. | 304 script: A string of Javascript code. |
| 241 timeout: Timeout for the Javascript code to return in seconds. | 305 timeout: Timeout for the Javascript code to return in seconds. |
| 242 Returns: | 306 Returns: |
| 243 A string of the verbatim output from the Javascript execution. | 307 A string of the verbatim output from the Javascript execution. |
| 244 """ | 308 """ |
| 245 return self.ExecuteJavascript("return " + script, timeout) | 309 return self.ExecuteJavascript("return " + script, timeout) |
| 246 | 310 |
| 247 def GetHistogram(self, histogram): | 311 def GetHistogram(self, histogram): |
| 248 js_query = 'statsCollectionController.getBrowserHistogram("%s")' % histogram | 312 js_query = 'statsCollectionController.getBrowserHistogram("%s")' % histogram |
| 249 string_response = self.ExecuteJavascriptStatement(js_query) | 313 string_response = self.ExecuteJavascriptStatement(js_query) |
| 314 self._logger.debug('Got %s histogram=%s', histogram, string_response) | |
| 250 return json.loads(string_response) | 315 return json.loads(string_response) |
| 251 | 316 |
| 252 def WaitForJavascriptExpression(self, expression, timeout, min_poll=0.1, | 317 def WaitForJavascriptExpression(self, expression, timeout, min_poll=0.1, |
| 253 max_poll=1): | 318 max_poll=1): |
| 254 """Waits for the given Javascript expression to evaluate to True within the | 319 """Waits for the given Javascript expression to evaluate to True within the |
| 255 given timeout. This method polls the Javascript expression within the range | 320 given timeout. This method polls the Javascript expression within the range |
| 256 of |min_poll| and |max_poll|. | 321 of |min_poll| and |max_poll|. |
| 257 | 322 |
| 258 Args: | 323 Args: |
| 259 expression: The Javascript expression to poll, as a string. | 324 expression: The Javascript expression to poll, as a string. |
| 260 min_poll: The most frequently to poll as a float. | 325 min_poll: The most frequently to poll as a float. |
| 261 max_poll: The least frequently to poll as a float. | 326 max_poll: The least frequently to poll as a float. |
| 262 Returns: The result of the expression. | 327 Returns: The result of the expression. |
| 263 """ | 328 """ |
| 264 poll_interval = max(min(max_poll, float(timeout) / 10.0), min_poll) | 329 poll_interval = max(min(max_poll, float(timeout) / 10.0), min_poll) |
| 330 self._logger.debug('Poll interval=%f seconds', poll_interval) | |
| 265 result = self.ExecuteJavascriptStatement(expression) | 331 result = self.ExecuteJavascriptStatement(expression) |
| 266 total_waited_time = 0 | 332 total_waited_time = 0 |
| 267 while not result and total_waited_time < timeout: | 333 while not result and total_waited_time < timeout: |
| 268 time.sleep(poll_interval) | 334 time.sleep(poll_interval) |
| 269 total_waited_time += poll_interval | 335 total_waited_time += poll_interval |
| 270 result = self.ExecuteJavascriptStatement(expression) | 336 result = self.ExecuteJavascriptStatement(expression) |
| 271 if not result: | 337 if not result: |
| 338 self._logger.error('%s not true after %f seconds' % (expression, timeout)) | |
| 272 raise Exception('%s not true after %f seconds' % (expression, timeout)) | 339 raise Exception('%s not true after %f seconds' % (expression, timeout)) |
| 273 return result | 340 return result |
| 274 | 341 |
| 275 def GetPerformanceLogs(self, method_filter=r'Network\.responseReceived'): | 342 def GetPerformanceLogs(self, method_filter=r'Network\.responseReceived'): |
| 276 """Returns all logged Performance events from Chrome. | 343 """Returns all logged Performance events from Chrome. |
| 277 | 344 |
| 278 Args: | 345 Args: |
| 279 method_filter: A regex expression to match the method of logged events | 346 method_filter: A regex expression to match the method of logged events |
| 280 against. Only logs who's method matches the regex will be returned. | 347 against. Only logs who's method matches the regex will be returned. |
| 281 Returns: | 348 Returns: |
| 282 Performance logs as a list of dicts, since the last time this function was | 349 Performance logs as a list of dicts, since the last time this function was |
| 283 called. | 350 called. |
| 284 """ | 351 """ |
| 285 all_messages = [] | 352 all_messages = [] |
| 286 for log in self._driver.execute('getLog', {'type': 'performance'})['value']: | 353 for log in self._driver.execute('getLog', {'type': 'performance'})['value']: |
| 287 message = json.loads(log['message'])['message'] | 354 message = json.loads(log['message'])['message'] |
| 355 self._logger.debug('Got Performance log: %s', log['message']) | |
| 288 if re.match(method_filter, message['method']): | 356 if re.match(method_filter, message['method']): |
| 289 all_messages.append(message) | 357 all_messages.append(message) |
| 358 self._logger.info('Got %d performance logs with filter method=%s', | |
| 359 len(all_messages), method_filter) | |
| 290 return all_messages | 360 return all_messages |
| 291 | 361 |
| 292 def GetHTTPResponses(self, include_favicon=False): | 362 def GetHTTPResponses(self, include_favicon=False): |
| 293 """Parses the Performance Logs and returns a list of HTTPResponse objects. | 363 """Parses the Performance Logs and returns a list of HTTPResponse objects. |
| 294 | 364 |
| 295 Use caution when calling this function multiple times. Only responses | 365 Use caution when calling this function multiple times. Only responses |
| 296 since the last time this function was called are returned (or since Chrome | 366 since the last time this function was called are returned (or since Chrome |
| 297 started, whichever is later). | 367 started, whichever is later). |
| 298 | 368 |
| 299 Args: | 369 Args: |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 315 else '', | 385 else '', |
| 316 'port': response_dict['remotePort'] if 'remotePort' in response_dict | 386 'port': response_dict['remotePort'] if 'remotePort' in response_dict |
| 317 else -1, | 387 else -1, |
| 318 'status': response_dict['status'] if 'status' in response_dict else -1, | 388 'status': response_dict['status'] if 'status' in response_dict else -1, |
| 319 'request_type': params['type'] if 'type' in params else '' | 389 'request_type': params['type'] if 'type' in params else '' |
| 320 } | 390 } |
| 321 return HTTPResponse(**http_response_dict) | 391 return HTTPResponse(**http_response_dict) |
| 322 all_responses = [] | 392 all_responses = [] |
| 323 for message in self.GetPerformanceLogs(): | 393 for message in self.GetPerformanceLogs(): |
| 324 response = MakeHTTPResponse(message) | 394 response = MakeHTTPResponse(message) |
| 395 self._logger.debug('New HTTPResponse: %s', str(response)) | |
| 325 is_favicon = response.url.endswith('favicon.ico') | 396 is_favicon = response.url.endswith('favicon.ico') |
| 326 if not is_favicon or include_favicon: | 397 if not is_favicon or include_favicon: |
| 327 all_responses.append(response) | 398 all_responses.append(response) |
| 399 self._logger.info('%d new HTTPResponse objects found in the logs %s ' | |
| 400 'favicons', len(all_responses), ('including' if include_favicon else | |
| 401 'not including')) | |
| 328 return all_responses | 402 return all_responses |
| 329 | 403 |
| 330 class HTTPResponse: | 404 class HTTPResponse: |
| 331 """This class represents a single HTTP transaction (request and response) by | 405 """This class represents a single HTTP transaction (request and response) by |
| 332 Chrome. | 406 Chrome. |
| 333 | 407 |
| 334 This class also includes several convenience functions for ChromeProxy | 408 This class also includes several convenience functions for ChromeProxy |
| 335 specific assertions. | 409 specific assertions. |
| 336 | 410 |
| 337 Attributes: | 411 Attributes: |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 440 expected_via_header = ParseFlags().via_header_value | 514 expected_via_header = ParseFlags().via_header_value |
| 441 self.assertNotIn('via', http_response.response_headers) | 515 self.assertNotIn('via', http_response.response_headers) |
| 442 if 'via' in http_response.response_headers: | 516 if 'via' in http_response.response_headers: |
| 443 self.assertNotIn(expected_via_header, | 517 self.assertNotIn(expected_via_header, |
| 444 http_response.response_headers['via']) | 518 http_response.response_headers['via']) |
| 445 | 519 |
| 446 @staticmethod | 520 @staticmethod |
| 447 def RunAllTests(): | 521 def RunAllTests(): |
| 448 """A simple helper method to run all tests using unittest.main(). | 522 """A simple helper method to run all tests using unittest.main(). |
| 449 """ | 523 """ |
| 524 flags = ParseFlags() | |
| 525 logger = GetLogger() | |
| 526 logger.debug('Command line args: %s', str(sys.argv)) | |
| 527 logger.info('sys.argv parsed to %s', str(flags)) | |
| 450 # The unittest library uses sys.argv itself and is easily confused by our | 528 # The unittest library uses sys.argv itself and is easily confused by our |
| 451 # command line options. Pass it a simpler argv instead, while working in the | 529 # command line options. Pass it a simpler argv instead, while working in the |
| 452 # unittest command line args functionality. | 530 # unittest command line args functionality. |
| 453 flags = ParseFlags() | |
| 454 unittest.main(argv=[sys.argv[0]], verbosity=2, failfast=flags.failfast, | 531 unittest.main(argv=[sys.argv[0]], verbosity=2, failfast=flags.failfast, |
| 455 catchbreak=flags.catch, buffer=(not flags.disable_buffer)) | 532 catchbreak=flags.catch, buffer=(not flags.disable_buffer)) |
| OLD | NEW |