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 |