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